手撕双链表

news2024/12/23 19:03:34

> 作者简介:დ旧言~,目前大一,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。
> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

 🌟前言       

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

🌙主体

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

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

 🌠动态管理

💤初始化动态双链表

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

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

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

2.对双链表进行初始化

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

💦这里采用malloc开辟空间

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

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

💦记得返回值

//初始化
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 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;
}

🌠头插头删尾插尾删

💤打印元素

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

//打印数据
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的前面进行插入

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

// 双向链表在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位置的结点

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

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

	//释放内存
	free(pos);

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

🌙代码总结

🌠主函数

//包含文件
#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头文件

//包含头文件
#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源文件



//包含文件
#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;
}

🌟结束语

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1015762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D视觉到三维视觉之结构光

3D视觉是计算机视觉的终极体现形式 2D视觉技术主要在二维空间下完成工作&#xff0c;三维信息基本上没有得到任何利用&#xff0c;而三维信息才真正能够反映物体和环境的状态&#xff0c;也更接近人类的感知模式。近年来&#xff0c;学术界和工业界推出了一系列优秀的算法和产…

【【萌新的RISC-V学习之再看计算机组成与设计大收获总六】】

萌新的RISC-V学习之再看计算机组成与设计大收获总六 我们在进行设计的时候首先要明白一点 就是 确定我们的设计所需要的 指令的大小和 地址的大小 指令集是32位的 而 地址则一般更多的是64位 数据也是64位 PC与指令寄存器之间的关系 PC是用来保存当前指令的地址。假设地址是0…

Huggingface遇到 Couldn‘t reach xxx on the Hub (ConnectionError)解决方法

文章目录 遇到的问题解决方法参考 遇到的问题 使用服务器下载Huggingface的数据集&#xff0c;显示ConnectionError: Couldn’t reach ‘Salesforce/dialogstudio’ on the Hub (ConnectionError) 具体代码如下&#xff1a; dataset load_dataset("Salesforce/dialogs…

Scanner类用法(学习笔记)

Scanner类用法&#xff08;学习笔记&#xff0c;后续会补充&#xff09; 1.next&#xff08;&#xff09;用法 package com.yushifu.scanner; import java.util.Scanner;//util java工具包 //Scanner类&#xff08;获取用户的输入&#xff09; Scanner s new Scanner&#…

Otter改造 增加springboot模块和HTTP调用功能

环境搭建 & 打包 环境搭建&#xff1a; 进入 $otter_home/lib 目录执行&#xff1a;bash install.sh 打包&#xff1a; 进入$otter_home目录执行&#xff1a;mvn clean install -Dmaven.test.skip -Denvrelease发布包位置&#xff1a;$otter_home/target 项目背景 阿里…

【量化交易】151个量化交易策略解析

我又来推书了~ 今天分享的这本书&#xff0c;量化交易领域的同学们肯定喜欢&#xff08;doge&#xff09;&#xff0c;它就是Zura Kakushadze大佬&#xff08;“Alpha101”作者&#xff09;撰写的《151 Trading Strategies》。&#xff08;文末领&#xff09; 《151 Trading …

创建对象内存分析

package com.mypackage.oop.demo03;public class Pet {String name;int age;//无参构造public void shout(){System.out.println("叫了一声");} }package com.mypackage.oop.demo03;public class Application03 {public static void main(String[] args) {Pet dog n…

vue中el-dialog 中的内容没有预先加载,因此无法获得内部元素的ref 的解决方案 使用强制提前加载dialog方法

问题描述 在没有进行任何操作的时候&#xff0c;使用 this.$refs.xxxx 无法获取el-dialog中的内部元素&#xff0c;这个问题会导致很多bug&#xff0c;其中目前网络上也有许多关于这个问题的解决方案&#xff0c;但是大多数是使用el-dialog中的open在dialog打开的时候使用thi…

typescript类型详解

typescript类型概述 typescript是JavaScript的超集,ts提供了js所有的功能.并且额外增加了:类型系统 所有的js代码都是ts代码js有类型(比如,number/string等),但是js不会检查变量的类型是否发生变化,而ts会检查.ts类型系统的主要优势为:可以显示标记出代码中的意外行为,从而降…

spi协议精讲

spi 总线是一种 高速的、全双工&#xff0c;同步串行总线&#xff0c;有四根线MISO MOSI SCLK CS 2.通信过程 3.极性和相位 因为没有像iic一样规定上升沿还是下降沿发送数据&#xff0c;spi的通信取决于极性和相位&#xff0c;因此有四种工作模式 CPHA0 表示SCK 第一个边沿时&…

Jetpack Compose 介绍和快速上手

Compose版本发展 19年&#xff0c;Compose在Google IO大会横空出世&#xff0c;大家都议论纷纷&#xff0c;为其前途堪忧。 21年7月Compose 1.0的正式发布&#xff0c;却让大家看到了Google在推广Compose上的坚决&#xff0c;这也注定Compose会成为UI开发的新风向。 23年1月…

基于springboot+vue的便利店信息管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

IDEA最新激 20活23码

人狠话不多 大家好&#xff0c;最近Intelli Idea官方的校验规则进行了更新&#xff0c;之前已经成功激20活23的Idea可能突然无法使用了。 特地从网上整理了最新、最稳定的激20活23码分享给大家&#xff0c;希望可以帮助那些苦苦为寻找Idea激20活23码而劳累的朋友们。 本激23…

所有字母异位词

class Solution { public:vector<int> findAnagrams(string s, string p) {std::vector<int> idxs;// 先获取p的hash串std::string dstr getHash(p);for (int i 0; i<s.length(); i) {// 使用滑动窗口&#xff0c;每次截取p的长度串并hashstd::string sub_str…

【Shiro】基本使用

1、环境准备 1、Shiro不依赖容器&#xff0c;直接创建maven工程即可 2、添加依赖 <dependencies><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.9.0</version></depen…

华为云云耀云服务器L实例评测|部署宝塔及使用宝塔管理服务器

在昨天的文章中,我们了解了一些关系实例的概念性的东西,并且购买了服务器,然后登录上后进行了简单的操作,今天我将会按照官网给出的教程,进行部署宝塔及使用宝塔对服务器进行管理的实践,那么 就让我们开始吧! 第一步:购买华为云云耀云服务器L实例 这个可以参考我的上一篇文章,…

关于医疗器械的检测认证

医疗器械注册审评流程&#xff1f; 【每日分享23.7.27】医疗器械产品注册申报流程&#xff08;超详细版&#xff09;之注册申报受理及审评审批 - 知乎 (zhihu.com) 医疗器械注册审评流程&#xff08;附图&#xff09;_申报_咨询_机构 (sohu.com) 型式试验&#xff1f; 型式试验…

Python 图形化界面基础篇:获取文本框中的用户输入

Python 图形化界面基础篇&#xff1a;获取文本框中的用户输入 引言 Tkinter 库简介步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建文本框步骤4&#xff1a;获取文本框中的用户输入步骤5&#xff1a;启动 Tkinter 主事件循环 完整…

少儿编程 2023年5月中国电子学会图形化编程等级考试Scratch编程四级真题解析(选择题)

2023年5月scratch编程等级考试四级真题 选择题(共10题,每题2分,共50分) 1、下列积木运行后的结果是 (说明:逗号后面无空格) A、我 B、爱 C、中 D、国 答案:B 考点分析:考查字符串相关积木的使用,逗号也是一个字符,所以两个连起来后第8个字符就是爱字,答案…

webpack:css-loader和style-loader关系

测试 当我们webpack 的 rules 啥都没配置的时候 const path require(path);module.exports {entry: ./src/index.js,output: {filename: index.js,path: path.resolve(__dirname, dist)},module: {rules: []} };我们在 js 中导入了 css&#xff0c;发现报错&#xff0c;因为…