前言
链表分多种,分别为
不带头不循环单向链表、不带头循环单向链表、带头循环单向链表、带头不循环单向链表
不带头不循环双向链表、不带头循环双向链表、带头循环双向链表、带头不循环双向链表
一共八种
在前一篇博客中完成的单链表即为不带头不循环单向链表
而今天要完成的是带头循环双向链表
链表的数据结构
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
单向链表因为是单一指向,所以只需要一个next指针指向下一个节点即可
但现在要实现的是双向链表,所以我们还需要多一个指针指向前一个节点,所以相比单链表来说多了一个prev指向前面一个节点
链表的实现
下面开始链表的实现
链表的初始化
LTNode* LTInit()
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail\n");
exit(1);
}
newnode->data = -1;
newnode->next = newnode->prev = newnode;
return newnode;
}
void LTInit(LTNode** pphead)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail\n");
exit(1);
}
newnode->data = -1;
newnode->next = newnode->prev = newnode;
*pphead = newnode;
}
这里链表有两种初始化的方法,一种方法是在传给函数参数的时候将链表头节点的地址传过来(也就是使用二级指针) ,这样直接在函数内部改变pphead即可
第二种方法就是将我们创建的新节点作为返回值传出去,在调用函数的地方使用一个值接收即可
这里两种方法都好,但是我会选择使用第二种方法
因为下面我们写的打印、尾插、删除等等函数都使用一级指针,如果在这个地方单独的使用一个二级指针会显得有点突兀,所以为了让未来的我们自己或者其他使用者能够简单的使用这个链表,尽量格式都统一了
这里因为是带头的单链表,我们初始化出来的带头节点我们可以把它叫做:哨兵位,哨兵位可以把它理解为一个放哨的,它不存储任何有效值,它只作为头节点
所以这里初始化的时候直接给它赋值为-1
newnode->next = newnode->prev = NULL;
上面这么做是错误的❌
因为我们写的是循环的链表,这样指向空怎么能算循环呢?
应该要让哨兵位的两个指针都指向自己,这就是一个合格的带头循环双向指针
当然为了简化我们后面获取节点的难度,我们可以直接写一个函数封装获取节点的方法
LTNode* LTBuyNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail\n");
exit(1);
}
newnode->data = x;
newnode->next = newnode->prev = newnode;
}
这样我们的初始化代码也可以很好的简化了
LTNode* LTInit()
{
//LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
//if (newnode == NULL)
//{
// perror("malloc fail\n");
// exit(1);
//}
//newnode->data = -1;
//newnode->next = newnode->prev = newnode;
LTNode* newnode = LTBuyNode(-1);
return newnode;
}
链表的打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
打印每一个节点我们就一定需要循环来解决
但是这个循环条件和单链表的循环条件是不同的
不带头不循环单向链表:
while (pcur != NULL)
带头循环双向链表:
while (pcur != phead)
单向链表的最后会指向NULL指针,但是带头循环双向链表不会,它最后会循环的指回头节点
所以判断循环结束的条件就是判断pcur是否回到了phead节点
然后我们每经过一个节点就打印一下它的数值即完成了打印函数
链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
LTNode* ptail = phead->prev;
newnode->next = phead;
newnode->prev = ptail;
phead->prev = newnode;
ptail->next = newnode;
}
这里我们需要改变3个节点:头节点,尾节点,新节点
在上面的代码中将创建了新变量ptail指向最后一个节点也就是phead->prev
如果你觉得很乱,我们可以先看newnode
newnode的下一个节点为了循环肯定要指向phead,所以newnode->next = phead
newnode的下一个节点就是2这个节点(也就是ptail),所以newnode->prev = ptail
最后别忘了phead的前一个节点已经不是2而是3了,ptail的下一个节点已经不是phead了而是newnode,这样就完成了尾插
链表的尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->prev;
LTNode* prev = del->prev;
prev->next = phead;
phead->prev = prev;
free(del);
del = NULL;
}
尾删和前面尾插的思路一样,先找到要删除的节点和删除节点的前一个节点
让删除节点的前后节点连接起来,相当于把del松开了,最后free(del)即可
链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
LTNode* next = phead->next;
newnode->next = next;
newnode->prev = phead;
next->prev = newnode;
phead->next = newnode;
}
注:头插不是插在phead的前面
如果插在phead的前面其实和尾插没有区别,只是我们看起来像是头插了,但是头节点还是phead的情况下其实它是在后面
所以我们还是先找到那三个节点:头节点,头节点的后一个节点,新节点
让它们一一相连即可
链表的头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->next;
LTNode* next = phead->next->next;
phead->next = next;
next->prev = phead;
free(del);
del = NULL;
}
头删也不是删哨兵位,而是哨兵位后面的一个节点
也是和前面删除一样,把第一个节点分离出去,让哨兵位和第二个节点相连使它变成第一个节点即可
最后不要忘记了free(del)
链表值查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
//找到了
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
查找肯定也是要遍历整个链表,循环条件和前面的打印一样
如果后面写的函数都要这样判断,为了让我们的代码通读性更强,我们可以换种写法,使用个函数来返回一下链表中的节点是否为空(链表为空指的是只剩下一个哨兵位)
bool LTEmpty(LTNode* phead) { assert(phead); if (phead->next == phead) return true; else return false; }
所以最后查找代码就可以这样写
LTNode* LTFind(LTNode* phead, LTDataType x) { assert(phead); LTNode* pcur = phead->next; while (!LTEmpty(phead)) { //找到了 if (pcur->data == x) { return pcur; } pcur = pcur->next; } //没有找到 return NULL; }
如果我们找到了x指定值,则返回当前节点
若出了循环还没有找到x,则说明链表中没有该值返回NULL即可
链表的指定节点后插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
LTNode* next = pos->next;
newnode->next = next;
newnode->prev = pos;
pos->next = newnode;
next->prev = newnode;
}
该插入方法和前面的插入基本相同
找到我们要的三个节点:pos节点,pos节点的下一个节点,新节点
让三者相连即可
链表的指定节点删除
void LTErase(LTNode* pos)
{
assert(pos);
assert(pos->next != pos);
LTNode* next = pos->next;
LTNode* prev = pos->prev;
prev->next = next;
next->prev = prev;
free(pos);
//函数参数不传二级指针无法修改pos
//pos = NULL;
}
删除也和前面的删除都基本相同
找到那三个节点:pos节点,pos的前一个节点,pos的后一个节点
让pos的前一个节点和pos的后一个节点相连即可
最后不要忘了free(pos)
这里pos = NULL是无效的,因为传的是一级指针,是形参,形参的改变无法改变实参,所以我们要置NULL只能在函数外面手动置NULL
链表的销毁
void LTDestroy(LTNode* phead)
{
LTNode* pcur = phead->next;
while (LTEmpty(phead))
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
}
双链表的销毁和单链表一样,都是遍历整个链表,一个个删除
但是这里这么写循环头节点是无法free()的,所以我们循环走完还要free(phead)最后一块节点
但是这个函数的缺点也是没有传二级指针,形参的改变无法改变实参,所以我们函数使用完成之后只能手动让phead = NULL,否则它将成为野指针
带头循环双向链表完整代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//带头双向循环链表
//void LTInit(LTNode** pphead);
LTNode* LTInit();
void LTPrint(LTNode* phead);
bool LTEmpty(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
void LTDestroy(LTNode* phead);
LTNode* LTBuyNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail\n");
exit(1);
}
newnode->data = x;
newnode->next = newnode->prev = newnode;
}
LTNode* LTInit()
{
//LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
//if (newnode == NULL)
//{
// perror("malloc fail\n");
// exit(1);
//}
//newnode->data = -1;
//newnode->next = newnode->prev = newnode;
LTNode* newnode = LTBuyNode(-1);
return newnode;
}
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
LTNode* ptail = phead->prev;
newnode->next = phead;
newnode->prev = ptail;
phead->prev = newnode;
ptail->next = newnode;
}
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->prev;
LTNode* prev = del->prev;
prev->next = phead;
phead->prev = prev;
free(del);
del = NULL;
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
LTNode* next = phead->next;
newnode->next = next;
newnode->prev = phead;
next->prev = newnode;
phead->next = newnode;
}
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->next;
LTNode* next = phead->next->next;
phead->next = next;
next->prev = phead;
free(del);
del = NULL;
}
bool LTEmpty(LTNode* phead)
{
assert(phead);
if (phead->next == phead)
return true;
else
return false;
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (!LTEmpty(phead))
{
//找到了
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
LTNode* next = pos->next;
newnode->next = next;
newnode->prev = pos;
pos->next = newnode;
next->prev = newnode;
}
void LTErase(LTNode* pos)
{
assert(pos);
assert(pos->next != pos);
LTNode* next = pos->next;
LTNode* prev = pos->prev;
prev->next = next;
next->prev = prev;
free(pos);
//函数参数不传二级指针无法修改pos
//pos = NULL;
}
void LTDestroy(LTNode* phead)
{
LTNode* pcur = phead->next;
while (LTEmpty(phead))
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
}
void TestList()
{
LTNode* plist = LTInit();
//LTPrint(plist);
//int ret = LTEmpty(plist);
//if (ret == true)
// printf("链表为空\n");
//else
// printf("链表不为空\n");
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
//LTPushFront(plist, 1);
//LTPrint(plist);
//LTPushFront(plist, 2);
//LTPrint(plist);
//LTPushFront(plist, 3);
//LTPrint(plist);
//LTPushFront(plist, 4);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//LTNode* find = LTFind(plist, 4);
//if (find == NULL)
// printf("没到了\n");
//else
// printf("找到了\n");
//LTInsert(find, 20);
//LTPrint(plist);
//LTErase(find);
//LTPrint(plist);
LTDestroy(plist);
//销毁传一级指针的代价就是需要手动将plist置NULL
plist = NULL;
}
int main()
{
TestList();
return 0;
}
完