文章目录
一、前言
链表是数据结构中十分重要的内容,我们知道链表的结构是非常多样的
当所有情况组合起来就有8种链表结构:
1.单向或者双向:
2.带头或者不带头:
3.循环或者非循环:
4.最常用的两种结构:
今天我们就来介绍优势更为明显的带头双向循环链表
在介绍带头双向循环链表之前呢,我们需要先来简单了解一下无头单向非循环链表,从图中我们也可以看出来,该结构较为简单。所以我们一般不会用其单独来存储数据。在实际应用中我们更多的是将其作为其他数据结构的子结构,如哈希桶、图的邻接表等等。在这里多提一句,这种结构在笔试面试中出现的概率是比较高的。
而我们今天要着重讲解的带头双向循环链表,它的结构相对来说是比较复杂的,一般用来单独存储数据。在实际使用链表数据结构,都是带头双向循环链表。虽然这个结构的结构较为复杂,但是在我们真正使用起来的时候,我们会发现它给我们带来了许多优势,反而实现起来更加简单了,这在后文讲解中会有所体现。
二、双向链表的实现
NOTE:老规矩,在这里我们先把所需要用的的库给到大家:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
①、定义节点
首先和单向链表一样,我们需要先定义一个节点,大致的实现都是相同的,但由于双向的结构,我们需要额外新增一个新的指针,因为我们需要一个指针指向前面一个指针指向后面,节点的定义具体如下:
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
②、创建新节点(BuyLTNode)
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail!");
exit(-1);
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
③、初始化链表(ListInit)
LTNode* ListInit()
{
LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
if (guard == NULL)
{
perror("malloc fail!");
exit(-1);
}
guard->next = guard;
guard->prev = guard;
return guard;
}
④、双向链表销毁(ListDestory)
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
⑤、双向链表打印(ListPrint)
void ListPrint(LTNode *phead)
{
assert(phead);
printf("phaed<=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
⑥、双向链表尾插(ListPushBack)
void ListPushBack(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;
}
⑦、双向链表头插(ListPushFront)
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
⑧、双链表尾删(ListPopBack)
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* tail = phead->prev;
LTNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;
}
⑨、双链表头删(ListPopFront)
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* first = phead->prev;
LTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
⑩、双向链表pos位置之前插入(ListInsert)
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
⑪、双向链表删除pos位置(ListEarse)
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
⑫、双向链表查找(ListFind)
LTNode* ListFind(LTNode* phead, LTDataType x)
{
assert(phead);
size_t n = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
三、总结
带头双向循环链表是一种较为复杂的就够,但由于其具有的循环特性,会使我们通过代码实现它时反而更为简单,双向链表这个结构的应用场景我们会在下一篇博客里通过oj题目为大家介绍,感谢大家的支持!
本文含有隐藏内容,请 开通VIP 后查看