大家好呀,我们今天来认识一下双向循环链表。
双向循环链表
目录
对双向循环链表的认识
学习了单链表后,对双向循环链表会变得较为简单。那么,什么是双向循环链表呢?
所谓双向循环链表,就是链表其中的节点都含有两个指针,一个指向后一个节点,一个指向前一个节点。如图:
也就是说,对于双向循环链表来说,头插和尾插会及其方便。那么接下来我们就来编写带哨兵尾的双向循环链表。
创建双向循环链表
创建链表,首先便是创建结构体,创建两个结构体指针和节点数据。代码如下:
typedef struct ListNode { struct ListNode* prev; struct ListNode* next; LTDataType data; }LTNode;
双向循环链表的功能与链表一致,头插尾插,头删尾删,查找。在编写功能之前,我们先对初始化和销毁函数进行编写。
初始化
我们先了解下什么是哨兵位。所谓哨兵位,就是带头结点的单链表的第一个节点,
属于附加的链表节点,无有效数值,只储存第一个有效节点的地址,负责找到第一个节点。那么我们在初始化时,就需要使用创建节点的函数赋一个无效值,并将两个指针置为NULL。
因此优先编写创建节点的函数。创建一个新节点,使用malloc进行开辟即可。进行检查,并将指针置空。代码如下:
LTNode* CreateLTNode(LTDataType x) { LTNode* newnode = (LTNode*)malloc(sizeof(LTNode)); if (newnode == NULL) { perror("malloc fail"); exit(-1); } newnode->data = x; newnode->next = NULL; newnode->prev = NULL; return newnode; }
紧接着编写初始化函数。代码如下:
LTNode* LTInit() { LTNode* phead = CreateLTNode(-1); phead->next = phead; phead->prev = phead; return phead; }
遍历(检查打印)函数
在编写功能之前,我们先编写打印函数。打印需要遍历,那么如何进行遍历,遍历的结束标志又是什么呢?
进行遍历,只需要不断地next即可,结束标志需要对哨兵位不需要打印进行考虑,所以让遍历指针cur!=phead即可。代码如下:
void LTPrint(LTNode* phead) { LTNode* cur = phead->next; printf("哨兵位 "); while (cur != phead) { printf("%d<=>", cur->data); cur = cur->next; } }
尾插,尾删
我们还是先来编写尾插。如何进行尾插呢?
只需要将原来的尾节点的next指向新节点,新节点的next指向头节点。头节点的prev指向新节点,新节点的prev指向原来的尾节点即可。如图:
代码如下:
void LTPushBack(LTNode* phead, LTDataType x) { assert(phead); LTNode* tail = phead->prev; LTNode* newnode = CreateLTNode(x); tail->next = newnode; newnode->prev = tail; newnode->next = phead; phead->prev = newnode; }
之后就是尾删。
尾删就较为简单了,将原来尾节点的前一个节点的next指向头节点,再让头节点的prev指向其即可。如图:
但是尾删需要注意,当链表只剩哨兵位的时候,就不能再删了,需要判断。代码如下:
void LTPopBack(LTNode* phead) { assert(phead); assert(phead->next = phead); LTNode* tail = phead->prev; LTNode* tailPrev = tail->prev; free(tail); tailPrev->next = phead; phead->prev = tailPrev; }
头插,头删
那么如何进行头插呢?
由于拥有哨兵位,那么我们首先找到哨兵位的头节点的next节点,我们让创建出的新节点newnode的next指向它,再让该节点的prev指向newnode,再让哨兵位和新节点建立联系就可以了。如图:
代码如下:
void LTPushFront(LTNode* phead, LTDataType x) { assert(phead); LTNode* newnode = CreateLTNode(x); newnode->next = phead->next; phead->next->prev = newnode; phead->next = newnode; newnode->prev = phead; }
紧接着就是头删,头删就比较简单了,直接让phead与其next的next相互链接,再将要删除的节点free掉就可以了。当然需要判断如果只有哨兵位的情况。为了方便编写,我们创建两个指针来保存后两个节点。如图:
代码如下:
void LTPopFront(LTNode* phead) { assert(phead); assert(phead->next != phead); LTNode* first = phead->next; LTNode* second = phead->next->next; phead->next = second; second->prev = phead; free(first); first = NULL; }
查找
查找函数就很好编写了,直接从哨兵位往后遍历就可以了,当回到哨兵位停止遍历即可。代码如下:
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 LTInsert(LTNode* pos, LTDataType x) { assert(pos); LTNode* posPrev = pos->prev; LTNode* newnode = CreateLTNode(x); posPrev->next = newnode; newnode->prev = posPrev; newnode->next = pos; pos->prev = newnode; }
接下来便是在任意位置删除。
任意位置删除就更为简单了,我们直接记录要删除节点的前一个与后一个节点,再将两个节点链接就可以了,最后进行free。当然还需要检查链表是否为空,为空则不能进行删除,编写完成。代码如下:
void LTErase(LTNode* pos) { assert(pos); LTNode* posPrev = pos->prev; LTNode* posNext = pos->next; posPrev->next = posNext; posNext->prev = posPrev; free(pos); pos = NULL; }
完整代码
list.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
//初始化
LTNode* LTInit();
//销毁
void LTDestory(LTNode* phead);
//打印
void LTPrint(LTNode* phead);
//创建节点
LTNode* CreateLTNode(LTDataType x);
//尾插
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);
//任意位置插入
void LTInsert(LTNode* pos, LTDataType x);
//任意位置删除
void LTErase(LTNode* pos);
list.c
#include"list.h"
//初始化
LTNode* LTInit()
{
LTNode* phead = CreateLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
//打印
void LTPrint(LTNode* phead)
{
LTNode* cur = phead->next;
printf("哨兵位 ");
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
}
//创建节点
LTNode* CreateLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = CreateLTNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next = phead);
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = CreateLTNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* first = phead->next;
LTNode* second = phead->next->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
//查找
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 LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = CreateLTNode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
//任意位置删除
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
pos = NULL;
}
test.c
#include"list.h"
int main()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushBack(plist, 5);
LTPrint(plist);
return 0;
}
小结
那么对双向循环链表的认识到这里就结束啦,其实小编偷了个懒,没有写销毁函数,这个相信大家都比较熟悉了,就不进行赘述了,我们下次再见!