【数据结构】双链表

🍃 如果觉得本系列文章内容还不错,欢迎订阅🚩

🎊个人主页:小编的个人主页

🎀 🎉欢迎大家点赞👍收藏⭐文章

✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍

目录

🐼前言

🌟在上一节我们实现了单链表,如果感兴趣的小伙伴,可以阅读我的上一篇文章:> 单链表,这一节小编给大家介绍另一种常见的链表:带头双向循环链表

🐼双链表

✨链表组合起来有8种,包括带头不带头循环不循环双向单向

今天我们要介绍的是常用的一种带头双向循环链表

如图:

🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟

✨双向链表节点定义如下:

c 复制代码
typedef int   LTDataType;
//双链表的结点定义
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向前一个节点的指针
}LTNode;

🐼初始化双链表

c 复制代码
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror(" LTBuyNode()::fail");
		exit(-1);
	}
	node->data = x;
	node->next = node->prev = node;//自循环
	return node;
}
//初始化双链表
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;;
}

🌻代码解析

💫我们向堆上申请了一个节点的空间,这里封装了一个函数LTBuyNode()因为是自循环,初始将每个节点的next和prev指向自已。将返回值给头结点。即LTNode* plist = LTInit(); //创建双链表的头结点

💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量

💫由于不改变头结点,以下函数接口都用一级指针接收

🐼尾插

c 复制代码
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	//phead phead->prev newnode
	newnode->next = phead;
	newnode->prev = phead->prev;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

🌻代码解析

💫 插入操作需要申请新节点(newnode),先修改新节点(newnode)的next和prev指针,再修改尾结点(phead->prev)的指针,将尾结点指针((phead->prev)的next指向头结点(phead),将头结点(phead)的prev指向新节点(newnode)

💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量

🍂画图剖析:

🍀测试结果:

🐼打印双链表

c 复制代码
//打印双链表
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

🌻代码解析

💫pcur指向第一个有效节点,让pcur往后走,直到pcur走到头结点,结束。

💫时间复杂度为O(N),程序遍历一次,空间复杂度为O(1),只创建有效个变量

🐼判断双链表是否为空

c 复制代码
//判断双链表是否为空
bool LTEmpty(LTNode* phead)
{
	return (phead->next == phead);
}

🌻代码解析

通过布尔类型的返回值,如果头结点是自循环的,那么链表为空,返回true,否则,返回false

💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量

🐼尾删

c 复制代码
//尾删
void LTPopBack(LTNode* phead)
{
		assert(!LTEmpty(phead));
		LTNode* del = phead->prev;
		//phead del
		del->prev->next = phead;
		phead->prev = del->prev;
		free(del);
		del = NULL;
}

🌻代码解析

先断言判断链表是否为空,将链表尾结点记作del,更新链表倒数第二个节点(del->prev)和头结点(phead)的指向。最后将尾结点(del)释放

💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量

🍂画图剖析:

🍀测试结果:

🐼头插

c 复制代码
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	//phead newnode phead->next
	LTNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->prev = newnode;
	phead->next = newnode;
}

🌻代码解析

插入操作首先要申请新节点(newnode),先修改新节点(newnode)的指向,再修改头节点(phead)的前继指针和后继指针。

💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量

🍂画图剖析:

🍀测试结果:

🐼头删

c 复制代码
//头删
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));
	//phead del->next
	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
}

🌻代码解析

断言确保链表不为空,将头结点的第一个节点记作del,改变第二个有效节点(del->next)的前继指针prev和头结点的后继指针。

💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量

🍂画图剖析:

🍀测试结果:

🐼查找元素

c 复制代码
//查找元素
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(!LTEmpty(phead));
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

🌻代码解析

查找链表不能为空,将第一个有效节点记作pcur,pcur往后走,当pcur不为头结点时,循环继续,每一次循环都判断pcur->data == x,如果存在值,返回该节点;遍历结束,未找到,返回NULL

💫时间复杂度为O(N),程序遍历一次,空间复杂度为O(1),只创建有效个变量

🍀测试结果:

🐼在pos位置之后插⼊数据

c 复制代码
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x) {
	assert(pos);
	//pos newnode pos->next
	LTNode* newnode = LTBuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

🌻代码解析

插入操作首先要申请新节点,先改变新节点的前,后继指针,再修改pos->next的前继节点,和pos的后继节点。

💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量

🍂画图剖析:

🍀测试结果:

🐼删除指定位置

c 复制代码
//删除指定位置
void LTErase(LTNode* pos)
{
	assert(pos);
	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

🌻代码解析

修改的指针有pos->prev pos pos->next,将pos的前一个指针(pos->prev)的后继指针和pos的后一个节点(pos->next)的前继指针.

💫时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量

🍂画图剖析:

🍀测试结果:

🐼销毁双链表

c 复制代码
//销毁顺序表
void LTDestroy(LTNode* phead)
{
	assert(phead && !LTEmpty(phead));
	LTNode* pcur = phead->next;
	while (pcur!=phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//头结点
	free(phead);
	phead = NULL;
}

🌻代码解析

将第一个有效节点记作pcur,保存pcur的下一个节点,删除pcur节点,让pcur走到next,最后将头结点释放,

💫时间复杂度为O(N),程序遍历一次,空间复杂度为O(1),只创建有效个变量

🍂画图剖析:

🍀测试结果:

🐼全部源码

List.h

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int   LTDataType;
//双链表的结点定义
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;
}LTNode;


//void LTInit(LTNode** pphead);
//初始化双链表
LTNode* LTInit();
//销毁双链表
void LTDestroy(LTNode* phead);
//打印双链表
void LTPrint(LTNode* phead);
//判断双链表是否为空
bool LTEmpty(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x);
//删除指定位置
void LTErase(LTNode* pos);
//查找元素
LTNode* LTFind(LTNode* phead, LTDataType x);

List.c

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "Listnode.h"

LTNode* LTBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror(" LTBuyNode()::fail");
		exit(-1);
	}
	node->data = x;
	node->next = node->prev = node;//自循环
	return node;
}
//初始化双链表
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;;
}

//销毁顺序表
void LTDestroy(LTNode* phead)
{
	assert(phead && !LTEmpty(phead));
	LTNode* pcur = phead->next;
	while (pcur!=phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//头结点
	free(phead);
	phead = NULL;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	//phead phead->prev newnode
	newnode->next = phead;
	newnode->prev = phead->prev;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

//打印双链表
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

//判断双链表是否为空
bool LTEmpty(LTNode* phead)
{
	return (phead->next == phead);
}

//尾删
void LTPopBack(LTNode* phead)
{
		assert(!LTEmpty(phead));
		LTNode* del = phead->prev;
		//phead del
		del->prev->next = phead;
		phead->prev = del->prev;
		free(del);
		del = NULL;
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	//phead newnode phead->next
	LTNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->prev = newnode;
	phead->next = newnode;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));
	//phead del->next
	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;
}

//在pos位置之后插⼊数据
void LTInsert(LTNode* pos, LTDataType x) {
	assert(pos);
	//pos newnode pos->next
	LTNode* newnode = LTBuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

//查找元素
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(!LTEmpty(phead));
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

//删除指定位置
void LTErase(LTNode* pos)
{
	assert(pos);
	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

test.c

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS
#include "Listnode.h"

void test01()
{
	LTNode* plist = LTInit(); //创建双链表的头结点
	尾插
	//LTPushBack(plist, 1);
	//LTPushBack(plist, 2);
	//LTPushBack(plist, 3);
	//LTPushBack(plist, 4);
	//LTPrint(plist);
	尾删
	//LTPopBack(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);
	/*LTPopBack(plist);
	LTPrint(plist);*/
	//头插
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);
	//头删
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);
	/*LTNode* ret = LTFind(plist, 4);*/
	/*if (ret != NULL)
		printf("找到了\n");
	else
		printf("没找到\n");*/
	//在pos位置之后插⼊数据
	//LTInsert(ret, 99);
	//LTPrint(plist);
	//删除指定位置
	//LTErase(ret);
	//LTPrint(plist);
	//销毁顺序表
	LTDestroy(plist);


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

🐼文末

感谢你看到这里,如果觉得本篇文章对你有帮助,点个赞👍 吧,你的点赞就是我更新的最大动力 ⛅️🌈 ☀️

相关推荐
老猿讲编程1 小时前
安全C语言编码规范概述
c语言·开发语言·安全
OrangeJiuce1 小时前
【QT中的一些高级数据结构,持续更新中...】
数据结构·c++·qt
学编程的小程4 小时前
LeetCode216
算法·深度优先
leeyayai_xixihah4 小时前
2.21力扣-回溯组合
算法·leetcode·职场和发展
01_4 小时前
力扣hot100——相交,回文链表
算法·leetcode·链表·双指针
萌の鱼4 小时前
leetcode 2826. 将三个组排序
数据结构·c++·算法·leetcode
Buling_04 小时前
算法-哈希表篇08-四数之和
数据结构·算法·散列表
AllowM4 小时前
【LeetCode Hot100】除自身以外数组的乘积|左右乘积列表,Java实现!图解+代码,小白也能秒懂!
java·算法·leetcode
RAN_PAND5 小时前
STL介绍1:vector、pair、string、queue、map
开发语言·c++·算法
左灯右行的爱情7 小时前
Redis数据结构总结-listPack
数据结构·数据库·redis