文章目录
- 概要
- 整体架构流程
- 小结
概要
*数据结构中的链表在实际开发中应用非常广泛,但写一个链表并不是一件简单的事情。
链表有八种结构,对于刚入门的新手来说,往往会先经历单链表的折磨。
而今天我要讲的带头双向链表非常适合新手学习,它实现起来不像单链表那么繁琐,应用起来更加得心应手,最主要的是它能让我们更好的理解链表。*
整体架构流程
1.创建哨兵位
哨兵位介绍:带头链表俗称哨兵位,哨兵位的作用非常强大,它可以让我们进行头删头插时不使用双指针,增加代码可读性。往往哨兵位里的内容是没有实际意义的,因此也不算做有效节点。
**.h头文件代码**
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* prev;
struct ListNode* next;
}ListNode;
// 创建返回链表的头结点.(创建哨兵位)
ListNode* ListCreate();
**.c逻辑代码**
// 创建返回链表的头结点.(创建哨兵位)
ListNode* BuyHead(LTDataType x)
{
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
if (phead == NULL)
{
perror("malloc fial:");
}
phead->data = x;
phead->next = NULL;
phead->prev = NULL;
return phead;
}
ListNode* ListCreate()
{
ListNode* guard = BuyHead(-1);
guard->next = guard;
guard->prev = guard;
return guard;
}
2.双向链表销毁
**.h头文件**
// 双向链表销毁
void ListDestory(ListNode* pHead);
**.c逻辑代码**
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(pHead);
}
3.双向链表打印
**.h头文件**
// 双向链表打印
void ListPrint(ListNode* pHead);
**.c逻辑代码**
// 双向链表打印
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
printf("Guard<=>");
while (cur != pHead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
4.双向链表尾插
**.h头文件**
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
**.c逻辑代码**
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* tail = pHead->prev;
ListNode* Tailnext = BuyHead(x);
tail->next = Tailnext;
Tailnext->prev = tail;
Tailnext->next = pHead;
pHead->prev = Tailnext;
}
**5.双向链表尾删 **
.h头文件
// 双向链表尾删
void ListPopBack(ListNode* pHead);
.c逻辑代码
//用于判断链表中是否有节点
bool ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(!ListEmpty(pHead));
ListNode* tail = pHead->prev;
ListNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = pHead;
pHead->prev = tailPrev;
}
6.判断链表中是否有节点
.h文件
//用于判断链表中是否有节点
bool ListEmpty(ListNode* phead);
.c逻辑代码
//用于判断链表中是否有节点
bool ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead;
}
7.双向链表头插
.h文件
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
.c逻辑代码
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newhead = BuyHead(x);
ListNode* cur = pHead->next;
pHead->next = newhead;
newhead->prev = pHead;
newhead->next = cur;
cur->prev = newhead;
}
8.双向链表头删
.h文件
// 双向链表头删
void ListPopFront(ListNode* pHead);
.c逻辑代码
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(!ListEmpty(pHead));
ListNode* cur = pHead->next;
ListNode* headnext = cur->next;
pHead->next = headnext;
headnext->prev = pHead;
free(cur);
}
9.双向链表查找
.h文件
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
10.双向链表在pos的前面进行插入
.h文件
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
.c逻辑代码
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyHead(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
.c测试代码
//常和查找一起使用
ListNode* Find = ListFind(p,3);
if (Find)
{
ListInsert(Find, 6);
}
11.双向链表删除pos位置的节点
.h头文件
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
.c逻辑代码
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
assert(!ListEmpty(pos));
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
.c测试代码
ListNode* Find = ListFind(p,3);
if (Find)
{
LisErase(Find);
}
12.插入(头插、尾插)复用
分析序号10代码,我们可以得到该代码也适用于头插、尾插,因此我们可以将头插、尾插简化:
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
ListInsert(cur, x);
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* tail = pHead;
ListInsert(tail, x);
}
13.删除(头删、尾删)复用
分析序号11代码,我们可以得到该代码也适用于头删、尾删,因此我们可以将头删、尾删简化:
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
ListErase(cur);
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
ListNode* tail = pHead->prev;
ListErase(tail);
}
小结
带头双向链表的实现并不复杂,但需要我们对单链表有一定的了解程度,因此在学习带头双向链表前,最好先学习单链表,带头双向链表虽然被称为六边形战士,但我们也需要先学好基本知识,
再利用好带头双向链表,完全有可能做到十分钟实现链表,谢谢大家!