在上一篇博客中,详细介绍了单链表的增删查改,虽然单链表的结构简单,但是用起来却不是那么顺手。因此根据单链表的种种缺点,这篇博客所介绍的带头双向循环链表将会带来极大的优化。
上图就是带头双向循环链表的主要结构了,很多同学看到这个结构就想知难而退了,但是只要耐心深究,就会发现除了结构比单链表复杂以外,它的实现反而简单了!!!
目录
1 链表的结点结构
typedef struct DListNode
{
struct DListNode* next;
struct DListNode* prev;
DLTDataType val;
}DLTNode;
细心的同学肯定会发现,这次链表中的结构相比于单链表多了一个prev的结构体指针,有了它的 存在就能实现双向的功能了!
2 链表初始化
在初始化链表前我们需要创建一个哨兵卫的头结点,要创建结点,那依旧少不了结点创建函数。
DLTNode* BuyListNode(DLTDataType x)
{
DLTNode* newnode = (DLTNode*)malloc(sizeof(DLTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
有了结点创建函数,我们就可以开始对链表进行初始化了。
DLTNode* DLTInit()
{
DLTNode* phead = BuyListNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
这里要注意,因为我们的链表是循环的,所以链表最后是不指向空的,而是要循环回哨兵卫的头结点! 我们通过调试,也可以很清楚的看到他们的地址都是相同的。
3 链表打印
void DLTPrint(DLTNode* phead)
{
assert(phead);
DLTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->val);
cur = cur->next;
}
printf("\n");
}
打印链表非常简单,唯一需要注意的是,遍历结束的标志就是遍历的指针回到了哨兵卫的头结点!
4 链表的增删查改
4.1 链表尾插
void DLTPushBack(DLTNode* phead, DLTDataType x)
{
//因为带哨兵卫的头结点,链表不可能为空,若为空就出错了
assert(phead);
DLTNode* newnode = BuyListNode(x);
//找到尾结点
DLTNode* next = phead->prev;
newnode->next = phead;
phead->prev = newnode;
next->next = newnode;
newnode->prev = next;
}
在开始对链表进行增删查改的时候,双向带头循环链表的优势就开始体现出来了,就像这次的尾插,我们不需要一个结点一个结点的遍历过去,通过哨兵卫的上一个结点就能马上找到尾结点,然后在尾结点和哨兵卫之间直接插入就完事了!
4.2 链表尾删
void DLTPopBack(DLTNode* phead)
{
assert(phead);
//哨兵卫头结点不能删除
assert(phead->next != phead);
//找到尾结点和尾结点的前一个结点
DLTNode* next = phead->prev;
DLTNode* nextPrev = next->prev;
phead->prev = nextPrev;
nextPrev->next = phead;
free(next);
}
尾删也变得更加方便,依旧通过哨兵卫的上一个结点就能快速找到尾结点。
4.3 链表头插
void DLTPushFront(DLTNode* phead, DLTDataType x)
{
assert(phead);
DLTNode* newnode = BuyListNode(x);
DLTNode* cur = phead->next;
newnode->next = cur;
cur->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
双向带头循环链表的头插相比于单链表的头插也更为方便,由于哨兵卫的存在,我们只是改变的是结构体的内部,但是在单链表中却是要改变链表的头,就需要传二级指针或者需要改变返回值。
4.4 链表头删
void DLTPopFront(DLTNode* phead)
{
assert(phead);
assert(phead->next != phead);
DLTNode* first = phead->next;
DLTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
}
4.4 查找链表中的值
DLTNode* DLTFind(DLTNode* phead, DLTDataType x)
{
assert(phead);
DLTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
4.5 在对应位置前面插入
void DLTInsert(DLTNode* pos, DLTDataType x)
{
assert(pos);
DLTNode* newnode = BuyListNode(x);
DLTNode* posPrev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
posPrev->next = newnode;
newnode->prev = posPrev;
}
在单链表中,我们要在pos位置前插入结点,那必须遍历找到pos位置的前一个结点,而且还要重点考虑会不会是头插这种情况。在这里,我们就完全不需要考虑这么多,通过prev指针,可以很轻松的实现这个功能。
4.6 删除对应位置的结点
void DLTErase(DLTNode* pos)
{
assert(pos);
DLTNode* posPrev = pos->prev;
DLTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
5 链表销毁
void DLTDestroy(DLTNode* phead)
{
assert(phead);
DLTNode* cur = phead->next;
while (cur != phead)
{
DLTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
对于链表的销毁,我们依旧是要一步步来,要注意链表和顺序表不同,是不能直接free的!
6 完整代码及运行结果
//DList.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int DLTDataType;
typedef struct DListNode
{
struct DListNode* next;
struct DListNode* prev;
DLTDataType val;
}DLTNode;
DLTNode* BuyListNode(DLTDataType x);
//初始化
DLTNode* DLTInit();
//打印
void DLTPrint(DLTNode* phead);
//尾插
void DLTPushBack(DLTNode* phead, DLTDataType x);
//尾删
void DLTPopBack(DLTNode* phead);
//头插
void DLTPushFront(DLTNode* phead, DLTDataType x);
//头删
void DLTPopFront(DLTNode* phead);
//查找
DLTNode* DLTFind(DLTNode* phead, DLTDataType x);
//在pos位置前插入
void DLTInsert(DLTNode* pos, DLTDataType x);
//删除pos位置的结点
void DLTErase(DLTNode* pos);
//链表销毁
void DLTDestroy(DLTNode* phead);
//DList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"DList.h"
DLTNode* BuyListNode(DLTDataType x)
{
DLTNode* newnode = (DLTNode*)malloc(sizeof(DLTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//初始化
DLTNode* DLTInit()
{
DLTNode* phead = BuyListNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
//打印
void DLTPrint(DLTNode* phead)
{
assert(phead);
DLTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->val);
cur = cur->next;
}
printf("\n");
}
//尾插
void DLTPushBack(DLTNode* phead, DLTDataType x)
{
//因为带哨兵卫的头结点,链表不可能为空,若为空就出错了
assert(phead);
DLTNode* newnode = BuyListNode(x);
//找到尾结点
DLTNode* next = phead->prev;
newnode->next = phead;
phead->prev = newnode;
next->next = newnode;
newnode->prev = next;
}
//尾删
void DLTPopBack(DLTNode* phead)
{
assert(phead);
//哨兵卫头结点不能删除
assert(phead->next != phead);
//找到尾结点和尾结点的前一个结点
DLTNode* next = phead->prev;
DLTNode* nextPrev = next->prev;
phead->prev = nextPrev;
nextPrev->next = phead;
free(next);
}
//头插
void DLTPushFront(DLTNode* phead, DLTDataType x)
{
assert(phead);
DLTNode* newnode = BuyListNode(x);
DLTNode* cur = phead->next;
newnode->next = cur;
cur->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
//DLTInsert(phead->next, x); //复用
}
//头删
void DLTPopFront(DLTNode* phead)
{
assert(phead);
assert(phead->next != phead);
DLTNode* first = phead->next;
DLTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
}
//查找
DLTNode* DLTFind(DLTNode* phead, DLTDataType x)
{
assert(phead);
DLTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//在pos位置前插入-- 注意pos位置不能是哨兵卫的头结点 C++中会改善这一点
void DLTInsert(DLTNode* pos, DLTDataType x)
{
assert(pos);
DLTNode* newnode = BuyListNode(x);
DLTNode* posPrev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
posPrev->next = newnode;
newnode->prev = posPrev;
}
//删除pos位置的结点-- 注意pos位置不能是哨兵卫的头结点 C++中会改善这一点
void DLTErase(DLTNode* pos)
{
assert(pos);
DLTNode* posPrev = pos->prev;
DLTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
//链表销毁
void DLTDestroy(DLTNode* phead)
{
assert(phead);
DLTNode* cur = phead->next;
while (cur != phead)
{
DLTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}