1.双向链表的概念
双向链表是对单链表的一种改进数据结构。在单链表里,每个节点仅包含一个指向后继节点的指针域;而双向链表在此基础上,为每个节点额外设置了一个指向前驱节点的指针域。
双向链表和单链表的显著区别在于节点的访问方式。在单链表中,若要访问某个节点的前驱节点,只能从链表的头节点开始,按照顺序依次遍历,直至找到目标节点的前驱,过程相对繁琐。但双向链表则不同,它的每个节点都能凭借自身的指针域,直接、便捷地访问其前驱节点和后继节点,无需从头节点开始按顺序逐个查找,大大提高了数据访问的灵活性和效率。
2.双向链表的结构与特点
双向链表的每个节点由三个部分组成
(1)存储当前节点的数据---data
(2)前驱指针---prev
(3)后驱指针---next

带头双向循环链表具有以下显著特点:
头结点(哨兵位)的存在
该链表设置了头结点作为哨兵位。这个特殊的头结点不存储实际的数据,却在链表的操作中扮演着至关重要的角色。它就像是链表的"守护者",使得链表无论在何种操作下,都能保持一种稳定的结构。
链表永不为空
得益于头结点的存在,带头双向循环链表在逻辑上永远不会为空。即使链表中没有存储有效数据的节点,头结点依然存在,保证了链表结构的完整性。这一特性避免了在处理空链表时复杂的边界条件判断,简化了代码逻辑,降低了编程难度。
无需二级指针
在对链表进行插入、删除等操作时,通常不需要使用二级指针。由于头结点始终存在,我们可以直接通过头结点来操作链表,避免了使用二级指针传递地址的复杂性。这不仅让代码更加简洁易懂,还减少了因指针操作不当而引发错误的可能性。
节点访问的高效性
在带头双向循环链表中,任意一个节点都可以方便地找到它的上一个节点和下一个节点。每个节点不仅有指向下一个节点的指针,还有指向前一个节点的指针,并且链表首尾相连形成循环。这种结构使得我们可以从任意一个节点出发,沿着指针的方向快速访问到其相邻节点,大大提高了数据的访问效率和操作的灵活性。
整体结构:

            
            
              cpp
              
              
            
          
          //定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;  //后继节点
	struct ListNode* prev;  //前驱节点
	LTDataType data;   //当前节点的数据
}LTNode;
        3.双链表的总接口
            
            
              cpp
              
              
            
          
          //初始化链表
LTNode* LTInit();
//打印链表
void LTPrint(LTNode* phead);
//判断是否为空
bool LTEmpty(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
// 在pos之前插入
void LTInsert(LTNode* pos, LTDataType x);
//在pos之后插入
void LTInsertAfter(LTNode* pos,LTDataType x);
// 删除pos位置的值
void LTErase(LTNode* pos);
//销毁
void LTDestroy(LTNode* phead);
        3.1初始化链表
3.1.1创建节点
当我们需要插入元素是,只需要创建节点,然后直接把值存入,两个指针置为空,返回该节点的地址就可
            
            
              cpp
              
              
            
          
          //创建节点
LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	//判断节点创建是否成功,避免野指针的出现
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
        3.1.2初始化链表
我们可以采用一种灵活高效的策略。首先,创建一个哨兵位节点。这个哨兵位节点就如同一个稳固的"基石",我们让它自成一个循环链表,形成一个初始的、稳定的结构框架。
当后续有数据添加需求时,我们可采用尾插法来添加新数据。尾插法就像是在已有的队伍末尾依次加入新成员,操作简单且有序。借助这种方式,数据的添加变得极为自由,只要有需要,随时都能进行添加操作,无需提前规划复杂的添加流程。
更为重要的是,采用这种方式无需我们为存储容量问题而烦恼。不像传统的一些存储方式需要频繁地进行扩容操作,过程繁琐且可能会造成资源的浪费。在这里,我们只需要根据实际的数据量不断开辟新的内存空间,真正做到了"用多少,开多少",极大地提高了内存资源的使用效率,避免了不必要的资源闲置与浪费。

            
            
              cpp
              
              
            
          
          //初始化链表
LTNode* LTInit()
{
	//创建一个哨兵位
	LTNode*phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
        3.2打印链表
我们从哨兵位的下一位开始打印,直到cur的下一位是phead打印完成
            
            
              cpp
              
              
            
          
          //打印链表
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("<==>phead<==>");
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur=cur->next;
	}
	printf("\n");
}
        3.3尾插
我们只需要将tail->next连接到newnode上,再将newnode->prev连接到tail上,最后phead的prev和newnode的next连接即可

            
            
              cpp
              
              
            
          
          //尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;
}
        3.4头插
头插就是将哨兵位和头一个节点断开,再进行和尾插类似的工作,特别注意我们要先保存好数据再进行相关操作,避免数据丢失。
            
            
              cpp
              
              
            
          
          //头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* first = phead->next;
	LTNode* newnode = BuyLTNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}
        3.5尾删
尾删我们要先找到tail的前一个节点tailprev,然后将尾节点直接释放掉即可。
特别注意:哨兵位不能删除
            
            
              cpp
              
              
            
          
          //尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next);
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;
	free(tail);
	phead->prev = tailprev;
	tailprev->prev = phead;
}
        3.6头删
头删就是将哨兵位的后一位删除,同样哨兵位不能为空
            
            
              cpp
              
              
            
          
          //头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next);
	LTNode* first = phead->next;
	LTNode* firstnext = first->next;
	free(first);
	first = NULL;
	phead->next = firstnext;
	firstnext->prev = phead;
}
        3.7判断是否为空
判断是否为空这里我们可以加上这个函数,如果phead下一个等于phead,说明链表为空只剩下哨兵位自己, 那么此时我们就不能继续删除了,这里我们可以用这个函数直接替换掉上面的断言
            
            
              cpp
              
              
            
          
          bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
        3.8查找
因为在循环链表中没有NULL,我们在查找过程中没有添加限制条件的话链表会无限循环,那么我们用phead作为条件,读取数据从第一位开始读,当读到phead表示已经把所有数据都遍历了一遍
            
            
              cpp
              
              
            
          
          //查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)
	{
		if (cur == x)
			return cur;
		cur=cur->next;
	}
	return NULL;
}
        3.9在pos插入数据(之前,之后,pos位)
我们通过pos->prev找到posprev的位置,然后将newnode与pos和1posprev联系起来即可
            
            
              cpp
              
              
            
          
          // 在pos之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* newnode = BuyLTNode(x);
	newnode->prev = posprev;
	posprev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}
        同理可得其他两种情况
            
            
              cpp
              
              
            
          
          // 在pos之后插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* posnext = pos->next;
	LTNode* newnode = BuyLTNode(x);
	newnode->prev = pos;
	pos->next = newnode;
	newnode->next = posnext;
	posnext->prev = newnode;
}
        
            
            
              cpp
              
              
            
          
          // 删除pos位置的值
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;
	free(pos);
	posprev->next = posnext;
	posnext->prev = posprev;
}
        3.10销毁
我们将链表的所有数值的清空
            
            
              cpp
              
              
            
          
          //销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}
        4.总结
至此,我们不难发现,无论是头插、头删操作,尾插、尾删操作,还是在指定的 `pos` 位置进行的相关操作,它们背后所遵循的原理实际上是相通的。本质上,头插、头删以及尾插、尾删操作都可以看作是 `pos` 位置操作的特殊情形。
以头插为例,它其实就是在链表头部这个特定的 `pos` 位置插入元素;头删则是删除链表头部这个 `pos` 位置的元素。同理,尾插是在链表尾部的 `pos` 位置插入元素,尾删则是删除尾部 `pos` 位置的元素。
基于这样的发现,我们完全有能力对现有的代码进行简化。通过将这些看似不同的操作归纳统一,提取出它们的共性部分,我们能够让代码的结构更加简洁清晰,减少冗余代码,提高代码的可维护性和可读性。如此一来,后续无论是对代码进行功能扩展还是故障排查,都会变得更加轻松高效。
            
            
              cpp
              
              
            
          
          void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
 
	LTInsert(phead, x);
}
 
 
void LTPushFront(LTNode* phead, LTDataType x)
{
	LTInsert(phead->next, x);
}
 
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTErase(phead->prev);
}
 
 
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
 
	LTErase(phead->next);
 
}
        5.完整代码
            
            
              cpp
              
              
            
          
          //List.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
//定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;  //后继节点
	struct ListNode* prev;  //前驱节点
	LTDataType data;   //当前节点的数据
}LTNode;
//初始化链表
LTNode* LTInit();
//打印链表
void LTPrint(LTNode* phead);
//判断是否为空
bool LTEmpty(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
// 在pos之前插入
void LTInsert(LTNode* pos, LTDataType x);
// 在pos之后插入
void LTInsertAfter(LTNode* pos, LTDataType x);
// 删除pos位置的值
void LTErase(LTNode* pos);
//销毁
void LTDestroy(LTNode* phead);
        
            
            
              cpp
              
              
            
          
          //Test.c
#include"List.h"
void TestList1()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
}
void TestList2()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
}
void TestList3()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);
	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		LTInsert(pos, 30);
	}
	LTPrint(plist);
	LTDestroy(plist);
	plist = NULL;
}
int main() 
{
	TestList1();
	TestList2();
	TestList3();
	return 0;
}
        
            
            
              cpp
              
              
            
          
          //List.c
#include"List.h"
//创建节点
LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	//判断节点创建是否成功,避免野指针的出现
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
//初始化链表
LTNode* LTInit()
{
	//创建一个哨兵位
	LTNode*phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
//打印链表
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("<==>phead<==>");
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur=cur->next;
	}
	printf("\n");
}
//判断是否为空这里我们可以加上这个函数,如果phead下一个等于phead,说明链表为空只剩下哨兵位自己,
// 那么此时我们就不能继续删除了,这里我们可以用这个函数直接替换掉上面的断言
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* first = phead->next;
	LTNode* newnode = BuyLTNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}
//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;
	free(tail);
	phead->prev = tailprev;
	tailprev->next = phead;
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* first = phead->next;
	LTNode* firstnext = first->next;
	free(first);
	phead->next = firstnext;
	firstnext->prev = phead;
}
//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)
	{
		if (cur == x)
			return cur;
		cur=cur->next;
	}
	return NULL;
}
// 在pos之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* newnode = BuyLTNode(x);
	newnode->prev = posprev;
	posprev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}
// 在pos之后插入
void LTInsertAfter(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* posnext = pos->next;
	LTNode* newnode = BuyLTNode(x);
	newnode->prev = pos;
	pos->next = newnode;
	newnode->next = posnext;
	posnext->prev = newnode;
}
// 删除pos位置的值
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;
	free(pos);
	posprev->next = posnext;
	posnext->prev = posprev;
}
//销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}
////代码简化
//void LTPushBack(LTNode* phead, LTDataType x)
//{
//	assert(phead);
//
//	LTInsert(phead, x);
//}
//
//
//void LTPushFront(LTNode* phead, LTDataType x)
//{
//	LTInsert(phead->next, x);
//}
//
//void LTPopBack(LTNode* phead)
//{
//	assert(phead);
//	assert(!LTEmpty(phead));
//	LTErase(phead->prev);
//}
//
//
//void LTPopFront(LTNode* phead)
//{
//	assert(phead);
//	assert(!LTEmpty(phead));
//
//	LTErase(phead->next);
//
//}
        