文章目录
前言
前面已经实现了单链表,先来回顾一下链表,链表是物理结构上不一定连续,逻辑结构上连续的一种数据结构。今天要实现的是带哨兵位的双向循环链表。简单介绍一下,我们先定义它的结构。既然它要实现双向循环。那每个节点就需要跟它的前后节点有联系。然后就是这个哨兵位的节点,当链表为空时。哨兵位的节点也是存在的。那接下来就一起看看它的接口函数的实现吧。
一、带哨兵位的双向循环链表的接口函数
1.0 定义双向带哨兵位的循环链表的节点
//定义链表的数据类型
typedef int TLDatatype;
//定义节点
typedef struct TListNode
{
struct TListNode* prev;
struct TListNode* next;
TLDatatype data;
}TLNode;
1.1 初始化和销毁
1.1.1 初始化
//初始化
TLNode* TLInit()
{
//当链表为空时,哨兵位节点也依然存在,所以一开始就要有哨兵位节点。
TLNode* phead = (TLNode*)malloc(sizeof(TLNode));
if (phead == NULL)
{
perror("malloc fail");
exit(-1);
}
//要实现循环,所以哨兵位节点的指针要指向自己
phead->next = phead;
phead->prev = phead;
phead->data = 0;
return phead;
}
1.1.2 销毁
//让cur从哨兵位节点的后一个节点开始遍历,当cur走到哨兵位节点的位置,就能销毁除了哨兵位节点的其他节点
void TLDestory(TLNode* phead)
{
//phead cur next
TLNode* cur = phead->next;
TLNode* next = cur->next;
while (cur != phead)
{
free(cur);
cur = next;
next = next->next;
}
//销毁哨兵位节点
phead->prev=NULL;
phead->next=NULL;
free(phead);
}
1.2 尾插和尾删
1.2.1 尾插
//创建新节点
```c
TLNode* BuyNewNode(TLDatatype x)
{
TLNode* newnode = (TLNode*)malloc(sizeof(TLNode));
if (newnode == NULL)
{
perror("BuyNewNode malloc fail");
return NULL;
}
newnode->next = NULL;
newnode->prev = NULL;
newnode->data = x;
return newnode;
}
//尾插
void TLPushBack(TLNode* phead, TLDatatype x)
{
assert(phead);
//创建新节点
TLNode* newnode = BuyNewNode(x);
//链接节点,头节点的prev要指向最后一个节点,最后一个节点的next要指向phead。
// phead tail newnode
TLNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
1.2.2 尾删
//判断链表是否空
bool TLCheckEmpty(TLNode* phead)
{
//如果链表的前一个节点等于哨兵位节点,说明为空
//如果它们相等,则返回true,说明链表为空。
//如果它们不相等,则返回false,说明链表不为空。
return phead->prev == phead;
}
void TLPopBack(TLNode* phead)
{
//phead是指向哨兵位节点的。它为NULL,说明哨兵位节点不存在,所以正常情况phead不可能为NULL。
assert(phead);
//链表为空,返回true,assert里表达式为假就会报错
assert(!TLCheckEmpty(phead));
//phead tailprev tail
TLNode* tail = phead->prev;
TLNode* tailprev = tail->prev;
tailprev->next = phead;
phead->prev = tailprev;
free(tail);
tail = NULL;
}
1.3 头插和头删
1.3.1 头插
void TLPushFront(TLNode* phead, TLDatatype x)
{
assert(phead);
TLNode* first = phead->next;
TLNode* newnode = BuyNewNode(x);
// phead newnode first
newnode->next = first;
first->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
1.3.2 头删
void TLPopFront(TLNode* phead)
{
assert(phead);
assert(!TLCheckEmpty(phead));
// phead frist fnext
TLNode* frist = phead->next;
TLNode* fnext = frist->next;
phead->next = fnext;
fnext->prev = phead;
free(frist);
frist = NULL;
}
1.4 在pos前插入和删除
1.4.1 在pos前插入
void TLInsert(TLNode* pos, TLDatatype x)
{
assert(pos);
//posprev newnode pos
TLNode* newnode = BuyNewNode(x);
TLNode* posprev = pos->prev;
newnode->next = pos;
newnode->prev = posprev;
posprev->next = newnode;
pos->prev = newnode;
}
1.4.2 在pos前删除
void TLErase(TLNode* pos)
{
assert(pos);
//prev cur pos
TLNode* cur = pos->prev;
TLNode* prev = cur->prev;
prev->next = pos;
pos->prev = prev;
free(cur);
cur = NULL;
}
1.4.3 复用TLInsert和TLErase实现尾插/删和头插/删
实现了在pos前插入和删除的接口函数。那尾插不就是在phead前插入数据,头插就是在phead->next前插入数据。
那么尾插和头插就能这样写了。尾删和头删也能复用。
//尾插
void TLPushBack(TLNode* phead, TLDatatype x)
{
assert(phead);
TLInsert(phead,x);
}
//尾删
void TLPopBack(TLNode* phead)
{
assert(phead);
//链表为空,返回true,
assert(!TLCheckEmpty(phead));
TLErase(phead);
}
//头插
void TLPushFront(TLNode* phead, TLDatatype x)
{
assert(phead);
TLInsert(phead->next, x);
}
//头删
void TLPopFront(TLNode* phead)
{
assert(phead);
assert(!TLCheckEmpty(phead));
TLErase(phead->next->next);
}
1.5 查找和打印
1.5.1 查找
//查找
TLNode* TLFind(TLNode* phead, TLDatatype x)
{
TLNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
else
cur = cur->next;
}
return NULL;
}
1.5.2 打印
//打印
void TLPrint(TLNode* phead)
{
assert(phead);
TLNode* cur = phead->next;
printf("<=");
while (cur != phead)
{
printf(" %d <=>", cur->data);
cur = cur->next;
}
printf("\n");
}
二、链表和顺序表的对比
- 顺序表能在它的空间里随机访问,而链表不能。
- 顺序表在物理结构上连续,链表不一定连续。
- 顺序表在插入和删除要挪动数据。链表不需要,它只用改指针。
- 动态顺序表的容量需要扩容。链表不需要。
三、单链表和带哨兵位的双向循环链表的对比
- 单链表的结构相对简单。但它不常用单独存储数据。它的尾插和尾删的时间复杂度都为O(n)。它的头插和头删时间复杂度为O(1)。
- 带哨兵位的双向循环链表的结构是链表中最复杂的。它一般可以用来单独存储数据。它的尾插、尾删、头插、头删的时间复杂度都是O(1)。