双向链表大型攻略
我们之前已经做了链表的大型攻略。包括如何创建一个链表,插入节点,打印数据,删除节点等常用的功能。我们通常将这种每个节点有一个数据段和一个指向下个节点指针的链表为单向链表。我想通过某一个节点能找到下一个节点,但是如果我想通过该节点找到上一个节点呢?这几乎是不可能的。除非我将单向链表首尾相连构成环形的单向循环链表,走一个循环之后就可以找到其上一个节点的数据了,但是这有过于麻烦,为了解决这种问题,我们引入了双向链表,也就是说在一个节点上不仅有指向下一个节点的指针,还有另外一个指针指向上一个节点。
没错,每一个节点就长这个样子,有两个指针,当然了我们给他们不容的名字,并将他们分别指向上一个和下一个节点:
当然了也需要有一个头指针和头节点
其实双向链表和单向链表的区别仅仅就是每个节点多了一个指向上个节点的指针,所以我们在操作时要多做一个指向的步骤。
我们首先创建一个节点,要记得是2个指针
typedef struct Node { int data; struct Node *prev; struct Node *next; }node;
用刚才的结构体写一个函数,新建节点,和之前的单向链表别无二致
/************************* *函数功能:新建节点 *输入参数:节点所存数据 *返 回 值:节点指针 ***************************/ node *BuildNodePtr(int data) { node *p_tmp = NULL; p_tmp = (node *)malloc(sizeof(node)); if (p_tmp == NULL) { printf("ERROR:创建节点指针失败\n"); exit(0); } memset(p_tmp,0,sizeof(node)); p_tmp->data = data; p_tmp->prev = NULL; p_tmp->next = NULL; return p_tmp; }
这次我们定义一个结构体,用来存放我们的双向链表
成员分别是头节点 尾节点 和链表内所有元素数量(链表长度)
typedef struct Link { node *head; node *tail; int linksize; }link;
注意这里 在新建链表的同时,也建了一个头节点,就是函数中的head
/************************* *函数功能:新建双向链表 *输入参数: *返 回 值:链表的指针 ***************************/ link *CreatLink() { link *p_tmp = (link *)malloc(sizeof(link)); node *head = BuildNodePtr(0); if (p_tmp != 0 && head != 0) { p_tmp->head = head; p_tmp->tail = head; p_tmp->linksize = 0; } else { printf("ERROR:创建双向链表失败\n"); exit(0); } return p_tmp; }
那么接下来我们看一下在链表尾部加一个新节点的图示:
其实和单向链表也是一样的,我们这里链表尾部的那个节点就是p_link->tail
本来p_link->tail是指向NULL,现在新增了一个节点,则其指向新节点
同时新节点p_node接上原尾节点成为新尾节点
/************************* *函数功能:在链表末尾插入节点 *输入参数: p_link 链表指针 p_node (要插入的)节点指针 *返 回 值:新插入的节点 *备 注: ***************************/ node *InsertNodeTail(link *p_link,node *p_node) { node *p_tmp = p_link->tail; if (p_tmp->next != NULL) { printf("ERROR:InsertNodeTail链表尾节点错误 插入节点失败\n"); } p_node->next = NULL; p_node->prev = p_tmp; p_tmp->next = p_node; p_link->tail = p_node; return p_link; }
为了测试我们写的这个插入函数能否使用,那还需要写一个打印函数,和上个博客的单向链表一样,直接复制过来(被我改了函数和参数名字)
/***************************************** *函数作用:打印整个链表 *输入参数:链表的指针 *返 回 值: *备 注: *****************************************/ void DisaplayList(link *p_link) { node *p_tmp = p_link->head; int i = 1; while (p_tmp != NULL) { printf("第%d个节点的值为:%d\n",i,p_tmp->data); p_tmp = p_tmp->next; i += 1; } }
经过我的测试,代码没有问题。
接下来思考下如何在链表中间插入一个节点
红色箭头为加入之前的指针,紫色箭头为加入新节点之后的。
/************************* *函数功能:在链表中插入节点 *输入参数: p_link 链表指针 p_node (要插入的)节点指针 n 插入位置(>1) *返 回 值: *备 注:插入位置为1时 插在头节点后的第一个 ***************************/ node *InserNodeNMiddle(link *p_link,node *p_node,int n) { int i = 0; node *p_tmp = p_link->head,*p_tmp2; if((n) == p_link->linksize) { //参数若等于目前链表长度,则在末尾加入节点 InsertNodeTail(p_link,p_node); return p_node; } if(n < 1 || n > p_link->linksize) { //参数不符合长度要求 printf("ERROR:InserNodeNMiddle插入位置错误\n"); return p_node; } //利用next指针向后寻找指定位置 while(i < n) { p_tmp = p_tmp->next; i++; } p_tmp2 = p_tmp->next; p_node->next = p_tmp2; p_tmp2->prev = p_node; p_tmp->next = p_node; p_node->prev = p_tmp; p_link->linksize += 1; return p_node; }
笔者写到这里时,调试遇到了一些问题,插入功能是可以的,但是打印出了差错,于是我对打印函数的判断稍作改动,将链表结构体的长度拿来作为参考
/***************************************** *函数作用:打印整个链表 *输入参数:链表的指针 *返 回 值: *备 注: *****************************************/ void DisaplayList(link *p_link) { node *p_tmp = p_link->head; int i = 0; printf("链表共:%d组数据\n",p_link->linksize); while (i < p_link->linksize) { printf("第%d个节点的值为:%d\n",i,p_tmp->data); p_tmp = p_tmp->next; i++; } printf("-----------------------------\n"); }
这样就能顺利运行并打印了
这样我们参考插入代码顺势写出双向链表的删除功能。
/***************************************** *函数作用:链表数据删除 *输入参数:链表的指针 删除第几个数据 *返 回 值: *备 注:参数为1时删除头节点后的第一个元素 *****************************************/ void *DeletNode(link *p_link,int n) { int i = 1; node *p_tmp = p_link->head,*p_tmp_free; if(n < 1 || n >= p_link->linksize) { //参数不符合长度要求 printf("ERROR:DeletNode删除位置错误\n"); return; } if((n+1) == p_link->linksize) { //参数若等于目前链表长度,则删除尾节点 printf("删除尾节点\n"); p_tmp = p_link->tail->prev; p_tmp->next = NULL; free(p_link->tail); p_link->linksize --; return; } //利用next指针向后寻找指定位置 while(i < n) { p_tmp = p_tmp->next; i++; } p_tmp_free = p_tmp->next; p_tmp_free->next->prev = p_tmp; p_tmp->next = p_tmp_free->next; free(p_tmp_free); p_link->linksize -= 1; return; }
修改和查询就不单独列出来了
总代码如下:
#include <string.h> #include <stdlib.h> #include <stdio.h> //节点 typedef struct Node { int data; struct Node *prev; struct Node *next; }node; //双向链表 分别指向链表头和尾的节点 typedef struct Link { node *head; node *tail; int linksize; }link; /***********函数声明***********/ node *BuildNodePtr(int data); link *CreatLink(); node *InsertNodeTail(link *p_link,node *p_node); void DisaplayList(link *p_link); node *InserNodeNMiddle(link *p_link,node *p_node,int n); void *DeletNode(link *p_link,int n); int FindLinkData(link *p_link,int n); void AmendLinkData(link *p_link,int n,int data); node *CheckLinkData(link *p_link,int data); void FreeLink(link *p_link); /*****************************/ /************************* *函数功能:新建节点 *输入参数:节点所存数据 *返 回 值:节点指针 ***************************/ node *BuildNodePtr(int data) { node *p_tmp = NULL; p_tmp = (node *)malloc(sizeof(node)); if (p_tmp == NULL) { printf("ERROR:BuildNodePtr创建节点指针失败\n"); exit(0); } memset(p_tmp,0,sizeof(node)); p_tmp->data = data; p_tmp->prev = NULL; p_tmp->next = NULL; return p_tmp; } /************************* *函数功能:新建双向链表 *输入参数: *返 回 值:链表的指针 ***************************/ link *CreatLink() { link *p_tmp = (link *)malloc(sizeof(link)); node *head = BuildNodePtr(0); if (p_tmp != 0 && head != 0) { p_tmp->head = head; p_tmp->tail = head; p_tmp->linksize = 1; } else { printf("ERROR:CreatLink创建双向链表失败\n"); exit(0); } return p_tmp; } /************************* *函数功能:在链表中插入节点 *输入参数: p_link 链表指针 p_node (要插入的)节点指针 n 插入位置(>1) *返 回 值: *备 注:插入位置为1时 插在头节点后的第一个 ***************************/ node *InserNodeNMiddle(link *p_link,node *p_node,int n) { int i = 1; node *p_tmp = p_link->head,*p_tmp2; if((n) == p_link->linksize) { //参数若等于目前链表长度,则在末尾加入节点 InsertNodeTail(p_link,p_node); return p_node; } if(n < 1 || n > p_link->linksize) { //参数不符合长度要求 printf("ERROR:InserNodeNMiddle插入位置错误\n"); return p_node; } //利用next指针向后寻找指定位置 while(i < n) { p_tmp = p_tmp->next; i++; } p_tmp2 = p_tmp->next; p_node->next = p_tmp2; p_tmp2->prev = p_node; p_tmp->next = p_node; p_node->prev = p_tmp; p_link->linksize += 1; return p_node; } /************************* *函数功能:在链表末尾插入节点 *输入参数: p_link 链表指针 p_node (要插入的)节点指针 *返 回 值:新插入的节点 *备 注: ***************************/ node *InsertNodeTail(link *p_link,node *p_node) { node *p_tmp = p_link->tail; if (p_tmp->next != NULL) { printf("ERROR:InsertNodeTail链表尾节点错误 插入节点失败\n"); return p_node; } p_node->next = NULL; p_node->prev = p_tmp; p_tmp->next = p_node; p_link->tail = p_node; p_link->linksize += 1; return p_node; } /***************************************** *函数作用:打印整个链表 *输入参数:链表的指针 *返 回 值: *备 注: *****************************************/ void DisaplayList(link *p_link) { node *p_tmp = p_link->head; int i = 0; printf("链表共:%d组数据\n",p_link->linksize); while (i < p_link->linksize) { printf("第%d个节点的值为:%d\n",i,p_tmp->data); p_tmp = p_tmp->next; i++; } printf("-----------------------------\n"); } /***************************************** *函数作用:链表数据删除 *输入参数:链表的指针 删除第几个数据 *返 回 值: *备 注:参数为1时删除头节点后的第一个元素 *****************************************/ void *DeletNode(link *p_link,int n) { int i = 1; node *p_tmp = p_link->head,*p_tmp_free; if(n < 1 || n >= p_link->linksize) { //参数不符合长度要求 printf("ERROR:DeletNode删除位置错误\n"); return; } if((n+1) == p_link->linksize) { //参数若等于目前链表长度,则删除尾节点 printf("删除尾节点\n"); p_tmp = p_link->tail->prev; p_tmp->next = NULL; free(p_link->tail); p_link->linksize --; return; } //利用next指针向后寻找指定位置 while(i < n) { p_tmp = p_tmp->next; i++; } p_tmp_free = p_tmp->next; p_tmp_free->next->prev = p_tmp; p_tmp->next = p_tmp_free->next; free(p_tmp_free); p_link->linksize -= 1; return; } /***************************************** *函数作用:链表数据查询 *输入参数:链表的指针 查询第几个数据 *返 回 值:查询到的数据 *备 注:参数为1时查询头节点后的第一个元素 *****************************************/ int FindLinkData(link *p_link,int n) { int i = 0; node *p_tmp = p_link->head; if(n < 1 || n >= p_link->linksize) { //参数不符合长度要求 printf("ERROR:FindLinkData查询位置错误\n"); return; } //利用next指针向后寻找指定位置 while(i < n) { p_tmp = p_tmp->next; i++; } printf("要查询的第%d个数据为%d\n",n,p_tmp->data); return p_tmp->data; } /***************************************** *函数作用:链表数据修改 *输入参数:链表的指针 修改第几个数据 修改后的数据 *返 回 值: *备 注:参数为1时查询头节点后的第一个元素 *****************************************/ void AmendLinkData(link *p_link,int n,int data) { int i = 0; node *p_tmp = p_link->head; if(n < 1 || n >= p_link->linksize) { //参数不符合长度要求 printf("ERROR:AmendLinkData修改位置错误\n"); return; } //利用next指针向后寻找指定位置 while(i < n) { p_tmp = p_tmp->next; i++; } p_tmp->data = data; return; } /***************************************** *函数作用:已知数据求在链表中的位置 *输入参数:链表的指针 已知的数据 *返 回 值:该节点地址 *备 注: *****************************************/ node *CheckLinkData(link *p_link,int data) { int i = 0; node *p_tmp = p_link->head; for(i = 0;i <= p_link->linksize;i++) { if(data == p_tmp->data) { printf("%要查的数据在链表的第%d个\n",i); return p_tmp; } if (p_tmp->next == NULL) { printf("%要查的数据不在该链表中\n"); return; } p_tmp = p_tmp->next; } } /***************************************** *函数作用:释放链表内存 *输入参数:链表的指针 *返 回 值: *备 注: *****************************************/ void FreeLink(link *p_link) { node *p_tmp = p_link->head,*p_free = NULL; while(p_tmp != NULL) { p_free = p_tmp; p_tmp = p_tmp->next; free(p_free); } } int main(int argc, char const *argv[]) { link *p_link = NULL; node *p_node = NULL; p_link = CreatLink(); InsertNodeTail(p_link,BuildNodePtr(1)); DisaplayList(p_link); InsertNodeTail(p_link,BuildNodePtr(11)); DisaplayList(p_link); InsertNodeTail(p_link,BuildNodePtr(111)); DisaplayList(p_link); InsertNodeTail(p_link,BuildNodePtr(1111)); DisaplayList(p_link); InsertNodeTail(p_link,BuildNodePtr(11111)); DisaplayList(p_link); /********插入*********/ InserNodeNMiddle(p_link,BuildNodePtr(8),5); DisaplayList(p_link); InserNodeNMiddle(p_link,BuildNodePtr(88),4); DisaplayList(p_link); /********删除*********/ DeletNode(p_link,1); DisaplayList(p_link); DeletNode(p_link,5); DisaplayList(p_link); DeletNode(p_link,7); DisaplayList(p_link); /********查询*********/ FindLinkData(p_link,1); FindLinkData(p_link,5); /********修改*********/ AmendLinkData(p_link,5,20); DisaplayList(p_link); CheckLinkData(p_link,20); FreeLink(p_link); return 0; }