手撕双链表

> 作者简介:დ旧言~,目前大一,现在学习Java,c,c++,Python等

> 座右铭:松树千年终是朽,槿花一日自为荣。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕

🌟前言

前面我们已经学习了顺序表和单链表,顺序表可以存储动态的数据,但是一旦元素过少,而又要开辟空间,这样就造成空间的浪费,而单链表以节点为单位存储,不支持随机访问,只能从头到尾遍历访问,为了解决上面两个问题,人们发现了双链表,把一个一个元素以链子的形式存储,可以存储相互的地址,那双链表如何实现呢,今天咱们就实现一下--《双链表》。

🌙主体

咱们从三个方面实现双链表,动态管理,头插头删尾插尾删,增删查改。

在程序中为了实现双链表,需要创建头文件List.h ,创建源文件Test.c,List.c。

🌠动态管理

💤初始化动态双链表

既然实现双链表,初始化动态的双链表必不可少,从两个方面实现初始化动态的双链表。

1.首先我们在List.h定义动态的双链表,省得我们再定义节点(双链表)。

cpp 复制代码
//定义数据类型
typedef int LTDataType;
//定义双链表初始化
typedef struct ListNode
{
	//上一个
	struct ListNode* next;
	//下一个
	struct ListNode* prev;
	LTDataType data;
}LTNode;

2.对双链表进行初始化

我们要明白,这里不像单链表一样,形成节点就行,还需要初始化。

💦这里采用malloc开辟空间

💦采用预指令判断空间是否开辟完成(没有开辟空间返回-1)

💦之后就是简单的初始数据

💦记得返回值

cpp 复制代码
//初始化
LTNode* BuyLTNode(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;
}

//形成双链表
LTNode* LTInit()
{
	//使头为0
	LTNode* phead = BuyLTNode(0);
	//构成循环
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

💤释放双链表内存

双链表的内存释放与单链表的内存释放有一定的区别,这里我们分开两类,清理与销毁

清理的代码如下:

cpp 复制代码
//清理
void ListClear(LTNode* phead)
{
	//断言
	assert(phead);
	
	//清理全部数据,保留头结点
	LTNode* cur = phead->next;
	
	//循环销毁
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
}

销毁的代码如下:

cpp 复制代码
//销毁
void ListDestory(LTNode** pphead)
{
	//断言
	assert(*pphead);

	//调用清理(函数)
	ListClear(*pphead);
	
	//释放内存
	free(*pphead);
	*pphead = NULL;
}

🌠头插头删尾插尾删

💤打印元素

打印元素就太简单了,直接上代码

cpp 复制代码
//打印数据
void LTPrint(LTNode* phead)
{
	//断言
	assert(phead);

	printf("phead<=>");
	
	LTNode* cur = phead->next;
	//注意循环的结束的语句
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	
	printf("\n");
}

💤尾插(重点)

有了这个图就对指向的改变就轻轻松松

cpp 复制代码
//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x)
{
	//断言
	assert(phead);

	LTNode* tail = phead->prev;
	
	//给添加的元素创建节点
	LTNode* newnode = BuyLTNode(x);

    //新开辟的元素下一个节点指向尾
	newnode->prev = tail;
	//尾的上一个节点指向新的元素
    tail->next = newnode;

    //新元素的上一个节点指向头
	newnode->next = phead;
    //头的下一节点指向新的元素节点
	phead->prev = newnode;

	//本质上与尾插相似 (双向链表在pos的前面进行插入)
	//LTInsert(phead, x);
}

💤尾删(重点)

双链表重在画图,希望小伙伴们能看得懂。

cpp 复制代码
//尾删
void LTPopBack(LTNode* phead)
{
	//断言
	assert(phead);
	//防止没有头指向没有元素
	assert(phead->next != phead);

	//找到尾
	LTNode* tail = phead->prev;
	//找到尾前面一个元素
	LTNode* tailPrev = tail->prev;
	
	//释放内存
	free(tail);

	//(把尾的上一个元素)的上一个指针 指向头
	tailPrev->next = phead;
	//头的下一个指针指向尾的前一个元素
	phead->prev = tailPrev;

	// 双向链表删除pos位置的结点(本质上与尾删相似)
	//LTErase(phead->prev);
}

💤头插(重点)

博主在这里采用三种方法,希望大家至少学会一种

cpp 复制代码
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//方法一
	//初始化
	//LTNode* newnode = BuyLTNode(x);
	//newnode->next = phead->next;
	//phead->next->prev = newnode;
	//phead->next = newnode;
	//newnode->prev = phead;

	//方法二
	//初始化
	LTNode* newnode = BuyLTNode(x);
	LTNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;

	//方法三
	// 双向链表删除pos位置的结点(本质上就是头插)
	//LTInsert(phead->next, x);
}

💤头删(重点)

双链表会自带梢兵位(这个到后期博主会讲)

cpp 复制代码
//头删(重点)
void LTPopFront(LTNode* phead)
{
	//断言
	assert(phead);
	//头指向不能为空
	assert(phead->next != phead);

	//找到梢兵位的节点
	LTNode* first = phead->next;
	//找到头后面一个元素
	LTNode* second = first->next;

	//释放内存
	free(first);

	//梢兵位的节点指向头后面一个元素
	phead->next = second;
	//后面一个元素指向梢兵位的节点
	second->prev = phead;

	//双向链表删除pos位置的结点(本质上和头删一样)
	//LTErase(phead->next);
}

🌠增删查改

💤统计双链表元素个数

这个函数还是比较简单的,注意循环的停止条件。

cpp 复制代码
//统计双链表元素个数
int LTSize(LTNode* phead)
{
	//断言
	assert(phead);

	int size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

💤双向链表在pos的前面进行插入

这里大家就看图理解就行啦

cpp 复制代码
// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
	//断言
	assert(pos);

	LTNode* posPrev = pos->prev;
	//为插入的元素开辟空间
	LTNode* newnode = BuyLTNode(x);

	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

💤双向链表删除pos位置的结点

这里大家就看图理解就行啦

cpp 复制代码
// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
	//断言
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	//释放内存
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

🌙代码总结

🌠主函数

cpp 复制代码
//包含文件
#include"List.h"

void TestList1()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);
	LTPrint(plist);

	LTPushFront(plist, 10);
	LTPushBack(plist, 10);

	LTPrint(plist);
}

void TestList2()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);
	LTPrint(plist);

	LTPopBack(plist);
	//LTPopFront(plist);
	LTPrint(plist);

	//LTPopFront(plist);
	//LTPopFront(plist);
	//LTPopFront(plist);
	//LTPopFront(plist);
	LTPrint(plist);
}

//void TestList3()
//{
//	LTNode* plist = LTInit();
//	LTPushBack(plist, 1);
//	LTPushBack(plist, 2);
//	LTPushBack(plist, 3);
//	LTPushBack(plist, 4);
//	LTPushBack(plist, 5);
//	LTPrint(plist);
//
//	LTPushFront(plist, 10);
//	LTPushFront(plist, 20);
//	LTPushFront(plist, 30);
//	LTPushFront(plist, 40);
//	LTPrint(plist);
//}
//
//void TestList4()
//{
//	LTNode* plist = LTInit();
//	LTPushBack(plist, 1);
//	LTPushBack(plist, 2);
//	LTPushBack(plist, 3);
//	LTPushBack(plist, 4);
//	LTPushBack(plist, 5);
//	LTPrint(plist);
//
//	LTPopFront(plist);
//	LTPrint(plist);
//
//	LTPopBack(plist);
//	LTPrint(plist);
//}

int main()
{
	TestList1();

	return 0;
}

🌠List.h头文件

cpp 复制代码
//包含头文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

//定义数据类型
typedef int LTDataType;
//定义双链表初始化
typedef struct ListNode
{
	//上一个
	struct ListNode* next;
	//下一个
	struct ListNode* prev;
	LTDataType data;
}LTNode;

//初始化
LTNode* BuyLTNode(LTDataType x);

//形成双链表
LTNode* LTInit();

//打印数据
void LTPrint(LTNode* phead);

//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x);
//尾删(重点)
void LTPopBack(LTNode* phead);
//头插(重点)
void LTPushFront(LTNode* phead, LTDataType x);
//头删(重点)
void LTPopFront(LTNode* phead);

//统计双链表元素个数
int LTSize(LTNode* phead);
// 双向链表查找(在双链表中不合适)
LTNode* LTFind(LTNode* phead, LTDataType x);

// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的结点
void LTErase(LTNode* pos);

//清理
void ListClear(LTNode* phead);
//销毁
void ListDestory(LTNode** pphead);

🌠List.c源文件

cpp 复制代码
//包含文件
#include"List.h"

//初始化
LTNode* BuyLTNode(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;
}

//形成双链表
LTNode* LTInit()
{
	//使头为0
	LTNode* phead = BuyLTNode(0);
	//构成循环
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

//打印数据
void LTPrint(LTNode* phead)
{
	//断言
	assert(phead);

	printf("phead<=>");
	
	LTNode* cur = phead->next;
	//注意循环的结束的语句
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	
	printf("\n");
}

//尾插(重点)
void LTPushBack(LTNode* phead, LTDataType x)
{
	//断言
	assert(phead);

	LTNode* tail = phead->prev;

	//给添加的元素创建节点
	LTNode* newnode = BuyLTNode(x);

	//新开辟的元素下一个节点指向尾
	newnode->prev = tail;
	//尾的上一个节点指向新的元素
	tail->next = newnode;

	//新元素的上一个节点指向头
	newnode->next = phead;
	//头的下一节点指向新的元素节点
	phead->prev = newnode;

	//本质上与尾插相似 (双向链表在pos的前面进行插入)
	//LTInsert(phead, x);
}

//尾删
void LTPopBack(LTNode* phead)
{
	//断言
	assert(phead);
	//防止没有头指向没有元素
	assert(phead->next != phead);

	//找到尾
	LTNode* tail = phead->prev;
	//找到尾前面一个元素
	LTNode* tailPrev = tail->prev;
	
	//释放内存
	free(tail);

	//(把尾的上一个元素)的上一个指针 指向头
	tailPrev->next = phead;
	//头的下一个指针指向尾的前一个元素
	phead->prev = tailPrev;

	// 双向链表删除pos位置的结点(本质上与尾删相似)
	//LTErase(phead->prev);
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//方法一
	//初始化
	//LTNode* newnode = BuyLTNode(x);
	//newnode->next = phead->next;
	//phead->next->prev = newnode;
	//phead->next = newnode;
	//newnode->prev = phead;

	//方法二
	//初始化
	LTNode* newnode = BuyLTNode(x);
	LTNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;

	//方法三
	// 双向链表删除pos位置的结点(本质上就是头插)
	//LTInsert(phead->next, x);
}

//头删(重点)
void LTPopFront(LTNode* phead)
{
	//断言
	assert(phead);
	//头指向不能为空
	assert(phead->next != phead);

	//找到梢兵位的节点
	LTNode* first = phead->next;
	//找到头后面一个元素
	LTNode* second = first->next;

	//释放内存
	free(first);

	//梢兵位的节点指向头后面一个元素
	phead->next = second;
	//后面一个元素指向梢兵位的节点
	second->prev = phead;

	//双向链表删除pos位置的结点(本质上和头删一样)
	//LTErase(phead->next);
}

//统计双链表元素个数
int LTSize(LTNode* phead)
{
	//断言
	assert(phead);

	int size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

// 双向链表在pos的前面进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
	//断言
	assert(pos);

	LTNode* posPrev = pos->prev;
	//为插入的元素开辟空间
	LTNode* newnode = BuyLTNode(x);

	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
	//断言
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	//释放内存
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}


//清理
void ListClear(LTNode* phead)
{
	//断言
	assert(phead);
	
	//清理全部数据,保留头结点
	LTNode* cur = phead->next;
	
	//循环销毁
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
}

//销毁
void ListDestory(LTNode** pphead)
{
	//断言
	assert(*pphead);

	//调用清理(函数)
	ListClear(*pphead);
	
	//释放内存
	free(*pphead);
	*pphead = NULL;
}

🌟结束语

今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

相关推荐
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
weixin_432702262 小时前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
passer__jw7672 小时前
【LeetCode】【算法】283. 移动零
数据结构·算法·leetcode
爱吃生蚝的于勒3 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~3 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
脉牛杂德4 小时前
多项式加法——C语言
数据结构·c++·算法
一直学习永不止步4 小时前
LeetCode题练习与总结:赎金信--383
java·数据结构·算法·leetcode·字符串·哈希表·计数
wheeldown12 小时前
【数据结构】选择排序
数据结构·算法·排序算法
躺不平的理查德16 小时前
数据结构-链表【chapter1】【c语言版】
c语言·开发语言·数据结构·链表·visual studio
阿洵Rain16 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法