数据结构学习源码_02

  • Post author:
  • Post category:其他




本节课讲述数据结构顺序表的实现,源码及注释如下:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
// 定义链表的结点类型——数据区+地址区
typedef struct ListNode{
    int data;
    struct ListNode *next;
}ListNode;
// 定义链表类型,以便于为指定链表附加length属性,便于对链表进行管理
typedef struct LinkList{
    /*  这里不是定义一个ListNode型指针,而是定义了一个ListNode类型的元素(虚拟头节点)
        使用head的地址域来指向链表的第一个节点而不是让head作为第一个节点
        [head|val|&p1] -> [p1|val|&p2] -> [p2|val|p3] -> [p3|val|NULL] -> NULL
        由于插入和删除操作都要找到对应节点的前一个节点,因此对于上述结构,若对第0位进行操作无需移动,对第1位进行操作只需移动1位,对第2位进行操作只需移动2位,以此类推...
    */
    ListNode head;
    // 链表的元素个数
    int length;
}LinkList;

// 因为有两个结构(链表结构和链表节点结构),因此要对两个结构分别初始化
// 初始化链表节点,在插入新节点时调用
ListNode *init_listnode(int val){
    ListNode *p=(ListNode*)malloc(sizeof(ListNode));
    p->data = val;
    return p;
}

// 初始化链表
LinkList *init_linklist(){
    LinkList *l=(LinkList*)malloc(sizeof(LinkList));
    // 链表的第一个节点为空
    l->head.next = NULL;
    l->length=0;
    return l;
}

// 释放节点空间,在释放链表空间时调用
void clear_listnode(ListNode *node){
    // 若该节点为空,则直接返回
    if (node == NULL) return ;
    // 释放该节点
    free(node);
    return ;
}

// 释放链表空间
void clear_linklist(LinkList *l){
    // 若该链表为空,释放该链表
    if (l == NULL) return;
    // 否则,从头节点开始遍历每一个节点并释放节点空间
    ListNode *p = l->head.next, *q;
    while (p){
    	// !!!先用q保存循环因子p的下一个节点的地址,防止后续节点丢失
        q = p->next;
        //!!!再对节点进行释放
        clear_listnode(p);
        //!!!最后一步通过交换地址实现遍历
        p = q;
    }
    free(l);
    return;
}

// 指定位置插入节点
int insert(LinkList *l, int ind, int val){
    // 若链表为空,插入失败
    if (l == NULL) return 0;
    // 若插入位置在第一个元素之前或在最后一个元素之后两位,插入失败
    if (ind < 0 || ind > l->length) return 0;
    // 取得head的地址以访问链表并初始化要插入的新节点
    // node用来指向要插入的节点
    ListNode *p = &(l->head), *node = init_listnode(val);
    while(ind--){
    	// 用p找到待插入位置的前一个节点
        p=p->next;
    }
    // !!!第一步先设置要插入的节点的指针区以保存其后续节点的地址
    node->next = p->next;
    // !!!第二步再设置前一个节点的指针区指向要插入的节点的地址
    p->next = node;
    // 链表长度加一
    l->length++;
    return 1;
}

int erase(LinkList *l, int ind){
    if (l == NULL) return 0;
    if (ind < 0 || ind >= l->length) return 0;
    // 用指针p找到待删除节点的前一个节点
    ListNode *p = &(l->head), *q;
    while (ind--){
        p = p->next;
    }
    // 先用q保存待删除节点的后一个节点的地址
    q = p->next->next;
    // 直接释放p->next节点
    clear_listnode(p->next);
    //将待删除节点的前一个节点与后一个节点相连
    p->next = q;
    // 链表长度减一
    l->length--;
    return 1;
}

// 输出整个链表
void output(LinkList *l){
    printf("LinkList(%d): ", l->length);
    // p非空(p!=NULL)也可以直接写成p
    for (ListNode *p = l->head.next; p!=NULL; p = p->next){
        printf("%d -> ", p->data);
    }
    printf("NULL\n");
    return ;
}

#define MAX_OP 30
int main(){
    srand(time(0));
    LinkList *l = init_linklist();
    for(int i = 0; i < MAX_OP; i++){
        int op = rand() % 3;
        // 测试用位置在0到l->length之间
        int ind = rand() % (l->length + 1);
        int val = rand() % 100;
        switch (op)
        {
            // 当哦op为0或1时均执行插入操作,实现66%的概率测试插入操作
            case 0:
            case 1: {
                printf("insert %d at %d to LinkList = %d\n",
                 val, ind, insert(l, ind, val));
            } break;
            case 2: {
                printf("erase item at %d from LinkList = %d\n",
                ind, erase(l, ind));
            } break;
        }
        output(l);
        printf("\n");
    }
    clear_linklist(l);
    return 0;
}



重要执行函数实现过程:

在这里插入图片描述

如图要把指针node所指向的④号节点插入到②号节点的位置

在这里插入图片描述

先遍历整个链表,用指针p找到②号节点的前一个节点

在这里插入图片描述

将④号节点的地址区(下一个节点的地址)指向p->next,也就是②号节点的地址。

在这里插入图片描述

将指针p所指节点的地址区指向④号节点,也就是p->next=node

在这里插入图片描述

大功告成!一定要注意插入操作的先后顺序,若先将p->next指向node则会丢失后面的所有节点,造成内存泄漏!!!另外对于单向循环列表,为方便进行插入和删除节点的操作,一般把head所指向的节点看作尾节点。



版权声明:本文为qq_44800871原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。