动态链表创建及操作(对静态的补充)

0.修改上两节中结构体形式和遍历链表函数

1、原因:不妨增加一个结构体中的成员变量用来表示数据,修改打印链表的函数,让它同时打印节点序号和节点数据。这样,我们能直观地看出头插法和尾插法创建动态链表时的特点。

2、修改之处:

//修改点1:结构体
struct Test
{
    int node;
    int idata;							//新增了一个代表链表元素数据的变量idata
    struct Test *next;
};

//修改点2:printLink函数
void printLink(struct Test *head) 		//同时打印节点序号node和数据idata
{
    struct Test *p = head; 
    printf("node:  ");
    while(p != NULL){
        printf("%d ", p->node);
        p = p->next;
    }
    putchar('\n');
    p = head;
    printf("idata: ");
    while(p != NULL){
        printf("%d ", p->idata);
        p = p->next;
    }
    putchar('\n');
}

1.头插法创建动态链表(延续上一节:节点前插入新节点的思路)

1、封装头插法创建动态链表的API:如果把低节点位置类比于入口,那么头插法就类似于栈的先进后出的特点,不断让新节点涌进来,同时新节点总是作为链表头。头插法是在上一节“节点前插入新节点”的特殊情况,但是你不知道一开始的链表头是不是NULL。它的特点是:先输入的数据,离链表头远,后显示。

 

  1. 思路:用malloc动态开辟我们再熟悉不过了,因此先暂时忽略动态开辟的新节点,只知道它保存在结构体指针new中。另外,相比于上一节中:节点前插入新节点,我们不需要形参nodePosition。
    f1. 封装头插法的API1: struct Test* insertFromHead(struct Test *head, struct Test *new);
    形参head用来接收链表头,new用来接收新节点,返回值是结构体指针,用来在别处修改链表头head
    	f1.1 新定义一个代表节点的结构体指针变量p,用来保存链表头head: struct Test *p = head;
    	f1.2 把节点new的node修改成1,因为我们总是认为新节点是链表头: new->node = 1;
        f1.3 判断head是否是NULL,
        	f1.3.1 如果是,那么说明本来链表是空的,那么就把新节点new当做链表头返回
            	f1.3.1.1 把节点new的next成员修改成NULL,这是必要的语句: new->next = NULL;
    			//内在逻辑:head为NULL是大概率的,若不把new的next设成NULL,那么链表尾巴就无法指向NULL
            	f1.3.1.2 直接返回new,修改别处的链表头head
        	f1.3.2 否则,说明链表一开始不是空,所以
                f1.3.2.1 那么让新节点指向旧链表头: new->next = head;
    	f1.4 while循环,控制循环的变量是节点p,当p!=NULL 时,进入循环,让head及以后的节点的node都自加1
        	f1.4.1 修改节点p的node,让它自加1: p->node = p->node + 1;
    		f1.4.2 修改代表节点的循环变量p,让p往后走: p = p->next;
    	f1.5 返回节点new,让新节点作为链表头

  2. 代码:
    struct Test* insertFromHead(struct Test *head, struct Test *new)
    {
        struct Test *p = head;
        new->node = 1;
        /* 如果head确实是NULL,即本来链表是空的,那么就把new返回,提前结束函数就行了 */
        if(head == NULL){
            new->next = NULL;
            return new;
        }else{
            new->next = head;   //否则让新节点new指向旧链表头head
        }
         /* 另外,让head及之后的node都自加1 */
        while(p != NULL){      
            p->node = p->node + 1;
            p = p->next;
        }
        return new;
    }

2、封装用头插法批量创建动态链表的API:

  1. 分开写的好处:既可以通过initLink()函数批量动态创建多个节点。又可以通过insertFromHead()函数根据需要单独用头插法动态创建一个新的链表头。
  2. 思路:
    f2. 封装头插法批量创建动态链表的API2: struct Test* initLink(struct Test *head);
    形参是链表头head,函数返回值是结构体指针,用来修改main函数中head的指向
    	f2.1 定义一个代表新节点的结构体指针变量new,用来保存开辟的动态内存: struct Test *new = NULL;
    	f2.2 while死循环,直到输入的数是0才终止开辟动态内存,同时把输入的数放在new.idata中
        	f2.2.1 输入新链表头中的数据,暂时保存在变量data中: scanf("%d", &data);
    		f2.2.2 判断输入的值是否是0,
            	f2.2.2.1 如果是,说明要终止开辟动态内存,用break结束死循环
                f2.2.2.2 否则,继续开辟动态节点
                	f2.2.2.2.1 用malloc函数开辟一个动态内存,大小是结构体的占用空间,地址保存在new中:
    					new = (struct Test*)malloc(sizeof(struct Test));
    				f2.2.2.2.2 malloc后面接个必要语句,判断是否成功,不成功就exit结束整个程序
                    f2.2.2.2.3 将键盘输入data赋值给新节点new中的idata: new->idata = idata;
    				f2.2.2.2.4 设置完毕新节点new的数据后,调用API1. 把原链表头head和新节点new放到
                    		头插法的函数中,实现动态链表的创建: head = insertFromHead(head,new);
    	f2.3 返回head

  3. 代码:
    struct Test* initLink(struct Test *head)
    {
        int data;
        struct Test *new = NULL;
        while(1){
            puts("请输入新链表头的数据(输入0代表停止创建动态链表节点)");
            scanf("%d", &data);
            if(data == 0){
                break;
            }else{
                new = (struct Test*)malloc(sizeof(struct Test));
                if(new == NULL){
                    puts("malloc error");
                    exit(-1);
                }
                new->idata = data;
                head = insertFromHead(head, new);
            }
        }
        return head;
    }

3、结合以上两个函数,实现动态链表创建:它的特点是:先输入的数据,离链表头远,后显示。

  1. 总体代码和演示结果:
    #include <stdio.h>
    #include <stdlib.h>
    struct Test
    {
        int node;
        int idata;
        struct Test *next;
    };
    void printLink(struct Test *head);
    struct Test* insertFromHead(struct Test *head, struct Test *new);
    struct Test* initLink(struct Test *head);
    int main(int argc, char const *argv[])
    {
        struct Test *head = NULL;
        puts("没有申请动态节点之前");
        printLink(head);
        head = initLink(head);				//头插法批量初始化动态链表
        puts("初始化完毕,结果为:");
        printLink(head);
        return 0;
    }
    void printLink(struct Test *head) 
    {
        struct Test *p = head; 
        printf("node:  ");
        while(p != NULL){
            printf("%d ", p->node);
            p = p->next;
        }
        putchar('\n');
        p = head;
        printf("idata: ");
        while(p != NULL){
            printf("%d ", p->idata);
            p = p->next;
        }
        putchar('\n');
    }
    struct Test* insertFromHead(struct Test *head, struct Test *new)
    {
        struct Test *p = head;
        new->node = 1;
        /* 如果head确实是NULL,即本来链表是空的,那么就把new返回,提前结束函数就行了 */
        if(head == NULL){
            new->next = NULL;
            return new;
        }else{
            new->next = head;   //否则让新节点new指向旧链表头head
        }
        while(p != NULL){       //让head及之后的node都自加1
            p->node = p->node + 1;
            p = p->next;
        }
        return new;
    }
    struct Test* initLink(struct Test *head)
    {
        int data;
        struct Test *new = NULL;
        while(1){
            puts("请输入新链表头的数据(输入0代表停止创建动态链表节点)");
            scanf("%d", &data);
            if(data == 0){
                break;
            }else{
                new = (struct Test*)malloc(sizeof(struct Test));
                if(new == NULL){
                    puts("malloc error");
                    exit(-1);
                }
                new->idata = data;
                head = insertFromHead(head, new);
            }
        }
        return head;
    }

 

2.尾插法创建动态链表(延续上一节:节点后插入新节点的思路)

1、封装尾插法创建动态链表的API:如果把低节点位置类比于出口,那么尾插法就类似于队列的先进先出的特点,新节点总是作为链表尾巴。尾插法是在上一节“节点后插入新节点”的特殊情况,而且你不知道一开始的链表头是不是NULL。尾插法创建动态链表时的特点就是:(先输入的数据,离链表头近,先显示)。

 

  1. 思路:
    f1. 封装尾插法的API1: struct Test* insertFromTail(struct Test *head, struct Test *new);
    形参head用来接收链表头,new用来接收新节点,返回值是结构体指针,用来在别处修改链表头head
    	f1.1 新定义一个代表节点的结构体指针变量p,用来保存链表头head: struct Test *p = head;
    	f1.2 把节点new的next修改成NULL,因为这是在链表尾插入新节点的必要语句,否则链表尾就无法指向NULL
        f1.3 判断head是否是NULL,
        	f1.3.1 如果是,那么说明本来链表是空的,那么就把新节点new当做链表头返回
            	f1.3.1.1 把节点new的node成员修改成1,示意它现在是链表头: new->node = 1;
            	f1.3.1.2 直接返回new,修改别处的链表头head
        //否则,说明链表一开始不是空,所以让链表尾巴指向新节点new,首先要做的就是定位到原先的链表尾
        f1.4 while循环,控制循环的变量是节点p的指向,当p->next !=NULL 时,进入循环
        //内在逻辑:控制循环的条件不能是p != NULL,否则最后节点p会定位在NULL,无法建立链表尾和new的关系
    		f1.4.1 修改代表节点的循环变量p,让p往后走: p = p->next;
            f1.4.2 修该记录节点数的循环变量cnt,让它自加1: cnt++;  
        f1.5 把节点p定位到链表尾后,让链表尾指向新节点new: p->next = new;
        //最后通过cnt记录新节点的node是多少
    	f1.6 因为循环控制变量是p->next的缘故,所以cnt少加了1: cnt = cnt + 1;
    	f1.7 修改新节点new的node,由于新节点插入在节点数为cnt的链表尾部,所以: new->node = cnt + 1;
        f1.8 返回链表头head

  2. 代码:
    truct Test* insertFromTail(struct Test *head, struct Test *new)
    {
        int cnt = 0;
        struct Test *p = head;
        new->next = NULL;
        /* 如果head确实是NULL,即本来链表是空的,那么就把new返回,提前结束函数就行了 */
        if(head == NULL){
            new->node = 1;
            return new;
        }
        /* 否则让链表尾巴指向新节点new */
        while(p->next != NULL){       
            p = p->next;
            cnt++;
        }
        p->next = new;
        /* 另外记录new的node是多少 */
        cnt = cnt + 1;
        new->node = cnt + 1;
        return head;
    }

2、封装用尾插法批量创建动态链表的API:

  1. 两个函数分开写的好处:既可以通过initLink()函数批量动态创建多个节点。又可以通过insertFromTail()函数根据需要单独用尾插法动态创建一个新的链表尾巴。
  2. 思路:
    f2. 封装尾插法批量创建动态链表的API2: struct Test* initLink(struct Test *head);
    形参是链表头head,函数返回值是结构体指针,用来修改main函数中head的指向
    	f2.1 定义一个代表新节点的结构体指针变量new,用来保存开辟的动态内存: struct Test *new = NULL;
    	f2.2 while死循环,直到输入的数是0才终止开辟动态内存,同时把输入的数放在new.idata中
        	f2.2.1 输入新链表头中的数据,暂时保存在变量data中: scanf("%d", &data);
    		f2.2.2 判断输入的值是否是0,
            	f2.2.2.1 如果是,说明要终止开辟动态内存,用break结束死循环
                f2.2.2.2 否则,继续开辟动态节点
                	f2.2.2.2.1 用malloc函数开辟一个动态内存,大小是结构体的占用空间,地址保存在new中:
    					new = (struct Test*)malloc(sizeof(struct Test));
    				f2.2.2.2.2 malloc后面接个必要语句,判断是否成功,不成功就exit结束整个程序
                    f2.2.2.2.3 将键盘输入data赋值给新节点new中的idata: new->idata = idata;
    				f2.2.2.2.4 设置完毕新节点new的数据后,调用API1. 把原链表头head和新节点new放到
                    		尾插法的函数中,实现动态链表的创建: head = insertFromTail(head,new);
    	f2.3 返回head

  3. 代码:
    struct Test* initLink(struct Test *head)
    {
        int data;
        struct Test *new = NULL;
        while(1){
            puts("请输入新链表尾的数据(输入0代表停止创建动态链表节点)");
            scanf("%d", &data);
            if(data == 0){
                break;
            }else{
                new = (struct Test*)malloc(sizeof(struct Test));
                if(new == NULL){
                    puts("malloc error");
                    exit(-1);
                }
                new->idata = data;
                head = insertFromTail(head, new);
            }
        }
        return head;
    }

3、结合以上两个函数,实现动态链表创建:(先输入的数据,离链表头近,先显示)

  1. 总体代码和演示结果:
    #include <stdio.h>
    #include <stdlib.h>
    struct Test
    {
        int node;
        int idata;
        struct Test *next;
    };
    void printLink(struct Test *head);
    struct Test* insertFromTail(struct Test *head, struct Test *new);
    struct Test* initLink(struct Test *head);
    int main(int argc, char const *argv[])
    {
        struct Test *head = NULL;
        puts("没有申请动态节点之前");
        printLink(head);
        head = initLink(head);				//尾插法批量初始化动态链表
        puts("初始化完毕,结果为:");
        printLink(head);
        return 0;
    }
    void printLink(struct Test *head) 
    {
        struct Test *p = head; 
        printf("node:  ");
        while(p != NULL){
            printf("%d ", p->node);
            p = p->next;
        }
        putchar('\n');
        p = head;
        printf("idata: ");
        while(p != NULL){
            printf("%d ", p->idata);
            p = p->next;
        }
        putchar('\n');
    }
    struct Test* insertFromTail(struct Test *head, struct Test *new)
    {
        int cnt = 0;
        struct Test *p = head;
        new->next = NULL;
        /* 如果head确实是NULL,即本来链表是空的,那么就把new返回,提前结束函数就行了 */
        if(head == NULL){
            new->node = 1;
            return new;
        }
        /* 否则让链表尾巴指向新节点new */
        while(p->next != NULL){       
            p = p->next;
            cnt++;
        }
        p->next = new;
        /* 另外记录new的node是多少 */
        cnt = cnt + 1;
        new->node = cnt + 1;
        return head;
    }
    struct Test* initLink(struct Test *head)
    {
        int data;
        struct Test *new = NULL;
        while(1){
            puts("请输入新链表尾的数据(输入0代表停止创建动态链表节点)");
            scanf("%d", &data);
            if(data == 0){
                break;
            }else{
                new = (struct Test*)malloc(sizeof(struct Test));
                if(new == NULL){
                    puts("malloc error");
                    exit(-1);
                }
                new->idata = data;
                head = insertFromTail(head, new);
            }
        }
        return head;
    }

 

3.头插法和尾插法代码总结

tip1:这两个程序很相近,我们都可以分为两个函数,第一个就是头插法或尾插法的API1,它们只负责:①建立新节点new和原先链表head的联系,②注明节点序号。

tip2:第二个是批量初始化动态节点的API2,它们负责:①循环开辟新节点new,②键盘输入初始化新节点new中的数据idata,③把head和new打包给API1。

4.完善删除链表节点的API

代码心得:

  1. 被删除的那个节点,我们不希望它再占用空间了,可以使用free函数(<stdlib.h>)把垃圾清除掉,其中free函数在“为字符串开辟动态内存”一节中讲过
  2. 在Linux中出现忘记函数声明在哪个头文件中的情况时,使用man指令能为开发带来便利,man是manual(手册)的缩写。比如man 3 free :代表尝试去手册第3页找库函数free的函数声明所在的头文件。
  3. 在delNode函数中,我们可以提前定义一个结构体指针deletedNode,用来代表将来要释放的链表节点,这是因为:在被删除节点还与它前面的节点有连接关系之前,不能随意释放它,如果释放后再对它操作会发生段错误,于是,要等被删节点与其他东西没关系的时候再释放。
  4. 删除节点时,要考虑删除的是否是第一个节点。①如果删除第一个节点,那么链表头会发生变化,体现在main函数中的head的指向发生变化。②如果删除的不是第一个节点,那么不需要修改链表头,以删除节点4为例,这里要注意:不能等到节点p移到4的位置才进行删除,而是需要通过节点3找节点4,因为我们需要修改节点3的指向(绕过节点4,指向节点5)。如图:

 

 

1、delNode()函数完善后的代码:

  1. 思路:先用头插法或者尾插法创建一个动态链表,然后再对delNode()函数进行测试
    f1. 封装删除静态链表某节点的API: struct Test* delNode(struct Test *head, int nodePosition);
    形参head用来接收链表头,形参nodePosition代表想要删除第几个,
    返回值是结构体指针,用来修改main函数的链表头head
    	f1.1 新定义一个代表节点的结构体指针变量p,用来保存链表头head: struct Test *p = head;
    	f1.2 新定义一个代表被删节点的结构体指针变量deletedNode: struct Test *deletedNode;
        f1.3 判断是否删除的是第1个节点
        	f1.3.1 如果是,说明链表头被删除,并且链表头需要改动
            	f1.3.1.1 记录被删节点: deletedNode = head;
            	f1.3.1.2 修改链表头的指向,让head指向节点2: head = head->next;
    			f1.3.1.2 用free函数释放掉被删节点: free(deletedNode);
    			f1.3.1.3 将节点p移动到新链表头,接下来准备修改往后各个节点的node: p = head;
    			f1.3.1.4 while循环,控制循环的变量p,当p!=NULL 时,进入循环,把后面所有节点的node-1
                    f1.3.1.4.1 从节点p开始,也就是从新链表头开始,修改node: p->node = p->node - 1;
    				f1.3.1.4.2 修改代表节点的循环变量p,让p往后走: p = p->next;
    			f1.3.1.5 修改完毕,返回节点head,提前结束函数: return head;
    	//否则,说明删除的不是链表头,不用修改链表头,接下来就正常删除
    	f1.4 while循环,控制循环的变量是链表头p,当p->next!=NULL 时,进入循环,希望找出被删节点
        //内在逻辑:控制循环条件不能是p!=NULL,否则最后节点p会定位在被删节点,无法解除被删节点和前节点的关系
        	f1.4.1 通过前一个节点访问后一个节点的node,判断 p->next->node 是否等于nodePosition
            	f1.4.1.1 如果是,说明找到可删除节点
                	f1.4.1.1.1 修改代表找到设定节点位置nodePosition的变量flag: flag = 1;
    				//内在逻辑:用flag=1代表找到了位置删除,flag=0代表没有找到位置删除,flag初始化成0
    				f1.4.1.1.2 记录被删节点: deletedNode = p->next;
    				f1.4.1.1.3 让节点p指向设定删除节点的后一个节点,断开节点p和它原先后面节点的连接: 
    					p->next = p->next->next;
    				f1.4.1.1.4 用free函数释放掉被删节点: free(deletedNode);
    				f1.4.1.1.5 删除成功后,break提前退出循环,不急着return,f1.4中来改后面节点的node
                f1.4.1.2 否则,修改代表节点的循环变量p,让p往后走: p = p->next;
        f1.5 判断代表找到设定节点位置nodePosition的变量flag是否等于1
        //内在逻辑:删除成功后,在return head之前,修改被删除节点后面节点的node,让它们减1。
        	f1.5.1 如果是,接下来让被删节点后面的节点中的node成员变量自减1
            	f1.5.1.1 修改代表节点的变量p,让p往后走: p = p->next;
    			//内在逻辑:比如说删除节点4之后,那么就从节点3跳到节点5,从原先的节点5位置开始修改node
    			f1.5.1.2 while循环,控制循环的变量p,当p!=NULL 时,进入循环
                	f1.5.1.2.1 修改循环变量p中的node成员变量,自减1: p->node = p->node - 1;
    				f1.5.1.2.2 修改代表节点的循环变量p,让p往后走: p = p->next;
    			f1.5.1.3 修改完毕,返回head,结束函数。
    		f1.4.2 否则,代表删除失败(比如被删节点的位置输入的过大)
            	那么,返回head  //即使删除失败,我们也不希望链表头发生改变

  2. 代码与演示:
    #include <stdio.h>
    #include <stdlib.h>
    struct Test
    {
        int node;
        int idata;
        struct Test *next;
    };
    void printLink(struct Test *head);
    struct Test* insertFromTail(struct Test *head, struct Test *new);
    struct Test* initLink(struct Test *head);
    struct Test* delNode(struct Test *head, int nodePosition);
    int main(int argc, char const *argv[])
    {
        struct Test *head = NULL;
        int nodePosition;
        puts("没有申请动态节点之前");
        printLink(head);
        head = initLink(head);				//尾插法批量初始化动态链表
        puts("初始化完毕,结果为:");
        printLink(head);
        puts("请输入删除哪个节点");
        scanf("%d", &nodePosition);
        head = delNode(head, nodePosition); 
        puts("删除节点以后");
        printLink(head);
        return 0;
    }
    void printLink(struct Test *head) 
    {
        struct Test *p = head; 
        printf("node:  ");
        while(p != NULL){
            printf("%d ", p->node);
            p = p->next;
        }
        putchar('\n');
        p = head;
        printf("idata: ");
        while(p != NULL){
            printf("%d ", p->idata);
            p = p->next;
        }
        putchar('\n');
    }
    struct Test* insertFromTail(struct Test *head, struct Test *new)
    {
        int cnt = 0;
        struct Test *p = head;
        new->next = NULL;
        /* 如果head确实是NULL,即本来链表是空的,那么就把new返回,提前结束函数就行了 */
        if(head == NULL){
            new->node = 1;
            return new;
        }
        /* 否则让链表尾巴指向新节点new */
        while(p->next != NULL){       
            p = p->next;
            cnt++;
        }
        p->next = new;
        /* 另外记录new的node是多少 */
        cnt = cnt + 1;
        new->node = cnt + 1;
        return head;
    }
    struct Test* initLink(struct Test *head)
    {
        int data;
        struct Test *new = NULL;
        while(1){
            puts("请输入新链表尾的数据(输入0代表停止创建动态链表节点)");
            scanf("%d", &data);
            if(data == 0){
                break;
            }else{
                new = (struct Test*)malloc(sizeof(struct Test));
                if(new == NULL){
                    puts("malloc error");
                    exit(-1);
                }
                new->idata = data;
                head = insertFromTail(head, new);
            }
        }
        return head;
    }
    struct Test* delNode(struct Test *head, int nodePosition)
    {
        int flag = 0;
        struct Test *p = head;
        struct Test *deletedNode;
        if(nodePosition == 1){
            deletedNode = head;
            head = head->next;
            free(deletedNode);
            p = head;
            while(p != NULL){
                p->node = p->node - 1;
                p = p->next;
            }
            return head;
        }
        while(p->next != NULL){
            if(p->next->node == nodePosition){
                flag = 1;
                deletedNode = p->next;
                p->next = p->next->next;
                free(deletedNode);
                break;
            }else{
                p = p->next;
            }
        }
        if(flag == 1){
            p = p->next;
            while(p != NULL){
                p->node = p->node - 1;
                p = p->next;
            }
            return head;
        }else{
            puts("删除失败");
            return head;
        }
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值