文章目录
带头循环双向链表
1. 介绍
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
2. 带头循环双向链表的项目实现
文件名 | 功能 |
---|---|
List.h | 创建双链表,完成函数名的声明 |
List.c | 实现双链表的各个功能函数 |
test.c | 测试双链表函数的正确性 |
2.1 双链表结构定义
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
2.2 动态申请一个结点
LTNode* BuyListNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
2.3 初始化双链表
初始化双链表其实就是在创建哨兵位的头结点
需要改变phead,因为实参是一个一级指针,如果形参也只是一级指针,就属于值传递了,函数内pphead的值改变并不会影响外面的实参。而初始化是需要修改实参的,这里介绍两种方法: 1. 用二级指针 2. 采用返回值
这里使用返回值
//初始化 ---> 创建哨兵位的头结点
LTNode* LTInit()
{
LTNode* phead = BuyListNode(-1); //创建带哨兵位的头结点, 其存储数据随便给
phead->prev = phead;
phead->next = phead;
return phead;
}
2.4 打印双链表
while循环遍历打印,遇到哨兵位头结点不打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
2.5 双链表尾插
原版
定义一个尾结点(哨兵位头结点的prev), 开辟新结点newnode, 将newnode链接到tail后面并链接好哨兵位头结点
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead); //链表中还有哨兵位的头结点不能删除
LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
复用版
将LTInsert中的第一个参数换成phead(在哨兵位头结点的前面尾插)
void LTPushBack(LTNode* phead, LTDataType x)
{
LTInsert(phead, x);
}
2.6 双链表尾删
原版
记录尾结点的上一个, 并将其与head结点链接起来, 最后释放掉tail结点
注意: 哨兵位头结点不能删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->prev != phead); //不能删掉哨兵位头结点
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
tailPrev->next = phead;
phead->prev = tailPrev;
free(tail);
}
复用版
将LTErase中的参数换成phead->prev(双链表的尾)
void LTPopBack(LTNode* phead)
{
LTErase(phead->prev);
}
2.7 双链表头插
原版
注意: 双链表的头结点是指哨兵位头结点的下一个
开辟一个新结点, 将此结点前后链接起来
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
LTNode* nextnode = phead->next;
nextnode->prev = newnode;
newnode->next = nextnode;
newnode->prev = phead;
phead->next = newnode;
}
复用版
将LTInsert中的第一个参数换成phead->next(双链表真正的头)
void LTPushFront(LTNode* phead, LTDataType x)
{
LTInsert(phead->next, x);
}
2.8 双链表头删
原版
记录头结点的下一个, 释放掉头结点, 将原头结点的下一个与head结点链接起来
注意: 哨兵位头结点不能删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->prev != phead); //不能删掉哨兵位头结点
LTNode* first = phead->next;
LTNode* second = first->next;
free(first);
second->prev = phead;
phead->next = second;
}
复用版
将LTErase中的参数换成phead->next(双链表真正的头)
void LTPopFront(LTNode* phead)
{
LTErase(phead->next);
}
2.9 查找双链表中的元素
遍历查找双链表中的元素,找到返回此结点处的指针,找不到返回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;
}
2.10 双链表在pos位置前插入元素
调用LTFind函数查找到pos位置, 开辟新结点, 将其链接起来
注意: 判断pos位置的合法性
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos); //pos位置的合法性
LTNode* newnode = BuyListNode(x);
LTNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
2.11 双链表删除pos位置的元素
调用LTFind函数查找到pos位置, 记录pos位置的前后结点, 释放pos位置, 并将原pos位置前后链接起来
注意: 判断pos位置的合法性
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* next = pos->next;
LTNode* prev = pos->prev;
free(pos);
prev->next = next;
next->prev = prev;
}
2.12 判断双链表是否为空
双链表为空是指, 链表中只剩下哨兵位的头结点
故判空操作,就是判断phead->prev == phead;
或phead->pnext == phead;
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->prev == phead;
}
2.13 计算双链表中结点个数
定义一个变量size, 遍历链表计算链表中结点个数并返回
size_t LTsize(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
size_t size = 0;
while (cur != phead)
{
size++;
cur = cur->next;
}
return size;
}
2.14 销毁双链表
先while循环释放除哨兵位以外其他结点, 最后再释放哨兵位头结点
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
//释放处哨兵位以外其他结点
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead); //释放哨兵位头结点
}