目录
1.简介
带头双向循环链表:结构在链表中最为复杂,一般用来单独存放数据,实际中使用最多的链表结构。但是其只是结构复杂,使用起来其实比较简便。
2.源码分享
1. 带头双向循环链表源码 DList · 斯文/Date Stucture - 码云 - 开源中国 (gitee.com)
2. 不带头单向链表 博客(11条消息) 单链表详细解析,详细图解加代码实现,轻松拿捏。_杨斯文。的博客-CSDN博客
源码: Single linked list/Single linked list · 斯文/Date Stucture - 码云 - 开源中国 (gitee.com)
3.链表模拟实现
3.1 动态申请一个节点
typedef int DListdatatype;
typedef struct DListNode
{
DListdatatype data;
struct DListNode* prev;
struct DListNode* next;
}DListNode;
由于我们需要做到双向循环,我们定义的结构体变量中需要两个指针,一个用来指向下一个,一个指向前一个。
// 创造节点
DListNode* BuyListNode(DListdatatype x)
{
DListNode* Node = (DListNode*)malloc(sizeof(DListNode));
Node->data = x;
Node->next = NULL;
Node->prev = NULL;
return Node;
}
动态申请节点,返回申请的节点地址,将不确定的指针都先置空,避免出现野指针。
3.2初始化链表,创建头节点
// 初始化头结点
DListNode* ListInit()
{
DListNode * head = BuyListNode(0);
head->next = head;
head->prev = head;
}
创造的头节点,并不使用数据,括号中的值可以随意输入。
3.3实现尾插
// 尾插
void PushBack(DListNode* p, DListdatatype x)
{
assert(p);
DListNode* newnode = BuyListNode(x);
DListNode* tail = p -> prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = p;
p->prev = newnode;
ListInsert(p, x); 利用pos前插入函数
}
其实函数的实现是比较简单的,记得处理好指针细节。
3.4 实现头插
// 头插
void ListPushFront(DListNode* p, DListdatatype x)
{
assert(p);
DListNode* newnode = BuyListNode(x);
DListNode* cur = p->next;
newnode->next = cur;
newnode->prev = p;
cur->prev = newnode;
p->next = newnode;
//ListInsert(p->next, x);
}
比较简单,不多做分析,处理好细节就可以了。
3.5 实现头删
// 头删
void ListPopFront(DListNode* p)
{
assert(p);
if (p->next == p)
{
printf("链表已空\n");
return;
}
else
{
DListNode* next = p->next;
p->next = next->next;
next->next->prev = p;
free(next);
}
/*ListErase(p->next);*/// 需要多判断一步是否为空,不空再删
}
3.6 尾删
// 尾删
void ListPopBack(DListNode* p)
{
assert(p);
if (p->next == p)
{
printf("链表已空\n");
return;
}
else
{
DListNode* tail = p->prev;
tail->prev->next = p;
p->prev = tail->prev;
free(tail);
}
/* ListErase(p->prev); */ // 需要多判断一步是否为空,不空再删
}
3.7打印链表
// 打印
void ListPrint(DListNode* p)
{
DListNode* cur = p->next;
while (cur != p)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
由于是循环链表,打印链表和后续的查找函数的循环参数的判断条件就不可以是 NULL,我们取头节点后的第一个节点,遍历节点,循环时,等于头节点,即遍历了一遍链表,跳出循环。
3.8实现查找
//查找 x, 返回地址
DListNode* ListFind(DListNode* p, DListdatatype x)
{
assert(p);
DListNode* cur = p->next;
while (cur != p)
{
if (cur->data == x)
{
printf("找到了\n");
return cur;
}
cur = cur->next;
}
printf("没找到\n");
return NULL;
}
3.9 实现pos位置前插入数据
// 在pos 位置前插入值
void ListInsert(DListNode* pos, DListdatatype x)
{
DListNode* newnode = BuyListNode(x);
DListNode* prev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
prev->next = newnode;
newnode->prev = prev;
}
注意pos后一个节点,注意替换顺序,避免丢失节点。
同时,我们可以使用这个函数来实现 ,头插,尾插
头插 : ListInsert(p->next, x); 在第一个数据节点前再插入新节点。
尾插:ListInsert(p, x); 利用pos前插入函数
尾插复杂一些
可以理解为,在头节点前插入一个节点,等于尾插。
3.10 删除 pos 位置的节点
// 删除pos 位置的 节点
void ListErase(DListNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
}
将pos 的前后节点链接到一起。
同理,可以使用这个函数实现头删,尾删
头删 :ListErase( p ->next) ;
尾删 : ListErase( p->prev);
3.11 销毁链表
// 销毁链表
void ListDestroy(DListNode* p)
{
assert(p);
DListNode* cur = p->next;
while (cur != p)
{
DListNode* next = cur->next;
free(cur);
cur = next;
}
free(p);
}
需要注意循环链表的遍历条件 ,逐步销毁。
本篇博客到此结束,谢谢观看!