声明:本文是个人学习过程的记录,如有不同意见,欢迎友好讨论
正文如下
双向链表
链表有(2*2*2)个种类,如下
带头和不带头什么意思?如下:
带头指的是有哨兵位,哨兵位即为头节点,哨兵位不存东西,哨兵位的下一个节点才开始存有效数据
不带头指的是链表没有哨兵位,头节点即为存储有效数据的第一个节点
单向双向什么意思?如下:
单向指的是链表只可以从头到尾遍历,不能从尾到头
双向是的是链表既可以从头到尾遍历,又可以从尾到头遍历
循环不循环什么意思?如下:
不循环指的是 尾节点的next指针指向NULL
循环指的是 尾节点的next指针指向第一个节点
所以我上一个博客里面写的单链表,实际上是
不带头 单向 不循环 链表
接下来咱要写的是 带头 双向 循环 链表
简称双向链表 如下图
实际上比较常见的链表就以上两种
双向链表结构看着吓人,其实比单链表简单点,代码也要少点
在单链表中,比如尾插,需要找到尾节点,这时候需要用循环
单链表中有很多需要循环来实现的操作
但是在双向链表中,这种代码是不需要的
正式开始----
首先当然是定义双线链表节点的结构
typedef int DataType;
typedef struct ListNode
{
DataType data; //存储的数据
struct ListNode* next; //指向下一个节点的指针
struct ListNode* prev; //指向上一个节点的指针
}Node;
注意区分:
单链表初始的时候:空链表
双向链表初始的时候:只有个头节点(哨兵位)
申请节点:
和之前单链表的差不多
代码如下:
Node* ApplyNode(DataType x)
{
Node* newnode = (Node*)malloc(sizeof(Node));
assert(newnode);
newnode->data = x;
newnode->next = newnode->prev = newnode; //确保申请到的每一个节点都是可以自循环的
return newnode;
}
双线链表初始化:
与之前写的单链表不同,双向链表需要确保哨兵位的存在,所以要在所有函数运行前,运行该初始化函数
注意:双向链表的哨兵位不存实质性数据,不可以被删除,修改(除非链表销毁了)
代码如下:
void ListInit(Node** pphead)//头节点定义的时候就是一级指针,要对一级指针修改,就要取一级指针的地址,即二级指针
{
*pphead = ApplyNode(-1);
}
插入:
Q: 可以传二级指针进插入类函数吗?
A:行,但是不太安全,哨兵位不可以被修改,传二级指针有导致哨兵位被修改的风险,所以传一级指针就好了
尾插
在最后一个有效数据后边插入,如果只有哨兵位的话就在哨兵位后插入
设插入的数据为newnode
指针指向做出的改变如下
——d3的next指针指向newnode,
——哨兵位的prev指针指向newnode(即“循环”,哨兵位的上一个节点是最后一个节点,即最后插入的newnode)
——newnode的prev指针指向d3
——newnode的next指针指向哨兵位
代码如下:
//尾插
void DLPushBack(Node* phead, DataType x)
{
assert(phead); //phead不可以为空
Node* newnode = ApplyNode(x);
Node* last = phead->prev; //原本的最后一个节点
last->next = newnode;
phead->prev = newnode;
newnode->next = phead;
newnode->prev = last;
}
头插
在第一个有效数据前面插入,即哨兵位后面插入
设插入的数据为newnode
指针指向做出的改变如下
——哨兵位的next指针指向newnode
——newnode的next指向哨兵位的next原本指向的数据(提前保存哨兵位的next原本指向的数据的地址)
——newnode的prev指向哨兵位
——哨兵位的next原本指向的数据的prev指向newnode
代码如下:
//头插
void DLPushFront(Node* phead, DataType x)
{
assert(phead);
Node* newnode = ApplyNode(x);
Node* tem = phead->next; //提前保存哨兵位的next原本指向的数据的地址
phead->next = newnode;
newnode->prev = phead;
newnode->next = tem;
tem->prev = newnode;
}
链表中间位置插入
道理大差不差,想清楚就好写得很 跟指定位置删除思路差不多其实
删除
头删
删除第一个有效数据节点(注意,不可以删哨兵位的)
代码如下:
//头删
void DLDelFront(Node* phead)
{
assert(phead);
Node* del = phead->next;
if (del == NULL||del==phead)
{
printf("%删除失败!\n");
return;
}
phead->next = phead->next->next;
phead->next->next->prev = phead;
free(del);
del = NULL;
}
尾删
删除最后一个有效数据节点(注意,不可以删哨兵位的)
代码如下:
//尾删
void DLDelBack(Node* phead)
{
assert(phead);
Node* del = phead->prev;
if (del == NULL || del == phead)
{
printf("%删除失败!\n");
return;
}
Node* del_prev = phead->prev->prev;
phead->prev = del_prev;
del_prev->next = phead;
free(del);
del = NULL;
}
指定删 ——》比较常用
删除对应数据的有效数据节点(注意,不可以删哨兵位的)
代码如下:
//指定删 -》比较常用
void DLDelSearch(Node* phead, DataType x)
{
assert(phead);
Node* pointer = phead->next;
if (pointer== phead)
{
printf("%双向链表未存储有效数据,删除失败!\n");
return;
}
//pointer指向的数据为x时跳出来
while (pointer->data != x)
{
if (pointer == phead) //遍历完成了都找不到,说明不存在,要防止死循环
{
printf("要删除的数据不存在!\n");
return;
}
pointer = pointer->next;
}
Node* del_next = pointer->next;
Node* del_prev = pointer->prev;
del_prev->next = del_next;
del_next->prev = del_prev;
free(pointer);
pointer = NULL;
}
查找
遍历然后找到对应的就行,跟前面的指定删一个道理
代码如下:
//查找
void DLSearch(Node* phead, DataType x)
{
assert(phead);
Node* pointer = phead->next; //从第一个有效数据节点开始遍历查找
if (pointer == phead)
{
printf("链表中未存储数据!\n");
return;
}
while (pointer->data!=x) //等于x时跳出
{
if (pointer==phead) //说明遍历完了都没找到
{
printf("您要查找的数据不存在!\n");
return;
}
pointer = pointer->next;
}
printf("找到了!\n");
return;
}
更改
查找到想要改的数据,然后改就行了
代码如下:
//更改
void DLChange(Node* phead, DataType x)
{
assert(phead);
Node* pointer = phead->next; //从第一个有效数据节点开始遍历查找
if (pointer == phead)
{
printf("链表中未存储数据!\n");
return;
}
while (pointer->data != x) //等于x时跳出
{
if (pointer == phead) //说明遍历完了都没找到
{
printf("您要查找的数据不存在!\n");
return;
}
pointer = pointer->next;
}
printf("找到了!\n");
DataType n = 0;
printf("请输入更改后的数据: ");
scanf("%d", &n);
pointer->data = n;
}
打印
代码如下:
//双向链表的打印
void DLPrint(Node* phead)
{
Node* pointer = phead->next;
while (pointer != phead)
{
printf("%d ", pointer->data);
pointer = pointer->next;
}
printf("\n");
}
销毁
就遍历,然后一个一个节点free,注意:哨兵位也要free
代码如下:
//销毁
void DLDestroy(Node* phead)
{
Node* pointer = phead->next;
while (pointer != phead)
{
Node* tem = pointer->next;
free(pointer);
pointer = tem;
}
//出来之后,pointer指向phead即哨兵位
free(phead);
phead = NULL;
pointer = NULL;
}
值得注意的一点是: