C语言-基础入门-学习笔记(16):单链表与双链表
一、链表简介
我们都知道,数组虽然使用方便,但是有两个重要的缺陷:
(1)数组内的元素类型必须相同。
(2)数组的元素个数在初始化之后就不能被改变了。
那么对于这两种情况的解决方法分别是:
(1)通过结构体来实现元素类型的不同。
(2)通过链表方式来实现元素个数的改变。
几乎可以这样理解:链表就是一个元素个数可以实时变大/变小的数组。
顾名思义,链表就是用锁链连接起来的表。这里的表指的是一个一个的节点(一个节点就是一个校区),节点中有一些内存可以用来存储数据(所以叫表,表就是数据表);这里的锁链指的是链接各个表的方法,C语言中用来连接2个表(其实就是2块内存)的方法就是指针。
链表是由若干个节点组成的(链表的各个节点结构是完全类似的),节点是由有效数据和指针组成的。有效数据区域用来存储信息完成任务的,指针区域用于指向链表的下一个节点从而构成链表。
二、单链表
1. 单链表的简单实现
实现前需要注意的点:
(1)由于链表是由一个一个节点组成的,而每个节点包括有效数据和指针,即是两个不同类型的变量,所以对于节点node采用结构体的方式来实现。
(2)链表的内存要求比较灵活,不能用栈,也不能用data数据段。只能用堆内存。
(3)头指针并不是节点,而是一个普通指针,只占4字节。头指针的类型是 struct node * 类型的,所以它才能指向链表的节点。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//构建链表节点,其中包括有效数据和指针
struct node{
int data; //有效数据
struct node *pNext; //指针
};
//构建链表节点函数
struct node* create_node(int data){
//定义一个节点p
struct node *p = (struct node *)malloc(sizeof(struct node));
//判断是否申请到了堆内存
if(NULL == p){
printf("malloc error.\n");
return NULL;
}
//给申请到的内存清零
memset(p,0,sizeof(struct node));
//向节点中写入数据
p->data = data;
p->pNext = NULL;
return p;
}
int main(void){
//构建头指针
struct node *pHeader = NULL;
/************构建第一个节点*************/
//这里函数的返回值是创建好的节点1的首地址,然后用头指针指向
pHeader = create_node(1); //通过此语句将头指针和节点1相连
/************构建第二个节点*************/
//这里函数的返回值是创建好的节点2的首地址,然后节点1的next指针指向
pHeader->pNext = create_node(2); //通过此语句将节点1和节点2相连
/************构建第三个节点*************/
//这里函数的返回值是创建好的节点3的首地址,然后节点2的next指针指向
pHeader->pNext->pNext = create_node(3); //通过此语句将节点2和节点3相连
printf("node1 data: %d\n",pHeader->data);
printf("node2 data: %d\n",pHeader->pNext->data);
printf("node3 data: %d\n",pHeader->pNext->pNext->data);
return 0;
}
2. 单链表插入新节点
这里为了操作方便,引入了头结点。头节点的特点是:第一,它紧跟在头指针后面。第二,头节点的数据部分是空的(有时候不是空的,而是存储整个链表的节点数),指针部分指向下一个节点,也就是第一个节点。
节点插入思路:
(1)尾部插入:将最后一个节点的pNext指针指向新添加的节点首地址,然后将新节点的pNext指针指向NULL,成为尾结点。
(2)头部插入:先将新节点的pNext指针指向节点1的首地址(因为一旦首节点与节点1的链断开,将无法找到节点1的首地址),然后再将首节点的pNext指针指向新节点的首地址。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//构建链表节点,其中包括有效数据和指针
struct node{
int data; //有效数据
struct node *pNext; //指针
};
//构建链表节点函数(输入参数为节点内数据)
struct node* create_node(int data){
//定义一个节点p
struct node *p = (struct node *)malloc(sizeof(struct node));
//判断是否申请到了堆内存
if(NULL == p){
printf("malloc error.\n");
return NULL;
}
//给申请到的内存清零
memset(p,0,sizeof(struct node));
//向节点中写入数据
p->data = data;
p->pNext = NULL;
return p;
}
//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
int cnt = 0; //节点计数
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext; //向后移一个节点
cnt++;
}
p->pNext = new; //最后一个节点的pNext指针指向新添加的节点首地址
ph->data = cnt + 1; //计数节点个数
}
//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
int cnt = 0;
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext; //向后移一个节点
cnt++;
}
new->pNext = ph->pNext; //先将节点1的首地址和新节点的pNext指针绑定
ph->pNext = new; //再将新节点的首地址和头节点的pNext指针绑定
ph->data = cnt + 1; //计数节点个数
}
int main(void){
//构建头指针和首节点
struct node *pHeader = create_node(0); //这里首节点的值先初始化为0(后期里面的值还可以表示节点个数)
//进行尾部插入: 1, 2, 3
tail_insert(pHeader, create_node(1));
tail_insert(pHeader, create_node(2));
tail_insert(pHeader, create_node(3));
//进行头部插入: 4, 5, 6, 1, 2, 3
head_insert(pHeader, create_node(6));
head_insert(pHeader, create_node(5));
head_insert(pHeader, create_node(4));
printf("The number of node is: %d\n",pHeader->data);
printf("node1 data: %d\n",pHeader->pNext->data);
printf("node2 data: %d\n",pHeader->pNext->pNext->data);
printf("node3 data: %d\n",pHeader->pNext->pNext->pNext->data);
printf("node4 data: %d\n",pHeader->pNext->pNext->pNext->pNext->data);
return 0;
}
3. 单链表之链表遍历
单链表的特点就是由很多个节点组成,头指针+头节点为整个链表的起始,最后一个节点的特征是它内部的pNext指针值为NULL。从起始到结尾中间由各个节点内部的pNext指针来挂接。通过起始+移动+结束标志的方法即可实现遍历。
具体方法为:从头指针+头节点开始,顺着链表挂接指针依次访问链表的各个节点,取出这个节点的数据,然后再往下一个节点,直到最后一个节点,结束返回。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//构建链表节点,其中包括有效数据和指针
struct node{
int data; //有效数据
struct node *pNext; //指针
};
//构建链表节点函数(输入参数为节点内数据)
struct node* create_node(int data){
//定义一个节点p
struct node *p = (struct node *)malloc(sizeof(struct node));
//判断是否申请到了堆内存
if(NULL == p){
printf("malloc error.\n");
return NULL;
}
//给申请到的内存清零
memset(p,0,sizeof(struct node));
//向节点中写入数据
p->data = data;
p->pNext = NULL;
return p;
}
//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
int cnt = 0; //节点计数
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext; //向后移一个节点
cnt++;
}
p->pNext = new; //最后一个节点的pNext指针指向新添加的节点首地址
ph->data = cnt + 1; //计数节点个数
}
//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
int cnt = 0;
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext; //向后移一个节点
cnt++;
}
new->pNext = ph->pNext;
ph->pNext = new;
ph->data = cnt + 1; //计数节点个数
}
//链表遍历函数(输入参数为首节点)
void traversal(struct node *ph){
struct node *p = ph;
printf("开始遍历:\n");
while(NULL != p->pNext){
p = p->pNext;
printf("node data: %d\n", p->data);
}
printf("结束遍历:\n");
}
int main(void){
//构建头指针和首节点
struct node *pHeader = create_node(0); //这里首节点的值先初始化为0(后期里面的值还可以表示节点个数)
//进行尾部插入: 1, 2, 3
tail_insert(pHeader, create_node(1));
tail_insert(pHeader, create_node(2));
tail_insert(pHeader, create_node(3));
//进行头部插入: 4, 5, 6, 1, 2, 3
head_insert(pHeader, create_node(6));
head_insert(pHeader, create_node(5));
head_insert(pHeader, create_node(4));
traversal(pHeader);
return 0;
}
4. 单链表之节点删除
删除节点重点是要先找到这个节点,然后删除它。
删除思路:通过遍历来查找节点。从头指针+头节点开始,顺着链表依次将各个节点拿出来,按照一定的方法比对,找到我们要删除的那个节点。
(1)待删除的不是尾节点:首先把待删除的节点的前一个节点的pNext指针指向待删除的节点的后一个节点的首地址(这样就把这个节点从链表中摘出来了),然后再将这个摘出来的节点free掉接口。
(2)待删除的是尾节点:首先把待删除的尾节点的前一个节点的pNext指针指向null(这时候就相当于原来尾节点前面的一个节点变成了新的尾节点),然后再将这个摘出来的节点free掉接口。
由于当遍历到待删除节点后,需要知道该节点的首地址,只能从它的前一个节点来获取,那么我们在移动到该节点之前就要先将待删除的前一个节点的pNext指针(待删除节点首地址)保存下来。用到了pPrev指针。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//构建链表节点,其中包括有效数据和指针
struct node{
int data; //有效数据
struct node *pNext; //指针
};
//构建链表节点函数(输入参数为节点内数据)
struct node* create_node(int data){
//定义一个节点p
struct node *p = (struct node *)malloc(sizeof(struct node));
//判断是否申请到了堆内存
if(NULL == p){
printf("malloc error.\n");
return NULL;
}
//给申请到的内存清零
memset(p,0,sizeof(struct node));
//向节点中写入数据
p->data = data;
p->pNext = NULL;
return p;
}
//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
int cnt = 0; //节点计数
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext; //向后移一个节点
cnt++;
}
p->pNext = new; //最后一个节点的pNext指针指向新添加的节点首地址
ph->data = cnt + 1; //计数节点个数
}
//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
int cnt = 0;
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext; //向后移一个节点
cnt++;
}
new->pNext = ph->pNext;
ph->pNext = new;
ph->data = cnt + 1; //计数节点个数
}
//链表遍历函数(输入参数为首节点)
void traversal(struct node *ph){
struct node *p = ph;
printf("开始遍历:\n");
while(NULL != p->pNext){
p = p->pNext;
printf("node data: %d\n", p->data);
}
printf("结束遍历:\n");
}
//链表删除节点函数(输入参数为首节点,要删除的节点数据)
int delate_node(struct node *ph, int data){
struct node *p = ph; //用来指向当前节点
struct node *pPrev = NULL; // 用来指向当前节点的前一个节点
//循环检测直到NULL结束
while(NULL != p->pNext){
pPrev = p; // 在p走向下一个节点前先将其保存
p = p->pNext; // 走到下一个节点,也就是循环增量
//如果检测到待删除数据
if(p->data == data){
//待删除节点为尾结点时
if(NULL == p->pNext){
pPrev->pNext = NULL; //待删除节点的的前一个节点的pNext指向NULL
free(p); //释放节点
}
//待删除节点不是尾结点时
else{
pPrev->pNext = p->pNext;//待删除节点的前一个节点的pNext指向下一个节点的首地址
free(p); //释放节点
}
return 0;
}
}
printf("没找到这个节点.\n");
return -1;
}
int main(void){
//构建头指针和首节点
struct node *pHeader = create_node(0); //这里首节点的值先初始化为0(后期里面的值还可以表示节点个数)
//进行尾部插入: 1, 2, 3
tail_insert(pHeader, create_node(1));
tail_insert(pHeader, create_node(2));
tail_insert(pHeader, create_node(3));
//进行头部插入: 4, 5, 6, 1, 2, 3
head_insert(pHeader, create_node(6));
head_insert(pHeader, create_node(5));
head_insert(pHeader, create_node(4));
traversal(pHeader);
delate_node(pHeader,5);
printf("节点删除后:\n");
traversal(pHeader);
return 0;
}
5. 单链表之逆序
逆序思路:首先遍历原链表,然后将原链表中的头指针和头节点作为新链表的头指针和头节点,原链表中的有效节点挨个依次取出来,采用头插入的方法插入新链表中即可。
链表逆序 = 遍历 + 头插入
第一步先将node1的pNext指向NULL,此时链被断开了,无法通过头指针进行链的遍历,所以在断开前要先将下一个节点的首地址进行pBack的保存,然后依次将剩余的节点进行头插,实现逆序。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//构建链表节点,其中包括有效数据和指针
struct node{
int data; //有效数据
struct node *pNext; //指针
};
//构建链表节点函数(输入参数为节点内数据)
struct node* create_node(int data){
//定义一个节点p
struct node *p = (struct node *)malloc(sizeof(struct node));
//判断是否申请到了堆内存
if(NULL == p){
printf("malloc error.\n");
return NULL;
}
//给申请到的内存清零
memset(p,0,sizeof(struct node));
//向节点中写入数据
p->data = data;
p->pNext = NULL;
return p;
}
//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
int cnt = 0; //节点计数
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext; //向后移一个节点
cnt++;
}
p->pNext = new; //最后一个节点的pNext指针指向新添加的节点首地址
ph->data = cnt + 1; //计数节点个数
}
//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
int cnt = 0;
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext; //向后移一个节点
cnt++;
}
new->pNext = ph->pNext;
ph->pNext = new;
ph->data = cnt + 1; //计数节点个数
}
//链表遍历函数(输入参数为首节点)
void traversal(struct node *ph){
struct node *p = ph;
printf("开始遍历:\n");
while(NULL != p->pNext){
p = p->pNext;
printf("node data: %d\n", p->data);
}
printf("结束遍历:\n");
}
//链表逆序函数(输入参数为首节点)
void reverse_list(struct node *ph){
struct node *p = ph->pNext; // pH指向头节点,p指向第1个有效节点
struct node *pBack; // 保存当前节点的后一个节点地址
//如果为首节点或只有一个有效节点,那么不需要逆序
if(NULL == p || NULL == p->pNext){
return;
}
while(NULL != p->pNext){
//在断开链之前,先将下一个节点的首地址保存到pBack指针内
pBack = p->pNext;
//如果是第一个节点的话,将其pNext指向NULL
if(ph->pNext == p){
p->pNext = NULL;
}
//将从第二个开始的节点进行前插
else{
head_insert(ph, p);
}
p = pBack; //因为第一个节点时pNext被指向NULL,所以需要借助pBack找到下一个节点的首地址
}
//当最后一个节点时,pNext指向NULL,所以没有进入循环内进行前插,所以需要单独对最后一个节点进行前插
head_insert(ph, p);
}
int main(void){
//构建头指针和首节点
struct node *pHeader = create_node(0); //这里首节点的值先初始化为0(后期里面的值还可以表示节点个数)
//进行尾部插入: 1, 2, 3
tail_insert(pHeader, create_node(1));
tail_insert(pHeader, create_node(2));
tail_insert(pHeader, create_node(3));
//进行头部插入: 4, 5, 6, 1, 2, 3
head_insert(pHeader, create_node(6));
head_insert(pHeader, create_node(5));
head_insert(pHeader, create_node(4));
traversal(pHeader);
reverse_list(pHeader);
printf("逆序后:\n");
traversal(pHeader);
return 0;
}
三、双链表
单链表各个节点之间只由一个指针单向链接,这样实现有一些局限性。局限性主要体现在单链表只能经由指针单向移动(一旦指针移动过某个节点就无法再回来,如果要再次操作这个节点除非从头指针开始再次遍历一次),因此单链表在某些算法上具有局限性。于是我们引出了双链表。
单链表的节点 = 有效数据 + 指针(指针指向后一个节点)
双向链表的节点 = 有效数据 + 2个指针(一个指向后一个节点,另一个指向前一个节点)
1. 双链表的简单实现
双链表的特点是,每个节点包含2个指针和一个数据区,其中pPrev指针指向前一个节点的首地址,pNext指针指向后一个节点的首地址。头结点的pPrev指针指向NULL,尾结点的pNext指针指向NULL。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//构建链表节点,其中包含有效数据和两个指针
struct node{
int data; //有效数据
struct node *pPrev; //前向指针
struct node *pNext; //后向指针
};
struct node * create_node(int data){
//定义一个节点p
struct node *p = (struct node *)malloc(sizeof(struct node));
//判断内存申请是否成功
if(NULL == p){
printf("malloc error.\n");
return NULL;
}
//如果申请成功了后,给内存清零
memset(p, 0, sizeof(struct node));
//向节点写入数据
p->data = data;
p->pPrev = NULL;
p->pNext = NULL;
return p;
}
int main(void){
//构建头指针和头节点
struct node *pHeader = create_node(0);
/************构建第一个节点*************/
pHeader->pNext = create_node(11);
/************构建第二个节点*************/
pHeader->pNext->pNext = create_node(12);
/************构建第三个节点*************/
pHeader->pNext->pNext->pNext = create_node(13);
//打印查看节点
printf("node1 data: %d\n",pHeader->pNext->data);
printf("node2 data: %d\n",pHeader->pNext->pNext->data);
printf("node3 data: %d\n",pHeader->pNext->pNext->pNext->data);
return 0;
}
2. 双链表之插入新节点
节点插入思路:
(1)尾部插入:将最后一个节点的pNext指针指向新添加的节点首地址;将新节点的pPrev指针指向上一个节点的首地址;将新节点的pNext指针指向NULL。
(2)头部插入:将头节点的pNext指针指向新节点的首地址;将第一个节点的pPrev指针指向新节点的首地址;将新节点的pPrev指针指向头节点的首地址;将新节点的pNext指针指向第一个节点的首地址。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//构建链表节点,其中包含有效数据和两个指针
struct node{
int data; //有效数据
struct node *pPrev; //前向指针
struct node *pNext; //后向指针
};
//构建链表节点函数(输入参数为节点内数据)
struct node * create_node(int data){
//定义一个节点p
struct node *p = (struct node *)malloc(sizeof(struct node));
//判断内存申请是否成功
if(NULL == p){
printf("malloc error.\n");
return NULL;
}
//如果申请成功了后,给内存清零
memset(p, 0, sizeof(struct node));
//向节点写入数据
p->data = data;
p->pPrev = NULL;
p->pNext = NULL;
return p;
}
//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
int cnt = 0; //节点计数
struct node *p = ph;
while(NULL != p->pNext){
//第一个作用是走的尾部,第二个作用是直接从第一个有效节点开始
p = p->pNext;
cnt++;
}
p->pNext = new; //将最后一个节点的pNext指针指向新添加的节点首地址
new->pPrev = p; //将新节点的pPrev指针指向最后一个节点的首地址
ph->data = cnt + 1;
}
//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
int cnt = 0;
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext;
cnt++;
}
//(1)将新节点的pNext指针指向第一个节点的首地址
new->pNext = ph->pNext;
//(2)将第一个节点的pPrev指针指向新节点的首地址
if(NULL != ph->pNext)
ph->pNext->pPrev = new;
//(3)将头节点的pNext指针指向新节点的首地址
ph->pNext = new;
//(4)将新节点的pPrev指针指向头节点的首地址
new->pPrev = ph;
//注意在(3)中ph->pNext被改变了,所以要放在(2)的后面,(4)要放在最后
ph->data = cnt + 1;
}
int main(void){
//构建头指针和头节点
struct node *pHeader = create_node(0);
//进行尾部插入: 1, 2, 3
tail_insert(pHeader, create_node(1));
tail_insert(pHeader, create_node(2));
tail_insert(pHeader, create_node(3));
//进行头部插入: 4, 5, 6, 1, 2, 3
head_insert(pHeader, create_node(6));
head_insert(pHeader, create_node(5));
head_insert(pHeader, create_node(4));
printf("The number of node is: %d\n",pHeader->data);
printf("node1 data: %d\n",pHeader->pNext->data);
printf("node2 data: %d\n",pHeader->pNext->pNext->data);
printf("node3 data: %d\n",pHeader->pNext->pNext->pNext->data);
printf("node4 data: %d\n",pHeader->pNext->pNext->pNext->pNext->data);
return 0;
}
3. 双链表之遍历节点
具体方法为:
(1)正向遍历:从头指针+头节点开始,顺着链表挂接指针依次访问链表的各个节点,取出这个节点的数据,然后再往下一个节点,直到最后一个节点,结束返回。
(2)反向遍历:从尾结点开始,顺着链表的前向指针依次访问链表的各个节点,取出这个节点的数据,然后再往前一个节点,直到首节点,结束返回。(这里的先后顺序见代码内)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//构建链表节点,其中包含有效数据和两个指针
struct node{
int data; //有效数据
struct node *pPrev; //前向指针
struct node *pNext; //后向指针
};
//构建链表节点函数(输入参数为节点内数据)
struct node * create_node(int data){
//定义一个节点p
struct node *p = (struct node *)malloc(sizeof(struct node));
//判断内存申请是否成功
if(NULL == p){
printf("malloc error.\n");
return NULL;
}
//如果申请成功了后,给内存清零
memset(p, 0, sizeof(struct node));
//向节点写入数据
p->data = data;
p->pPrev = NULL;
p->pNext = NULL;
return p;
}
//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
int cnt = 0; //节点计数
struct node *p = ph;
while(NULL != p->pNext){
//第一个作用是走的尾部,第二个作用是直接从第一个有效节点开始
p = p->pNext;
cnt++;
}
p->pNext = new; //将最后一个节点的pNext指针指向新添加的节点首地址
new->pPrev = p; //将新节点的pPrev指针指向最后一个节点的首地址
ph->data = cnt + 1;
}
//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
int cnt = 0;
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext;
cnt++;
}
//(1)将新节点的pNext指针指向第一个节点的首地址
new->pNext = ph->pNext;
//(2)将第一个节点的pPrev指针指向新节点的首地址
if(NULL != ph->pNext)
ph->pNext->pPrev = new;
//(3)将头节点的pNext指针指向新节点的首地址
ph->pNext = new;
//(4)将新节点的pPrev指针指向头节点的首地址
new->pPrev = ph;
//注意在(3)中ph->pNext被改变了,所以要放在(2)的后面,(4)要放在最后
ph->data = cnt + 1;
}
//通过pNext指针进行正向链表遍历(输入参数为首节点)
void forward_traversal(struct node *ph){
struct node *p = ph;
printf("开始遍历:\n");
while(NULL != p->pNext){
p = p->pNext;
printf("node data: %d\n", p->data);
}
printf("结束遍历:\n");
}
//通过pPrev指针进行反向链表遍历(输入参数为尾节点)
void backword_traversal(struct node *pt){
struct node *p = pt;
printf("开始遍历:\n");
while(NULL != p->pPrev){
printf("node data: %d\n", p->data);
p = p->pPrev;
}
printf("结束遍历:\n");
}
int main(void){
//构建头指针和头节点
struct node *pHeader = create_node(0);
struct node *pTail = pHeader;
//进行尾部插入: 1, 2, 3
tail_insert(pHeader, create_node(1));
tail_insert(pHeader, create_node(2));
tail_insert(pHeader, create_node(3));
//进行头部插入: 4, 5, 6, 1, 2, 3
head_insert(pHeader, create_node(6));
head_insert(pHeader, create_node(5));
head_insert(pHeader, create_node(4));
//找到尾结点(用于测试反向遍历)
while(NULL != pTail->pNext){
pTail = pTail->pNext;
}
forward_traversal(pHeader);
backword_traversal(pTail);
return 0;
}
4. 双链表之删除节点
(1)待删除的不是尾节点:首先把待删除的节点的前一个节点的pNext指针指向待删除的节点的后一个节点的首地址(这样就把这个节点从链表中摘出来了),然后再将待删除节点的后一个节点的pPrev指针指向待删除节点的前一个节点的首地址,最后将这个摘出来的节点free掉接口。
(2)待删除的是尾节点:首先把待删除的尾节点的前一个节点的pNext指针指向null(这时候就相当于原来尾节点前面的一个节点变成了新的尾节点),然后再将这个摘出来的节点free掉接口。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//构建链表节点,其中包含有效数据和两个指针
struct node{
int data; //有效数据
struct node *pPrev; //前向指针
struct node *pNext; //后向指针
};
//构建链表节点函数(输入参数为节点内数据)
struct node * create_node(int data){
//定义一个节点p
struct node *p = (struct node *)malloc(sizeof(struct node));
//判断内存申请是否成功
if(NULL == p){
printf("malloc error.\n");
return NULL;
}
//如果申请成功了后,给内存清零
memset(p, 0, sizeof(struct node));
//向节点写入数据
p->data = data;
p->pPrev = NULL;
p->pNext = NULL;
return p;
}
//尾部节点插入函数(输入参数为首节点和新节点)
void tail_insert(struct node *ph, struct node *new){
int cnt = 0; //节点计数
struct node *p = ph;
while(NULL != p->pNext){
//第一个作用是走的尾部,第二个作用是直接从第一个有效节点开始
p = p->pNext;
cnt++;
}
p->pNext = new; //将最后一个节点的pNext指针指向新添加的节点首地址
new->pPrev = p; //将新节点的pPrev指针指向最后一个节点的首地址
ph->data = cnt + 1;
}
//头部节点插入函数(输入参数为首节点和新节点)
void head_insert(struct node *ph, struct node *new){
int cnt = 0;
struct node *p = ph;
while(NULL != p->pNext){
p = p->pNext;
cnt++;
}
//(1)将新节点的pNext指针指向第一个节点的首地址
new->pNext = ph->pNext;
//(2)将第一个节点的pPrev指针指向新节点的首地址
if(NULL != ph->pNext)
ph->pNext->pPrev = new;
//(3)将头节点的pNext指针指向新节点的首地址
ph->pNext = new;
//(4)将新节点的pPrev指针指向头节点的首地址
new->pPrev = ph;
//注意在(3)中ph->pNext被改变了,所以要放在(2)的后面,(4)要放在最后
ph->data = cnt + 1;
}
//通过pNext指针进行正向链表遍历(输入参数为首节点)
void forward_traversal(struct node *ph){
struct node *p = ph;
printf("开始遍历:\n");
while(NULL != p->pNext){
p = p->pNext;
printf("node data: %d\n", p->data);
}
printf("结束遍历:\n");
}
//通过pPrev指针进行反向链表遍历(输入参数为尾节点)
void backword_traversal(struct node *pt){
struct node *p = pt;
printf("开始遍历:\n");
while(NULL != p->pPrev){
printf("node data: %d\n", p->data);
p = p->pPrev;
}
printf("结束遍历:\n");
}
//链表删除节点函数(输入参数为首节点,要删除的节点数据)
int delate_node(struct node *ph, int data){
struct node *p = ph; //用来指向当前节点
//除去只有头指针的情况
if(NULL == p){
return -1;
}
while(NULL != p->pNext){
p = p->pNext; //走到下一个节点
//如果检测到待删除数据
if(p->data == data){
//当待删除数据为尾结点时
if(NULL == p->pNext){
//把待删除的尾节点的前一个节点的pNext指针指向null
p->pPrev->pNext = NULL;
}
//当待删除数据不是尾结点时
else{
//待删除的节点的前一个节点的pNext指针指向待删除的节点的后一个节点的首地址
p->pPrev->pNext = p->pNext;
//待删除节点的后一个节点的pPrev指针指向待删除节点的前一个节点的首地址
p->pNext->pPrev = p->pPrev;
}
free(p);
return 0;
}
}
printf("未找到目标节点.\n");
return -1;
}
int main(void){
//构建头指针和头节点
struct node *pHeader = create_node(0);
struct node *pTail = pHeader;
//进行尾部插入: 1, 2, 3
tail_insert(pHeader, create_node(1));
tail_insert(pHeader, create_node(2));
tail_insert(pHeader, create_node(3));
//进行头部插入: 4, 5, 6, 1, 2, 3
head_insert(pHeader, create_node(6));
head_insert(pHeader, create_node(5));
head_insert(pHeader, create_node(4));
forward_traversal(pHeader);
delate_node(pHeader,3);
forward_traversal(pHeader);
return 0;
}