数据结构-带头双向循环链表


一.头结点

在链表中设置头结点的作用是什么

标识链表:头结点是链表的特殊节点,它的存在能够明确标识出这是一个链表。在链表中,头结点通常不包含任何数据,它的主要作用是作为链表的入口,使得链表的操作更加方便。
简化操作:头结点的存在可以简化链表的操作。例如,当我们需要遍历整个链表时,只需要从头结点开始即可,无需关心链表的起始位置。同时,头结点的存在也使得在链表末尾插入或删除节点等操作更加方便。
提高效率:头结点的存在可以提高链表操作的效率。由于头结点是链表的第一个节点,因此在遍历链表时,我们无需担心指针的移动方向问题。同时,由于头结点在链表中的特殊位置,当需要访问链表的第一个元素时,可以通过直接访问头结点来获取,无需遍历整个链表。

在这里插入图片描述

二.双链表

1·双链表的概念与结构

概念:双链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针双向链接次序实现的。

结构图:
在这里插入图片描述

2.与单链表相比

双向性:双链表支持在每个节点上存储前驱节点和后继节点的指针,使得在任何节点上都可以方便地找到其前驱节点和后继节点。而单链表只能通过遍历整个链表来查找特定节点的下一个节点或上一个节点,效率较低。
插入和删除操作更高效:由于双链表支持双向链接,因此在插入和删除操作中,双链表只需要重新调整相关的指针即可,而不需要像单链表那样需要遍历整个链表。这使得双链表的插入和删除操作更高效。

三.循环链表

1.关于循环链表

循环链表是一种特殊的链表,其中最后一个节点指向第一个节点,即起始节点。起始节点充当列表开头的参考点。

(1)遍历时,可以从任何节点开始并以任何方向向前或向后遍历列表,直到到达开始的同一节点。
(2)循环链表没有开始也没有结束。
(3)在循环链表中,最后一个节点地址部分保存第一个节点的地址,从而形成一个循环链状结构。

(4).结构图:
在这里插入图片描述

2.循环链表的优点

(1)内存利用率是循环链表的共同优势之一
与线性数据结构不同,循环链表可以让人有效地使用内存,因为链表的大小动态增加或减少,因此不会浪费内存。此外,无需预先分配内存。
(2)实施
由于能够利用内存和易于数据操作,像堆栈和队列这样的线性数据结构通常可以使用链表轻松实现。
(3)易于数据操作
可以有效地处理循环链表的插入和删除,而无需重新构造链表。插入或删除元素后无需移动元素,只需更新下一个指针中存在的地址。

四.带头双向循环链表

1.带头双向循环链表

结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

2.结构图

在这里插入图片描述

3.实现

思路:
(1)利用结构体存储数据和指针(结构体能够存储不同类型的数据)
(2)通过malloc函数进行扩容
(3)通过多条件实现
(4)我们实现初始化、打印、扩容、尾插、头插等函数
框架:
结构体的创建、一些定义等
List.h

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDataType;

typedef struct ListNode
{
    
    
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}ListNode;

Test.c

void Test() {
    
    
	ListNode* plist = ListInit()初始化,头结点不存储数据;
	}
int main() {
    
    
	Test();
	return 0;
}

函数实现:
(1)扩容函数:
返回值:一个结构体指针
参数:传来的数据
通过malloc函数实现扩容,并将扩容后的指针置空和存储传来的数据
代码:

ListNode* BuyListNode(LTDataType x) {
    
    //扩容
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));//扩容
	newnode->data = x;//存储数据
	newnode->next = NULL;//指针置空
	newnode->prev = NULL;
	return newnode;//返回指针
}

(2)初始化函数
我们开始要初始化一个头结点(不存储数据)
返回:一个指针(我们的头结点)
代码:

ListNode* ListInit() {
    
    //初始化
	ListNode* phead = BuyListNode(0);//第一个数据不打印
	phead->next = phead;//只有一个头结点,因为是双向循环链表,自己连接自己
	phead->prev = phead;
	return phead;
}

如图:
在这里插入图片描述
打印函数
代码:

void ListPrint(ListNode* phead) {
    
    
	assert(phead);//判断是否为空
	//初始条件
	ListNode* cat = phead->next;//打印出头结点next指向的结构体开始
	//结束条件
	while (cat!= phead)//当打印完一次后就会遇上同结点,此时结束打印
	{
    
    
		printf("%d ", cat->data);
		//迭代条件
		cat = cat->next;
	}
	printf("\n");
}

(3)尾插函数
我们要将一个数据尾插进去
第一步:找到这个链表的结尾处,我们可以通过头结点找到
第二步:我们重新将结尾处结构体的next指向尾插进来的结构体,再将尾插进来的结构体的prev指向结尾处的结构体,再再将尾插进来的结构体的next指向头结点,再再再将头结点的perv指向尾插进来的结构体
插前:
在这里插入图片描述
插后:
在这里插入图片描述
代码:

void ListPushBack(ListNode* phead, LTDataType x) {
    
    //尾插
	assert(phead);//判断是否为空
	ListNode* newnode = BuyListNode(x);//扩容
	ListNode* cat = phead->prev;//找到尾结点并保存
	//重新连接
	cat->next = newnode;
	newnode->prev = cat;
	phead->prev = newnode;
	newnode->next = phead;
}

检查:
尾插 1 、2 、3

ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);

运行结果:
在这里插入图片描述
(4)头插函数
我们要将一个数据尾插进去
第一步:通过头结点找到第一个存储数据的点
第二步:我们重新将第一存储数据的结点处结构体的prev指向头插进来的结构体,再将头插进来的结构体的next指向第一个存储数据的结构体,再再将头插进来的结构体的prtv指向头结点,再再再将头结点的next指向头插进来的结构体

插前:
在这里插入图片描述
插后:
在这里插入图片描述
代码:

void ListPushFront(ListNode* phead, LTDataType x) {
    
    //头插
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	ListNode* cat = phead->next;//找到头结点并保存
	//重新连接
	cat->prev = newnode;
	newnode->next = cat;
	phead->next = newnode;
	newnode->prev = phead;
}

检查:

头插 0
ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushFront(plist, 0);

运行结果:
在这里插入图片描述
(5)头删函数
我们想要删除第一个数据
第一步:通过头结点找到第一个存储数据的结点,再通过第一个存储数据的结点来找到第二个存储数据的结点
第二步:将第一个存储数据的结点释放掉,然后再将第二个存储数据的结点的prev指向头结点,再再头结点的next指向第二个存储数据的结点
删前:
在这里插入图片描述
删后:
在这里插入图片描述

代码:

void ListPopFront(ListNode* phead) {
    
    //头删
	assert(phead);
	assert(phead->next != phead);//
	ListNode* cat = phead->next->next;
	//通过第一个存储结点找到第二个存储的结点并保存
	free(phead->next);//释放掉
	phead->next = NULL;//并置空
	//重新连接
	cat->prev = phead;
	phead->next = cat;
	
}

检查:
头删一个数据

ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushFront(plist, 0);
ListPopFront(plist);

运行结果:

在这里插入图片描述
(6)尾删函数
我们将最后一个结点删除
第一步:通过头结点来找到最后一个结点,再通过最后一个结点来找到倒数第二个结点
第二步:将最后一个结点释放掉,再将倒数第二个结点的next指向头结点,再再将头结点的prev指向倒数第二个结点
删前:
在这里插入图片描述
删后:
在这里插入图片描述
检查
尾删一个数据

ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushFront(plist, 0);
ListPopFront(plist);
ListPopBack(plist);
ListPrint(plist);

运行结果:
在这里插入图片描述

五.代码一览

List.h:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDataType;

typedef struct ListNode
{
    
    
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}ListNode;

ListNode* ListInit();//初始化
void ListDestory(ListNode* phead);//清空
void ListPrint(ListNode* phead);//打印

void ListPushBack(ListNode* phead, LTDataType x);//尾插
void ListPushFront(ListNode* phead, LTDataType x);//头插
void ListPopFront(ListNode* phead);//头删
void ListPopBack(ListNode* phead);//尾删

List.c:

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"

ListNode* BuyListNode(LTDataType x) {
    
    //扩容
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

ListNode* ListInit() {
    
    //初始化
	ListNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

void ListPrint(ListNode* phead) {
    
    
	assert(phead);
	//初始条件
	ListNode* cat = phead->next;
	//结束条件
	while (cat!= phead)
	{
    
    
		printf("%d ", cat->data);
		//迭代条件
		cat = cat->next;
	}
	printf("\n");
}
void ListPushBack(ListNode* phead, LTDataType x) {
    
    //尾插
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	ListNode* cat = phead->prev;
	cat->next = newnode;
	newnode->prev = cat;
	phead->prev = newnode;
	newnode->next = phead;
}
void ListPushFront(ListNode* phead, LTDataType x) {
    
    //头插
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	ListNode* cat = phead->next;
	cat->prev = newnode;
	newnode->next = cat;
	phead->next = newnode;
	newnode->prev = phead;
}
void ListPopFront(ListNode* phead) {
    
    //头删
	assert(phead);
	assert(phead->next != phead);
	ListNode* cat = phead->next->next;
	free(phead->next);
	phead->next = NULL;
	cat->prev = phead;
	phead->next = cat;
	
}
void ListPopBack(ListNode* phead) {
    
    //尾删
	assert(phead);
	assert(phead->prev != phead);
	ListNode* cat = phead->prev->prev;
	free(phead->prev);
	phead->prev = NULL;
	cat->next = phead;
	phead->prev = cat;
}

Test.c

void Test() {
    
    
	ListNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushFront(plist, 0);
	ListPopFront(plist);
	ListPopBack(plist);
	ListPrint(plist);
	}

	int main() {
    
    
	Test();
	return 0;
}

以上就是全部代码了
还有一些查改等接口函数没实现,感兴趣的可以去试试

以上就是我的分享了,如果有什么错误,欢迎在评论区留言。
最后,谢谢大家的观看!

猜你喜欢

转载自blog.csdn.net/2302_79539362/article/details/134750330