文章目录
1. 双向循环链表的结构
首先,双向链表区别去单链表,它拥有两个节点
一个在前,一个在后,它的节点结构如下
//优化前
struct DListNode
{
struct Node* prev;
int data;
struct Node* next;
};
//为了更加方便我们后面使用该结构体,我们可以使用typedef关键字来定义自己习惯的数据类型名
//优化后
typedef int DLDataType;
typedef struct DListNode
{
struct Node* prev;
DLDataType data;
struct Node* next;
}DLNode;
知道结构后,我们只需要多创造几个节点然后利用其节点的指针,使其链接起来,就是我们的双向链表了。
双向循环链表就是双向链表上将首尾串起来
分别是 1节点的prev指向5节点,5节点的next指向1节点。
2.双向循环链表的创建
(1)创造节点
首先我们先创建一个节点
DLNode* CreateNode(DLDataType x)
{
//首先我们创造一个节点
DLNode* Node = (DLNode*)malloc(sizeof(DLNode));
//当malloc失败后结束
if(Node == NULL)
{
perror("malloc fail");
exit(-1);
}
//将新开的节点指针初始化为空,防止野指针
Node->data = x;
Node->prev = NULL;
Node->next = NULL;
return Node;
}
双链表需要注意的是,每当我们创建一个新节点,我们需要让其与前驱节点2次链接
一次是新节点的prev指向前驱节点
一次是前驱节点的next指向新节点
(2)初始化哨兵位
代码实现:
//构造一个空的双向循环链表
//初始化哨兵位
DLNode* CreateList()
{
DLNode* phead = CreateNode(0);//调用上面的CreateNode()函数创建数据域为0的头指针指向头节点。
phead->next = phead;
phead->prev = phead;
return phead;
}
3.双向循环链表的尾插尾删
(1)尾插:
思路:双向循环链表首尾相连
void DLPushBack(DLNode* phead,DLDataType x)
{
assert(phead);
DLNode* newnode = CreateNode(x);
DLNode* tail = phead->prev;//tail指向链表的尾
tail->next = newnode; //让尾指向新节点
newnode->prev = tail; //新节点的prev指向(old)尾
newnode->next = phead; //新节点的next指向phead
phead->prev = newnode; //phead的prev指向newnode
}
尾插的思维图
在插入newnode前
插入让其首尾相连后
(2)尾删:
思路:先找到链表的尾和尾的前一个
void DLPopBack(DLNode* phead)
{
assert(phead);
assert(phead->next != phead);//判断链表是否为空
DLNode* tail = phead->prev; //找到链表的尾
DLNode* tailPrev = tail->prev;//找到链表尾的前一个
tailPrev->next = phead; //(new)尾的next指向phead
phead->prev = tailPrev;//phead的prev指向(new)尾
free(tail);
}
尾删的思维图
原链表
删除后:
4.双向循环链表的头插头删
(1)头插:
画图思维实现:
第一种:
代码实现:
void DLPushFront(DLNode* phead,DLDataType x)
{
assert(phead);
DLNode* newnode = CreateNode(x);
//使第一个节点与新节点链接
newnode->next = phead->next;
phead->next->prev=newnode;
//使头节点与新节点链接
phead->next = newnode;
newnode->prev = phead;
}
第二种:
无关顺序
代码实现:
void DLPushFront(DLNode* phead,DLDataType x)
{
assert(phead);
DLNode* newnode = CreateNode(x);
//让first拿到第一个节点
DLNode* first = phead->next;
//phead newnode first 三指针纯粹的链接关系
//顺序无关
//哨兵位phead与新节点的链接
phead->next = newnode;
newnode->prev = phead;
//新节点与第一个节点的链接
newnode->next = first;
first->prev = newnode;
}
(2)头删:
思维画图实现:
代码实现:
void DLPopFront(DLNode* phead)
{
assert(phead);
assert(phead->next != phead);
DLNode* first = phead->next;
DLNode* second = first->next;
free(first);
phead->next = second;
second->prev = phead;
}
5.双向循环链表的查找和Print
(1)查找
查找代码实现:
DLNode* DLFind(DLNode* phead,DLDataType x)
{
assert(phead);
DLNode* cur = phead->next;
while(cur != phead)
{
if(cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
(2)Print
Print代码实现:
void DLPrint(DLNode* phead)
{
assert(phead);
DLNode* cur = phead->next;
while(cur != phead)
{
printf("%d ",cur->data);
cur = cur->next //向后进
}
printf("\n");
}
6.双向循环链表在任意位置之前插入和删除任意位置的元素
(1)插入:
思维画图实现:
代码实现:
void DLInsert(DLNode* pos,DLDataType x)
{
assert(pos);
DLNode* prev = pos->prev;
DLNode* newnode = CreateNode(x);
// prev newnode pos 三指针链接
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
由上的代码 我们可以优化尾插与头插
直接复用:
//尾插
void DLPushBack(DLNode* phead,DLDataType x)
{
assert(phead);
DLInsert(phead,x);//此时Insert的pos与phead在同一位置。
}
//头插
void DLPushFront(DLNode* phead,DLDataType x)
{
assert(phead);
DLInsert(phead->next,x);
}
(2)删除:
思维画图实现:
代码实现:
void DLErase(DLNode* pos)
{
assert(pos);
DLNode* prev = pos->prev;
DLNode* next = pos->next;
free(pos); //删除pos位置
prev->next = next;
next->prev = prev;
}
由上的代码 我们可以优化尾删与头删
直接复用:
//尾删
void DLPopBack(DLNode* phead)
{
assert(phead);
assert(phead->next != phead);//判断链表是否为空
DLErase(phead-prev);
}
//头删
void DLPopFront(DLNode* phead)
{
assert(phead);
assert(phead->next != phead);
DLErase(phead->next);
}
7.双向循环链表的判空,长度和销毁
(1)判空
判空代码实现:
bool LTEmpty(DLNode* phead)
{
assert(phead);
//当哨兵位的next为自己,开始循环时,则链表为空
return phead->next == phead;
}
(2)长度(size)
长度代码实现:
size_t DLSize(DLNode* phead)
{
assert(phead);
DLNode* cur = phead->next;
while(cur != phead)
{
size++;
cur = cur->next;
}
return size;
}
(3)链表的销毁
销毁链表的代码实现:
void DLDestroy(DLNode* phead)
{
assert(phead);
DLNode* cur = phead->next;//拿到第一个节点位置
while(cur != phead)
{
DLNode* next = cur->next;//记录其下一个位置
free(cur);
cur = next;
}
free(phead);
}
8.总结
双向循环链表相较于单链表更为复杂,但是上手却更为简单。
文章若有遗漏和错误,请多多指出,Thanks!