目录
一、前情提要
上一篇讲到了静态链表,它的本质和单链表相同,换了一种实现的形式罢了。但单链表存在一个问题,就是只能从头走到尾,一条路走到黑。那如果我要返回上一个结点呢?没办法,单链表无法完成。这样就衍生出了双向链表。本篇围绕双向链表给出代码和分析。
二、双向链表的功能和实现
(一)双向链表的初始化
双向链表和单向链表的不同就在于,它可以指向前面,也可以指向后面。所以自然想到在指针域中多添加一个Pre指针(指向该结点的前驱),配合上Next指针,就可以达到效果。
直接看代码吧。
typedef struct NodeList
{
int Data;
struct NodeList* Pre,Next;
}Node,*LinkList;
LinkList InitList() //初始化链表,返回头指针
{
LinkList Ptr=(int*)malloc(sizeof(int)); //申请一块内存
if(Ptr==NULL)
{
printf("申请内存失败");
}
else
{
Ptr->Next=NULL; //头结点前驱为NULL。
Ptr->Pre=NULL; //头结点后继为NULL。
}
return Ptr; //返回头指针
}
不论什么链表我都习惯写一个头结点,原因在单链表那详细说明了。
(二)双向链表的插入:头插、尾插、中间插
注意: 双向链表的插入和单链表有所差别,主要体现在有个Pre指针,所以插入时,修改指针的顺序尤为重要!!
下面为了展示的更加清楚,我画图来展示。
修改指针的正确顺序有很多,会一种就行了。
先开始的图:
第一步:新结点的Next指向结点2,Pre指针指向结点1
第二步:结点2的Pre修改成指向结点1。
第三步:结点1的Next修改成指向新结点。
注意:
这三步看似很简单,很容易犯错,第二步和第三步不能颠倒!!!
如果先进行第三步,那么就变成了
这样的话,我们就不能通过结点1找到结点2了。
因为之前我们去找结点2是利用结点1的Next,现在结点1的Next都修改了,所以就找不到了结点2了。找都找不到,更不可能改变不结点2的Pre了。
注意:头插要注意,要特判一下。如果是空表,那么Ptr->Next==NULL,NULL不是结点,没有Pre和Next指针。
搞清楚指针改变顺序了,插入就简单了。看代码吧。
//头插
void Head_Insert(LinkList Ptr,int Data)//传入头指针,头插
{
Node* New_Node=(Node*)malloc(sizeof(Node));//先开辟一块内存
if(New_Node==NULL)
{
printf("申请内存失败");
return;
}
New_Node->Data=Data;
New_Node->Next=Ptr->Next;//新结点的Next和Pre分别指向对应结点
New_Node->Pre=Ptr;
if(Ptr->Next!=NULL)
{
Ptr->Next->Pre=New_Node;//结点2的Pre指针指向新结点
} //这里需要特判,如果为空表,那么Ptr->Next==NULL,NULL不是结点,没有Pre和Next指针。
Ptr->Next=New_Node;//结点1的Next指针指向新结点
}
//尾插
void Tail_Insert(LinkList Ptr,int Data)//传入头指针
{
Node* New_Node=(Node*)malloc(sizeof(Node));
if(New_Node==NULL)
{
printf("申请内存失败");
return;
}
for(Node* i=Ptr;i!=NULL;i=i->Next)
{
if(i->Next==NULL)
{
New_Node->Data=Data;
New_Node->Next=NULL;
New_Node->Pre=i;
//i->Next->Pre=New_Node这一步会出现问题,因为i结点Next指向NULL,NULL没有Pre
//所以可以省略这一步
i->Next=New_Node;
return;
}
}
}
//查找
Node* Find(LinkList Ptr,int Data)
{
for(Node* i=Ptr;i!=NULL;i=i->Next)
{
if(i->Data==Data)
{
return i;
}
}
printf("未查询到");
return NULL;
}
//在值为k的结点后面插入新结点
void Val_Insert(LinkList Ptr,int k,int Data)
{
Node* New_Node=(Node*)malloc(sizeof(Node)); //开辟新的结点
Node* Tar_Node=Find(Ptr,k); //找到值为k的目标结点
New_Node->Data=Data;
//下面按那三步走就行了
New_Node->Next=Tar_Node->Next;//处理好新结点的Next和Pre指针。
New_Node->Pre=Tar_Node;
Tar_Node->Next->Pre=New_Node;//目标结点下一个结点的Pre指针修改
Tar_Node->Next=New_Node;//目标结点的Next指针修改。
}
(三)双向链表删除和清空操作
删除某个结点操作还是比较简单的。
画个图说明一下。
我们已知结点2,要用结点2来访问结点1和结点3,结点1==结点2->Pre,结点3==结点2->Next。
这是先开始的图。我们要删除结点2。
第一步:将结点1的Next改成结点3。但是结点1和结点3我们都是要用结点2来表示的,写起来可能有点绕。
第一步逻辑是:结点2->Pre->Next=结点2->Next;
然后图就变成了
第二步:将结点3的Pre改成结点1。
第二步的逻辑:结点2->Next->Pre=结点2->Pre;
然后图变成了
第三步:最后一步直接Free结点2就好。
注意:
(1)第一步和第二步可以混着来,没有先后顺序!!!
原因就是结点1,3都是由结点2来访问的,不用担心修改了其中一个结点的指针然后找不到了的问题。
(2)最后一个结点的删除要特殊处理!
因为最后一个结点的Next指向的是NULL,NULL它不是一个结点,不需要它指向其他地方,况且它还没有Next和Pre指针。。
//删除值为x的结点
void Delete(LinkList Ptr,int Data)
{
Node* Tar_Node=Find(Ptr,Data);//先找到值为x的结点
if(Tar_Node==NULL)
{
printf("未查询到此结点");
return;
}
else
{
if(Tar_Node->Next!=NULL)//判断是否是最后一个结点
{
Tar_Node->Next->Pre=Tar_Node->Pre;//结点2->Next->Pre=结点2->Pre;
Tar_Node->Pre->Next=Tar_Node->Next;//结点2->Pre->Next=结点2->Next;
free(Tar_Node);
}
else
{
Tar_Node->Pre->Next=NULL;//如果是最后一个结点,那么只需要将上一个结点指向NULL就行了。
free(Tar_Node); //如果按照之前的写会错,因为NULL没有Pre和Next指针。
}
}
}
//清空链表
void MK_Empty(LinkList Ptr)
{
for(Node* Tar_Node=Ptr->Next;Tar_Node!=NULL;Tar_Node=Tar_Node->Next) //从头结点后面一个结点开始删除。
{
if(Tar_Node->Next!=NULL)//特判是否是最后一个结点
{
Tar_Node->Next->Pre=Tar_Node->Pre;//结点2->Next->Pre=结点2->Pre;
Tar_Node->Pre->Next=Tar_Node->Next;//结点2->Pre->Next=结点2->Next;
free(Tar_Node);
}
else
{
Tar_Node->Pre->Next=NULL; //如果是最后一个结点,那么只需要将上一个结点指向NULL就行了。
free(Tar_Node); //如果按照之前的写会错,因为NULL没有Pre和Next指针。
}
}
}
至此,功能基本就讲完了。最后把代码合并一下。
三、代码实现
#include <stdio.h>
#include <stdlib.h>
typedef struct NodeList
{
int Data;
struct NodeList* Pre;
struct NodeList* Next;
}Node,*LinkList;
LinkList InitList() //初始化链表,返回头指针
{
LinkList Ptr=(Node*)malloc(sizeof(Node)); //申请一块内存
if(Ptr==NULL)
{
printf("申请内存失败");
}
else
{
Ptr->Next=NULL; //头结点前驱为NULL。
Ptr->Pre=NULL; //头结点后继为NULL。
}
return Ptr; //返回头指针
}
//头插
void Head_Insert(LinkList Ptr,int Data)//传入头指针,头插
{
Node* New_Node=(Node*)malloc(sizeof(Node));//先开辟一块内存
if(New_Node==NULL)
{
printf("申请内存失败");
return;
}
New_Node->Data=Data;
New_Node->Next=Ptr->Next;//新结点的Next和Pre分别指向对应结点
New_Node->Pre=Ptr;
if(Ptr->Next!=NULL)
{
Ptr->Next->Pre=New_Node;//结点2的Pre指针指向新结点
} //这里需要特判,如果为空表,那么Ptr->Next==NULL,NULL不是结点,没有Pre和Next指针。
Ptr->Next=New_Node;//结点1的Next指针指向新结点
}
//尾插
void Tail_Insert(LinkList Ptr,int Data)//传入头指针
{
Node* New_Node=(Node*)malloc(sizeof(Node));
if(New_Node==NULL)
{
printf("申请内存失败");
return;
}
for(Node* i=Ptr;i!=NULL;i=i->Next)
{
if(i->Next==NULL)
{
New_Node->Data=Data;
New_Node->Next=NULL;
New_Node->Pre=i;
//i->Next->Pre=New_Node这一步会出现问题,因为i结点Next指向NULL,NULL没有Pre
//所以可以省略这一步
i->Next=New_Node;
return;
}
}
}
//查找
Node* Find(LinkList Ptr,int Data)
{
for(Node* i=Ptr;i!=NULL;i=i->Next)
{
if(i->Data==Data)
{
return i;
}
}
printf("未查询到");
return NULL;
}
//在值为k的结点后面插入新结点
void Val_Insert(LinkList Ptr,int k,int Data)
{
Node* New_Node=(Node*)malloc(sizeof(Node)); //开辟新的结点
Node* Tar_Node=Find(Ptr,k); //找到值为k的目标结点
New_Node->Data=Data;
//下面按那三步走就行了
New_Node->Next=Tar_Node->Next;//处理好新结点的Next和Pre指针。
New_Node->Pre=Tar_Node;
Tar_Node->Next->Pre=New_Node;//目标结点下一个结点的Pre指针修改
Tar_Node->Next=New_Node;//目标结点的Next指针修改。
}
//删除值为x的结点
void Delete(LinkList Ptr,int Data)
{
Node* Tar_Node=Find(Ptr,Data);//先找到值为x的结点
if(Tar_Node==NULL)
{
printf("未查询到此结点");
return;
}
else
{
if(Tar_Node->Next!=NULL)//判断是否是最后一个结点
{
Tar_Node->Next->Pre=Tar_Node->Pre;//结点2->Next->Pre=结点2->Pre;
Tar_Node->Pre->Next=Tar_Node->Next;//结点2->Pre->Next=结点2->Next;
free(Tar_Node);
}
else
{
Tar_Node->Pre->Next=NULL;//如果是最后一个结点,那么只需要将上一个结点指向NULL就行了。
free(Tar_Node); //如果按照之前的写会错,因为NULL没有Pre和Next指针。
}
}
}
//清空链表
void MK_Empty(LinkList Ptr)
{
for(Node* Tar_Node=Ptr->Next;Tar_Node!=NULL;Tar_Node=Tar_Node->Next) //从头结点后面一个结点开始删除。
{
if(Tar_Node->Next!=NULL)//特判是否是最后一个结点
{
Tar_Node->Next->Pre=Tar_Node->Pre;//结点2->Next->Pre=结点2->Pre;
Tar_Node->Pre->Next=Tar_Node->Next;//结点2->Pre->Next=结点2->Next;
free(Tar_Node);
}
else
{
Tar_Node->Pre->Next=NULL; //如果是最后一个结点,那么只需要将上一个结点指向NULL就行了。
free(Tar_Node); //如果按照之前的写会错,因为NULL没有Pre和Next指针。
}
}
}
//链表输出
void PrintList(LinkList Ptr)
{
if(Ptr->Next==NULL)
{
printf("此表为空表");
return;
}
for(Node* i=Ptr->Next;i!=NULL;i=i->Next)//遍历输出
{
printf("%d ",i->Data);
}
printf("\n");
}
int main()
{
LinkList Ptr=InitList();
Head_Insert(Ptr,1);
Head_Insert(Ptr,2);
Head_Insert(Ptr,3);
Head_Insert(Ptr,4);
PrintList(Ptr);
Tail_Insert(Ptr,5);
PrintList(Ptr);
Val_Insert(Ptr,2,2);
PrintList(Ptr);
Delete(Ptr,1);
PrintList(Ptr);
MK_Empty(Ptr);
PrintList(Ptr);
return 0;
}