1. 带头双向循环链表
链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
怎么算出8种情况:每次两种情况,三次,所以是2*2*2=8
1. 单向或者双向
2. 带头或者不带头
3. 循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1. 无头单向非循环链表:结构简单,一般不会单独用来单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希表,图的邻接表等等。另外这种解构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,是带头双向循环链表。另外这个结构虽然复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
2. 带头双向循环链表概念
带头双向循环链表是一种链表数据结构,它具有以下特点:
1. 头结点:带头双向循环链表包含一个头结点,它位于链表的起始位置,并且不存储实际数据。头结点的前驱指针指向尾节点,头节点的后继指针指向第一个实际数据节点。
2. 循环连接:尾节点的后继指针指向头节点,而头结点的前驱指针指向尾节点,将链表形成一个循环连接的闭环。这样可以使链表在遍历时可以无限循环,方便实现循环操作。
3. 双向连接:每个节点都有一个前驱指针和一个后继指针,使得节点可以向前和向后遍历。前驱指针指向前一个节点,后继指针指向后一个节点。
总结:带头双向循环链表可以支持在链表的任意位置进行插入和删除操作,并且可以实现正向和反向的循环遍历。通过循环连接的特性,链表可以在连续的循环中遍历所有节点,使得链表的操作更加灵活和高效。
3. 带头双向链表的实现
1. 带头双向循环链表的结构
typedef int LTDataType;//代码中将int定义为LTDataType是为了提供代码的可读性和可维护性,并增加代码的灵活性。
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;//存储下一个节点的地址
struct ListNode* prev;//存储上一个节点的地址
}LTNode;//重新命名结构体类型
通过将int定义为LTDataType,可以在代码中使用LTDataType作为数据类型,而不是直接使用int。这样做的好处有以下几点:
可读性:使用LTDataType作为数据类型可以使代码更具可读性。LTDataType作为一个自定义的数据类型名称,可以更好地表达代码中数据的含义和用途,提高代码的可理解性。
可维护性:将int定义为LTDataType可以方便地在代码中统一修改数据类型。如果将来需要将数据类型更改为其他类型,只需修改typedef语句中的定义,而不需要在整个代码中逐个修改具体的数据类型,减少了修改的工作量和出错的可能性。
灵活性:通过使用LTDataType,可以在代码中轻松更改数据类型,而不会对代码的其他部分产生影响。这种抽象化的方式可以使代码更具通用性,便于在不同的场景中重用。
原文链接:https://blog.youkuaiyun.com/weixin_65186652/article/details/132290503
2. 动态申请节点函数
LTNode* BuyLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
//刚申请下的堆区空间有可能开辟失败,所以要进行检查
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
//开辟好后就赋值
newnode->data = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
3. 链表的初始化
//初始化--因为要改动指向结构体的指针,所以要么就取地址,用二级指针接收。
//要么就像下面这样,用返回值接收。
LTNode* LTInit()// 由于形参phead是实参plist的拷贝
{
LTNode* guard = BuyLTNode(-1);
guard->next = guard;
guard->prev = guard;
return guard;
}
int main()
{
LTNode* plist = LTInit();
}
4. 链表打印
void LTPrint(LTNode* phead)
{
assert(phead);
printf("guard<==>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("\n");
}
5. 链表尾部插入节点
与单链表有两个不一样的点:
情况一:
1. 单链表尾插节点需要遍历全链表,当指针走到链表最后一个节点的时候,判断判断tail->next是否为NULL,若为NULL,则跳出遍历的循环,尾插新结点。然而带头双向循环链表不需要遍历链表,只需要对哨兵位的头节点的prev域解引用,直接找到带头双向循环链表的尾节点,尾插新节点。
情况二:
2. 头指针的区别:带头双向循环链表不需要判断头指针是否指向NILL,是因为哨兵位的头节点也是有它的地址的,添加新节点时只需要直接在尾节点尾插。然而单链表却需要判断头指针是否指向NIULL,而且需要用到二级指针,比较棘手。
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;//通过哨兵位的头节点的prev找到链表最后一个结点,并用tail指向
LTNode* newnode = BuyLTNode(x);
tail->next= newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
6. 链表头部插入节点
//方法一,不需要创建变量
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyLTNode(x);
newnode->next = phead->next;//把头结点后面的d1的地址赋值给新结点的next
phead->next->prev = newnode;//d1指向新节点
phead->next = newnode;//改变头节点的next,让它指向新结点
newnode->prev = phead;//新结点的prev指向phead头插完毕.
}
7. 链表尾删节点
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next == phead);
LTNode* tail= phead->prev;
LTNode* Prev= tail->prev ;
Prev->next = phead;
phead->prev = Prev;
free(tail); //先删除再链接
tail = NULL;
}
8. 链表头删节点
void LTPopFront(LTNode* phead)
{
assert(phead);
LTNode* first = phead->next;
LTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first=NULL;
}
9. 链表查找/修改某个值
LTNode* STFind(LTNode* phead, LTDataType x)
{
//assert(phead);
LTNode* cur = phead->next;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
if (cur->data == phead->data)//若重新回到哨兵位,则说明链表遍历完毕,找不到x值,返回NULL
{
break;
}
}
return NULL;
}
10. 在链表pos位置之前插入值
void LTInsert(LTNode* pos,LTDataType x)//输入要删除的数的位置即可
{
assert(pos);
LTNode* newnode = BuyLTNode(x);
LTNode* prev = pos->prev;
prev->next = newnode;
newnode->next = pos;
newnode->prev = prev;
pos->prev = newnode;
}
11. 在链表pos位置处删除此节点
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
12. 释放链表动态申请的空间
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead=NULL;
}