数据结构与算法(四)——线性表(下)双向链表篇

目录

 一、前情提要

 二、双向链表的功能和实现

 三、代码实现


 一、前情提要

         上一篇讲到了静态链表,它的本质和单链表相同,换了一种实现的形式罢了。但单链表存在一个问题,就是只能从头走到尾,一条路走到黑。那如果我要返回上一个结点呢?没办法,单链表无法完成。这样就衍生出了双向链表。本篇围绕双向链表给出代码和分析。

二、双向链表的功能和实现

(一)双向链表的初始化

        双向链表和单向链表的不同就在于,它可以指向前面,也可以指向后面。所以自然想到在指针域中多添加一个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;
} 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值