今天船船计划用简单的方式向大家介绍数据结构中重要知识点——带头双向循环列表
目录
1、链表的概念
2、链表的基本分类
2.1分类
2.2 常用链表
2.3常用链表的分析?
3.?带头双向循环链表的实现
3.1头文件包含
3.2用结构体构造单位结点
3.3定义相应函数?
3.4实现相应函数?
3.4.1创建哨兵位+创建链表头节点
3.4.2链表输出函数
3.4.3实现在pos位置之前插入x、删除pos位置函数
3.4.4尾插、头插?
3.4.5?尾删、头删
4.结语
1、链表的概念
我们必须要明确的一件事,要想学习双向带头循环列表我们首先需要了解链表的基本概念和它的几种基本分类
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。
下面我将用图象化的形式向您展示链表的概念
?从上图观察我们可以得出一些结论
- 链式结构逻辑上连续,但是物理上不一定连续;
- 链表中的结点一般都是堆上申请的;
- 从堆上申请的空间一般都是按照一定策略分配的,申请的空间可能连续也可能不连续;
2、链表的基本分类
2.1分类
链表的分类有:单向和多向、带头和不带头、循环和不循环,通过排列组合共有8种形式;
1.单向或多向
?2.带头或不带头
3.循环或不循环
2.2 常用链表
实际中我们常用的链表有两种:无头单向非循环列表、带头双向循环链表?
2.3常用链表的分析?
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是由于其本身的优势,实现的过程会变得十分简单。
3.?带头双向循环链表的实现
3.1头文件包含
首先先写出链表实现过程中所需要用的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
3.2用结构体构造单位结点
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
3.3定义相应函数?
//创建链表的头结点
LTNode* ListInit();
//双向链表打印
void ListPrint(LTNode* phead);
//双向链表尾插
void ListPushBack(LTNode* phead, LTDataType x);
//双向链表头插
void ListPushFront(LTNode* phead, LTDataType x);
//双向链表尾删
void ListPopBack(LTNode* phead);
//双向链表头删
void ListPopFront(LTNode* phead);
// 在pos位置之前插入x
void ListInsert(LTNode* pos, LTDataType x);
// 删除pos位置的节点
void ListErase(LTNode* pos);
3.4实现相应函数?
3.4.1创建哨兵位+创建链表头节点
//创建哨兵位
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)//防止指针为空
{
perror("malloc fail");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
?prev指向的是结点前一个结点、next指向的是该结点的下一个结点?
//初始化,设置带头一个双向循环列表
LTNode* ListInit()
{
LTNode* phead = BuyListNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
3.4.2链表输出函数
void ListPrint(LTNode* phead)
{
assert(phead);//检查phead是否为空指针
LTNode* cur = phead->next;//定义一个指向哨兵位后一位结点的指针,从此处开始遍历
while (cur != phead)//因为本链表结构循环,因此在指针返回头节点式前需要继续遍历
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
3.4.3实现在pos位置之前插入x、删除pos位置函数
得益于双向带头循环链表的特性,可以通过实现上述两个函数,轻易实现尾插、头插等功能;
?prev指向的是结点前一个结点、next指向的是该结点的下一个结点
// 在pos位置之前插入x
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;//先找到结点的前一位
LTNode* newnode = BuyListNode(x);//申请一个新的结点
// prve newnode pos
prev->next = newnode;//将新结点链接到链表上
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
??
// 删除pos位置的节点
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;//找到原结点的前一位
LTNode* next = pos->next;找到原结点的后一位
prev->next = next;
next->prev = prev;
free(pos);//释放pos位置的空间
}
3.4.4尾插、头插?
//尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead, x);
}
//头插
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead->next, x);
}
3.4.5?尾删、头删
//尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//防止只有单个结点
ListErase(phead->prev);//函数传入头指针(哨兵位),头指针的prev就是尾结点
}
//头删
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//防止只有单个结点
ListErase(phead->next);//删除头结点的next,也就是链表的第一个结点(头删)
}
4.结语
看到这里,相信老铁们对如何实现带头双向循环列表相应的了解。
我是计算机海洋的新进船长Captain_ldx,如果我的文章能对您有帮助的话,麻烦各位观众姥爷们点赞、收藏、关注,你们的每一次举手之劳都将化为船长的前进动力!
|