数据结构——双向链表_使 node类描述双向链表的组成节点,该类应包含如下数据成员 data 于保

//申请新的结点[声明]
LTNode* BuyListNode(LTDataType x);

/申请新的结点[实现]
LTNode* BuyListNode(LTDataType x)
{
	LTNode* newnode = malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc error");
	}
	else
	{
		newnode->data = x;
		//避免野指针
		newnode->next = NULL;
		newnode->prev = NULL;
		return newnode;
	}
}

2.8 链表的尾插

准备工作准备就绪后,就开始链表的主要功能的实现啦。

首先就是链表的尾插。我们先来看看单链表是如何进行尾插的,对于单链表,我们需要对单链表进行遍历找到最后一个结点,再对这个结点和新结点进行链接的操作。而对于双向链表,就没有这么复杂了,因为哨兵卫的上一个结点就已经指向了链表的最后一个结点,在找到这个尾结点之后,剩下的就是链接的问题了。相比于单链表要复杂一点,要将哨兵卫、尾结点、新结点这三个结点进行链接

具体链接和代码如下:

//链表的尾插[声明]
void LTPushBack(LTNode* phead, LTDataType x);

//链表的尾插[实现]
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	//创建新的结点
	LTNode* newnode = BuyListNode(x);
	//尾结点的链接
	tail->next = newnode;
	newnode->prev = tail;
	//哨兵卫的链接
	phead->prev = newnode;
	newnode->next = phead;
}

2.9链表的尾删

在讲解完链表的尾插,接着就是链表的尾删了。同样地,我们不需要对链表进行遍历来找到尾结点,只需要通过哨兵卫来找到最后一个结点,并将其置空,再将倒数第二个结点和哨兵卫进行链接。不

具体图片和代码如下:

//链表的尾删
void LTPopBack(LTNode* phead);

//链表的尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//判断是否只有哨兵卫
	assert(!LTEmpty(phead));
	//改变哨兵卫的链接
	phead->prev = phead->prev->prev;
	//释放尾结点的内存
	free(phead->prev->next);
	//改变尾部链接
	phead->prev->next = phead;
}

2.10链表的头插

在尾插、尾删之后,就是头插、头删了,那么该如何实现链表的头插呢。

其步骤也很简单,先是申请一个新的结点,进而将该结点与哨兵卫与链表的头结点进行链接。需要注意的一点就是判断链表是否只有哨兵卫,若是只有哨兵卫就无须删除了。

具体图片和代码如下:

//链表的头插[声明]
void LTPushFront(LTNode* phead, LTDataType x);

//链表的头插[实现]
void LTPushFront(LTNode* phead, LTDataType x)
{
	//创建新的结点
	LTNode* newnode = BuyListNode(x);
	LTNode* first = phead->next;
	//改变链接关系
	newnode->next = first;
	first->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
	//代码复用
	//LTInsert(phead->next, x);
}

2.11链表的头删

头插之后便是头删了,这一部分的代码也是相对简单的,主要思路是将哨兵卫与头结点的下一个结点链接,并将头结点的内存释放。唯一需要注意的一点就是判断链表是否只有哨兵卫,若是只有哨兵卫就无须删除了。

//链表的头删[声明]
void LTPopFront(LTNode* phead);

//链表的头删[实现]
void LTPopFront(LTNode* phead)
{
	assert(phead);
	//改变链接关系+释放内存
	phead->next = phead->next->next;
	free(phead->next->prev);
	phead->next->prev = phead;
}

2.12链表的插入【pos之前】

链表的头插、头删、尾插、尾删已经全部讲解完毕了,但其实对于双向链表来说,以上四个功能并不需要,只需要通过链表的插入和删除便可以实现,也就是代码的复用。但是为了帮助小伙伴们更好的理解双向链表,便将上述功能一一实现。

接下来就让我们进入“”链表的插入“”这一功能的实现,我们采取的是在**"pos"之前进行数据的插入**。对“pos”之后插入数据的感兴趣的小伙伴也可以尝试自己编写,两者的逻辑是类似的。

具体的实现逻辑呢,就是申请一个新的结点,并将pos位置的结点、pos位置之前的结点和新结点进行链接。不过需要注意的一点是,如果你用的的指针过少,便要注意链接的顺序,若是链接顺序便可能无法找到原来结点的数据,比较保险且快捷的方法就是再另外设置一个指针用来记录链接的结点,便可以毫无顾虑的进行结点间的链接了。

注:在尾插的代码中对该段代码的复用有些独特,是通过传哨兵卫的指针进行插入,这是由于双向链表的循环特性决定的,链表的最后一个结点就是哨兵卫的前一个结点

//链表的插入(前)[声明]
void LTInsert(LTNode* pos, LTDataType x);

//链表的插入(前)[实现]
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = BuyListNode(x);
	LTNode* prev = pos->prev;
	//改变链接关系
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

2.13链表的删除

链表的删除的实现逻辑大致就是将要删除的结点置空,并将其前后两个结点进行链接即可。不过要注意的依旧是链表是否只含有哨兵卫,若只含有哨兵卫,就无需进行链表的删除。

//链表的删除[声明]
void LTErase(LTNode* pos);

//链表的删除[实现]
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* next = pos->next;
	LTNode* prev = pos->prev;
	//改变链接关系并释放内存
	prev->next = next;
	next->prev = prev;
	free(pos);
}

2.14链表的销毁

不知不觉之中,就来到了双向链表的最后一个功能:链表的销毁

其实现逻辑:从哨兵卫的下一个结点开始遍历链表,边遍历边销毁,直至遍历到哨兵卫为止,最后将哨兵卫的内存销毁并将指针置空

//链表的销毁[声明]
void LTDestroy(LTNode* phead);

//链表的销毁[实现]
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = cur=NULL;
}

三、双向链表完整代码(含测试模块)

3.1 List.h

#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<stdbool.h>

//数据类型与结构体定义
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;	//存放下一个结点
	struct ListNode* prev;	//存放上一个结点
	LTDataType data;		//数据类型 
}LTNode;

//申请新的结点
LTNode* BuyListNode(LTDataType x);
//链表初始化
LTNode* LTInit();
//链表的打印
void LTPrint(LTNode* phead);
//判断链表是否只有哨兵卫
bool LTEmpty(LTNode* phead);
//链表的查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);
//链表的尾删
void LTPopBack(LTNode* phead);
//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);
//链表的头删
void LTPopFront(LTNode* phead);
//链表的插入(前)
void LTInsert(LTNode* pos, LTDataType x);
//链表的删除
void LTErase(LTNode* pos);
//链表的销毁
void LTDestroy(LTNode* phead);


3.2 List.c

#include "List.h"


//申请新的结点
LTNode* BuyListNode(LTDataType x)
{
	LTNode* newnode = malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc error");
	}
	else
	{
		newnode->data = x;
		//避免野指针
		newnode->next = NULL;
		newnode->prev = NULL;
		return newnode;
	}
}
//链表初始化
LTNode* LTInit()
{
	LTNode* phead =BuyListNode(-1);
	phead->next = phead;	//下一个结点指向本身
	phead->prev = phead;	//上一个结点指向本身
	return phead;
}

//判断链表是否只有哨兵卫
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

//链表的查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}


//链表的打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->next == phead)
		{
			printf("<->%d<->", cur->data);
		}
		else
		{
			printf("<->%d", cur->data);
		}
		cur = cur->next;
	}
	printf("\n");
}

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	//创建新的结点
	LTNode* newnode = BuyListNode(x);
	//尾结点的链接
	tail->next = newnode;
	newnode->prev = tail;
	//哨兵卫的链接
	phead->prev = newnode;
	newnode->next = phead;
	//代码复用
	//LTInsert(phead, x);
}



//链表的尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//判断是否只有哨兵卫
	assert(!LTEmpty(phead));
	//改变哨兵卫的链接
	phead->prev = phead->prev->prev;
	//释放尾结点的内存
	free(phead->prev->next);
	//改变尾部链接
	phead->prev->next = phead;
	//代码复用
	//LTErase(phead->prev);
}

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	//创建新的结点
	LTNode* newnode = BuyListNode(x);
	LTNode* first = phead->next;
	//改变链接关系
	newnode->next = first;
	first->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
	//代码复用
	//LTInsert(phead->next, x);
}

//链表的头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	//改变链接关系+释放内存
	phead->next = phead->next->next;
	free(phead->next->prev);
	phead->next->prev = phead;
	//代码复用
	//LTErase(phead->next);

}

//链表的插入(前)
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = BuyListNode(x);
	LTNode* prev = pos->prev;
	//改变链接关系
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

//链表的删除
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* next = pos->next;
	LTNode* prev = pos->prev;
	//改变链接关系并释放内存
	prev->next = next;
	next->prev = prev;
	free(pos);
}

//链表的销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = cur=NULL;
}

3.3 text.c

#include "List.h"

//测试代码1:尾插
text1()
{
	LTNode* phead = LTInit();
	LTPushBack(phead, 1);
	LTPushBack(phead, 2);
	LTPushBack(phead, 3);
	LTPushBack(phead, 4);
	LTPrint(phead);
}

//测试代码2:尾删
text2()
{
	LTNode* phead = LTInit();
	LTPushBack(phead, 1);
	LTPushBack(phead, 2);
	LTPushBack(phead, 3);
	LTPushBack(phead, 4);
	LTPrint(phead);
	printf("\n");
	LTPopBack(phead);
	LTPopBack(phead);
	LTPrint(phead);
}


//测试代码3:头插
text3()
{
	LTNode* phead = LTInit();
	LTPushFront(phead, 4);
	LTPushFront(phead, 3);
	LTPushFront(phead, 2);
	LTPushFront(phead, 1);
	LTPrint(phead);
}


//测试代码4:头删
text4()
{
	LTNode* phead = LTInit();
	LTPushFront(phead, 4);
	LTPushFront(phead, 3);
	LTPushFront(phead, 2);
	LTPushFront(phead, 1);
	LTPopFront(phead);
	LTPopFront(phead);
	LTPrint(phead);
}


//测试代码5:插入(前)
text5()
{
	LTNode* phead = LTInit();
	LTPushFront(phead, 4);
	LTPushFront(phead, 2);


![img](https://img-blog.csdnimg.cn/img_convert/fd1607b2b6ce931c299a510d2e305057.png)
![img](https://img-blog.csdnimg.cn/img_convert/ecdcbda5c6e1853ffa0ea162b3c48989.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

hFront(phead, 4);
	LTPushFront(phead, 3);
	LTPushFront(phead, 2);
	LTPushFront(phead, 1);
	LTPopFront(phead);
	LTPopFront(phead);
	LTPrint(phead);
}


//测试代码5:插入(前)
text5()
{
	LTNode* phead = LTInit();
	LTPushFront(phead, 4);
	LTPushFront(phead, 2);


[外链图片转存中...(img-xMbqFjHu-1714519449356)]
[外链图片转存中...(img-BAJe3Gia-1714519449357)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值