双向链表
定义
我们一开始学习的链表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表。
虽然使用单向链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,如果算法中需要大量地找某指定节点的前驱节点,使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,而 "从后往前" 找并不是它的强项。
为了能够高效率解决类似的问题,就发明了双向链表。从名字上理解双向链表,即链表是 "双向" 的,如下图所示:
从上图中可以看到,双向链表中各节点包含以下 3 部分信息(如下图所示):
指针域 prior:用于指向当前节点的直接前驱节点;
数据域 data:用于存储数据元素。
指针域 next:用于指向当前节点的直接后继节点;
因此,双链表的节点结构用 C 语言实现为:
typedefstructNode{structNode*prior;//指向直接前驱节点ElemType data;//数据域structNode*next;//指向直接后继节点} Node;复制代码
注意:因为带头节点会更好操作,所以我的代码都有头节点。
1、双向链表的创建
同单链表相比,双链表仅是各节点多了一个用于指向直接前驱的指针域。因此,我们可以在单链表的基础轻松实现对双链表的创建。
//1、初始化双向链表(带头节点)StatusinitLinkList(LinkList *list){//创建头节点*list=malloc(sizeof(Node));if(*list==NULL) {returnERROR; } (*list)->prior =NULL; (*list)->data =-1; (*list)->next =NULL;printf("已初始化链表~\n");returnOK;}复制代码
2、遍历双向链表
和单向链表遍历方式一模模一样样,这里就不多讲。我多加了一层使用prior指针逆序输出,相信有点基础的同学应该能一眼看明白。
//2、遍历双向链表voidprintfLinkLisk(LinkListlist){printf("遍历链表:\n");if(list==NULL||list->next ==NULL) {printf("这是一个空链表\n");return; } LinkList p =list;//判断next是否全部正确printf("根据next从前往后遍历:");while(p->next) {printf("%d ",p->next->data); p = p->next; }printf("\n");//判断prior是否全部正确printf("根据prior从后往前遍历:");while(p !=list) {printf("%d ",p->data); p = p->prior; }printf("\n");}复制代码
3、根据索引位置添加节点
因为我的双向链表有头节点,所以只有两种添加情况:
添加至表的中间位置
同单链表添加数据类似,双向链表中间位置添加数据需要经过以下 4 个步骤(步骤中的顺序中 3 必须放到 1 和 2 后面,其它顺序可变),如下图所示:
将priorNode->next节点的prior指向新节点;
将新节点->next指向原来的priorNode->next;
将priorNode->next指向新节点;
新节点的prior指向priorNode。
添加至表尾
与添加到表中间的步骤只需要少掉步骤 1。因为priorNode->next是Null,不能用它执行操作,否则会崩溃。
//3、根据索引位置插入数据至链表中StatusinsertLinkList(LinkList *list,intindex, ElemType data){if(list==NULL|| index <0) {returnERROR; }inti =0; LinkList priorNode = *list;//判断插入的位置,这里开始位置是0,index超过链表长度则插入末尾while(i < index && priorNode->next !=NULL) { priorNode = priorNode->next; i++; } LinkList newNode =malloc(sizeof(Node));if(newNode ==NULL) {returnERROR; } newNode->data = data;//插入操作共四步,看好了,别眨眼//1.将priorNode->next节点的前驱指向新节点if(priorNode->next) { priorNode->next->prior = newNode; }//2.将新节点->next指向原来的priorNode->nextnewNode->next = priorNode->next;//3.将priorNode->next指向新节点priorNode->next = newNode;//4.新节点的前驱指向priorNodenewNode->prior = priorNode;returnOK;}复制代码
4、根据索引位置删除节点
根据索引删除节点时,只需遍历链表找到要删除的结点,更改前驱节点的next和后继节点的prior即可。
need-to-insert-img
//4、根据索引位置删除节点StatusdeleteLinkListByIndex(LinkList *list,intindex, ElemType *data){if(*list==NULL|| index <0) {returnERROR; } LinkList locaNode = *list;inti =0;while(i <= index) { locaNode = locaNode->next;if(locaNode ==NULL) {printf("没有这个你想要删除的节点\n");returnERROR; } i++; }//开始删除,只需要做两步//1、更改前驱节点的nextlocaNode->prior->next = locaNode->next;//2、更改后继节点的prior。if(locaNode->next) { locaNode->next->prior = locaNode->prior; } *data = locaNode->data;free(locaNode);returnOK;}复制代码
5、根据存储的值删除节点
根据值删除节点时,只需遍历链表找到要删除的结点,更改前驱节点的next和后继节点的prior即可。
//5、根据存储的值删除节点StatusdeleteLinkListByData(LinkList *list, ElemType data){if(*list==NULL) {returnERROR; } LinkList locaNode = (*list)->next;while(locaNode) {if(locaNode->data == data) {break; } locaNode = locaNode->next; }if(locaNode ==NULL) {printf("没有这个你想要删除的节点\n");returnERROR; }//开始删除,只需要做两步locaNode->prior->next = locaNode->next;if(locaNode->next) { locaNode->next->prior = locaNode->prior; }free(locaNode);returnOK;}复制代码
6、根据值查找节点
方法同单向链表
//6、查找元素StatusselectNode(LinkListlist, ElemType data, LinkList *locaNode){if(list==NULL) {returnERROR; } LinkList p =list->next;while(p) {if(p->data == data) { *locaNode = p;break; } p = p->next; }if(*locaNode ==NULL) {printf("没有这个你想要的节点\n");returnERROR; }else{returnOK; }}复制代码
其它辅助代码
#include"stdlib.h"#defineOK 1#defineERROR 0//元素类型typedefintElemType;//状态类型typedefintStatus;//定义节点结构体typedefstructNode{structNode*prior;ElemType data;structNode*next;} Node;typedefNode *LinkList;intmain(intargc,constchar* argv[]){ LinkListlist; initLinkList(&list);for(inti =0; i <10; i ++) { insertLinkList(&list, i, i); } printfLinkLisk(list);intindex, data;printf("输入你想插入的位置(从0开始)和存储的值:");scanf("%d %d",&index,&data); insertLinkList(&list, index, data); printfLinkLisk(list);printf("输入你想删除的位置(从0开始):");scanf("%d",&index); deleteLinkListByIndex(&list, index, &data); printfLinkLisk(list);printf("输入你想删除的节点的值(只删最前的那个):");scanf("%d",&data); deleteLinkListByData(&list, data); printfLinkLisk(list);printf("\n");return0;}复制代码
输出结果:
双向循环链表
定义
双向循环链表和它名字的表意一样,就是把双向链表的两头连接,使其成为了一个环状链表。只需要将表中最后一个节点的next指针指向头节点,头节点的prior指针指向尾节点,链表就能成环儿,如图所示:
需要注意的是,虽然双向循环链表成环状,但本质上还是双向链表,因此在双向循环链表中,依然能够找到头指针和头节点等。双向循环链表和双向链表相比,唯一的不同就是双向循环链表首尾相连,其他都完全一样。
注意:因为我上面已经讲了双向链表,所以这里只注重讲他们的实现差异。另因为带头节点会更好操作,所以我的代码都有头节点。
1、双向循环链表的创建
初始化时需要将头节点的next和prior都指向自己。
//1、初始化双向循环链表(带头节点)StatusinitLinkList(LinkList *list){//创建头节点*list=malloc(sizeof(Node));if(*list==NULL) {returnERROR; }//前驱和后继都指向自己(*list)->prior = *list; (*list)->data =-1; (*list)->next = *list;printf("已初始化链表~\n");returnOK;}复制代码
2、遍历双向循环链表
注意它的尾节点的next不再是Null,而是头节点
//2、遍历双向循环链表voidprintfLinkLisk(LinkListlist){printf("遍历链表:\n");if(list==NULL||list->next ==list) {printf("这是一个空链表\n");return; } LinkList p =list;//判断next是否全部正确printf("根据next从前往后遍历:");while(p->next !=list) {printf("%d ",p->next->data); p = p->next; }printf("\n");//判断prior是否全部正确printf("根据prior从后往前遍历:");while(p !=list) {printf("%d ",p->data); p = p->prior; }printf("\n");}复制代码
3、根据索引位置添加节点
这里不需要判断尾节点的next是否为Null,因为它会指向头节点。
//3、根据索引位置插入数据至链表中StatusinsertLinkList(LinkList *list,intindex, ElemType data){if(list==NULL|| index <0) {returnERROR; }inti =0; LinkList priorNode = *list;//判断插入的位置,这里开始位置是0,index超过链表长度则插入末尾while(i < index && priorNode->next != *list) { priorNode = priorNode->next; i++; } LinkList newNode =malloc(sizeof(Node));if(newNode ==NULL) {returnERROR; } newNode->data = data;//插入操作共四步,看好了,别眨眼//1.将priorNode->next节点的前驱指向新节点priorNode->next->prior = newNode;//2.将新节点->next指向原来的priorNode->nextnewNode->next = priorNode->next;//3.将priorNode->next指向新节点priorNode->next = newNode;//4.新节点的前驱指向priorNodenewNode->prior = priorNode;returnOK;}复制代码
4、根据索引位置删除节点
这里不需要判断尾节点的next是否为Null,因为它会指向头节点。
//4、根据索引位置删除节点StatusdeleteLinkListByIndex(LinkList *list,intindex, ElemType *data){if(*list==NULL|| index <0) {returnERROR; } LinkList locaNode = *list;inti =0;//注意别删了头节点while(i <= index) { locaNode = locaNode->next;if(locaNode == *list) {printf("没有这个你想要删除的节点\n");returnERROR; } i++; }//开始删除,只需要做两步locaNode->prior->next = locaNode->next; locaNode->next->prior = locaNode->prior; *data = locaNode->data;free(locaNode);returnOK;}复制代码
5、根据存储的值删除节点
这里不需要判断尾节点的next是否为Null,因为它会指向头节点。
//5、根据存储的值删除节点StatusdeleteLinkListByData(LinkList *list, ElemType data){if(*list==NULL) {returnERROR; } LinkList locaNode = (*list)->next;while(locaNode != *list) {if(locaNode->data == data) {break; } locaNode = locaNode->next; }if(locaNode == *list) {printf("没有这个你想要删除的节点\n");returnERROR; }//开始删除,只需要做两步locaNode->prior->next = locaNode->next; locaNode->next->prior = locaNode->prior;free(locaNode);returnOK;}复制代码
6、根据值查找节点
尾节点的next可是头节点哦,找到它就是最后一个了。
//6、查找元素StatusselectNode(LinkListlist, ElemType data, LinkList *locaNode){if(list==NULL) {returnERROR; } LinkList p =list->next;while(p !=list) {if(p->data == data) { *locaNode = p;break; } p = p->next; }if(*locaNode ==NULL) {printf("没有这个你想要的节点\n");returnERROR; }else{returnOK; }}复制代码
其它辅助代码
#include"stdlib.h"#defineOK 1#defineERROR 0//元素类型typedefintElemType;//状态类型typedefintStatus;//定义节点结构体typedefstructNode{structNode*prior;ElemType data;structNode*next;} Node;typedefNode *LinkList;intmain(intargc,constchar* argv[]){ LinkListlist; initLinkList(&list);for(inti =0; i <10; i ++) { insertLinkList(&list, i, i); } printfLinkLisk(list);intindex, data;printf("输入你想插入的位置(从0开始)和存储的值:");scanf("%d %d",&index,&data); insertLinkList(&list, index, data); printfLinkLisk(list);printf("输入你想删除的位置(从0开始):");scanf("%d",&index); deleteLinkListByIndex(&list, index, &data); printfLinkLisk(list);printf("输入你想删除的节点的值(只删最前的那个):");scanf("%d",&data); deleteLinkListByData(&list, data); printfLinkLisk(list);printf("\n");return0;}复制代码
输出结果
参考链接:https://juejin.im/post/5e885200f265da47bc5902c4