【数据结构】双向链表详解

news2024/12/25 1:02:38

在这里插入图片描述
当我们学习完单链表后,双向链表就简单的多了,双向链表中的头插,尾插,头删,尾删,以及任意位置插,任意位置删除比单链表简单,今天就跟着小张一起学习吧!!

双向链表的分类

双向不带头链表

在这里插入图片描述

双向带头循环链表

在这里插入图片描述

还有双向带头不循环链表,双向不带头循环链表,着重使用双向带头循环链表,带头也就是有哨兵位。

双向带头循环链表

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

双向循环链表的接口实现

ListNode* ListCreate(int x)//创建新结点
ListNode* createhead()//创建哨兵位
void ListPushBack(ListNode* phead, int x)//尾插
void SListPrint(ListNode* phead)//打印链表
void SListPushFront(ListNode* phead, int x)//头插
void SListPopBack(ListNode* phead)//尾删
void SListPopFront(ListNode* phead)//头删
void SListInsert(ListNode* pos, int x)//pos前插
void SListErase(ListNode* pos)//删除pos;
ListNode* SListFind(ListNode* phead,int x)//查找链表中第一个x

0.结点结构体创建

typedef struct ListNode
{
	int data;//数据
	struct ListNode* next;//下个结点地址
	struct ListNode* prev;//上个结点地址
}ListNode;

1.创建一个新结点

ListNode* ListCreate(int x)//创建新结点
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));//给新结点申请空间
	if (newnode == NULL)
	{
		perror("malloc error");//没申请到,malloc返回NULL给newnode
		
	}
	newnode->data = x;//新结点的数据给x
	newnode->next = NULL;//新结点的下一个结点地址为空
	newnode->prev = NULL;//新结点的上一个结点地址为空
	return newnode;//新结点的地址返回回去
}

2.创建哨兵位

该哨兵位作为链表的头结点,不存数据

ListNode* createhead()
{
	ListNode* head = ListCreate(-1);//哨兵位结点的数据随便给个-1
	head->next = head;
	head->prev = head;
	return head;//返回哨兵位的头结点地址
}

在这里插入图片描述当没有带数据的新结点时,先让他上一个结点地址,和下一关结点地址都存入head自己的地址

3.尾插

void ListPushBack(ListNode* phead, int x)//尾插
{
	ListNode* newnode = ListCreate(int x);
	ListNode* tail = phead->prev;//记录尾结点的地址
	newnode->prev =tail;//新结点的prev存放尾结点的地址-1
	newnode->next = phead;//新结点的next存放头结点哨兵位的地址->2
	phead->prev = newnode;//头结点的prev存放新结点的地址->4
	tail->next = newnode;//尾结点的next存放新节点的地址->3

}

分析:在这里插入图片描述
由于使用tail记录了尾结点的地址,所以1,2,3,4可以任意切换顺序,如果没有记录,记得先将新结点的prev,next,先保存,然后在修改head->prev, head->prev->next;防止修改过程中,尾结点的地址丢失。
在这里插入图片描述
同样适用于在哨兵位后插新结点

4.打印双链表

void SListPrint(ListNode* phead)//打印链表
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
    }
	printf("\n");


}

分析:定义一个指针cur遍历整个链表,由于是循环链表,肯定会指向哨兵位,哨兵位结点的数据不使用,所以cur指针从头节点哨兵位的下一个结点开始遍历,直到cur==phead,循环结束,每次打印cur->data,然后移动cur到下一个结点。

5.头插

void SListPushFront(ListNode* phead, int x)//头插
{
	ListNode* newnode = ListCreate(x);
	
	newnode->next = phead->next;
	phead->next->prev = newnode;
	newnode->prev = phead;
	phead->next = newnode;

}

分析:在这里插入图片描述
注意:先保存newnode->next;和newnode->prev;
此时如果先进行4的话,phead->next的地址就变成了newnode,之前的phead->next地址丢失掉。
还有一种方法是先保存* next=phead->next;然后1,2,3,4,顺序可以调换。

6.尾删

void SListPopBack(ListNode* phead)//尾删
{
	ListNode* last = phead->prev->prev;//记录尾结点的上一个结点的地址
	phead->prev = last;//头节点哨兵位的prev存入last的地址
	last->next = phead;//last的next存入phead的地址

}

分析:在这里插入图片描述当要删除尾结点,我们可以先记录尾结点的上一个结点的地址,因为要删除尾结点,改变就是将尾结点的上一个结点last的next存phead的地址,phead的prev存入的是last的地址
在这里插入图片描述除哨兵位还有一个结点的尾删剩下一个哨兵位头结点,同样适用上面的代码

7.头删

void SListPopFront(ListNode* phead)//头删
{
	ListNode* next = phead->next;
	phead->next = next->next;
	next->next->prev = phead;
}

分析:在这里插入图片描述先保存哨兵位的下一个结点地址到next,然后将phead的next中存入next->next;将next->next->prev存入phead的首地址

8.pos指针指向的结点前插

void SListInsert(ListNode* pos, int x)//pos前插
{
	ListNode* newnode = ListCreate(x);//申请新结点将地址存放在newnode变量中
	newnode->next = pos;//新结点的下一个结点保存pos指针指向节点的地址
	newnode->prev = pos->prev;//新结点的prev保存pos指针指向的节点的上一个节点的地址
	pos->prev->next=newnode;//pos指针指向的节点的前一个结点的next保存newnode指针指向的结点
	pos->prev = newnode;//pos指针指向的结点的prev保存newnode指针指向结点的地址
}

分析:在这里插入图片描述

9.删除pos指针指向的结点

void SListErase(ListNode* pos)//删除pos;
{
	ListNode* last = pos->prev;//记录pos指针指向结点的前一个结点地址
	ListNode* next = pos->next;//记录pos指针指向结点的后一个结点地址
	last->next = next;//操作1
	next->prev = last;//操作2

}

分析:在这里插入图片描述

10.查找链表第一个出现的x

ListNode* SListFind(ListNode* phead,int x)
{
	ListNode* cur = phead->next;//cur指针先指向phead的下一个结点的位置
	while (cur != phead)//循环遍历
	{
		if (cur->data == x)//第一次找到
		{
			return cur;返回结点数据等于x的地址



		}
		cur = cur->next;//cur指针指向下一个结点





	}
	return NULL;
}

分析:类似于打印打印链表,遍历循环,从phead->next开始遍历,当cur不等于phead继续循环,如果cur->datax,就return cur的内容,也就是datax的结点地址,循环结束没有找到的话,就是该链表没有等于x的结点返回NULL;

11.双向链表的销毁

void Destroy(ListNode* phead)
{
	
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

分析,定义一个指针cur先指向phead的下一个结点,开始遍历,先保存cur指向结点的下一个结点的地址到next,然后释放掉cur指针指向的空间,然后让cur指向已经保存在next中下一个结点的地址,依次循环释放掉所有的结点,循环结束,释放掉哨兵位。

12.完整源码

#include <stdio.h>
#include <stdlib.h>
typedef struct ListNode
{
	int data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;
ListNode* ListCreate(int x)//创建新结点
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc error");
		//return;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
ListNode* createhead()
{
	ListNode* head = ListCreate(-1);
	head->next = head;
	head->prev = head;
	return head;
}
void ListPushBack(ListNode* phead, int x)//尾插
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->data = x;
	ListNode* tail = phead->prev;
	newnode->prev =tail;
	phead->prev = newnode;
	tail->next = newnode;
	newnode->next = phead;
	

}
void SListPrint(ListNode* phead)//打印链表
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d->",cur->data);
		cur = cur->next;
    }
	printf("\n");


}
void SListPushFront(ListNode* phead, int x)//头插
{
	ListNode* newnode = ListCreate(x);
	
	newnode->next = phead->next;
	phead->next->prev = newnode;
	newnode->prev = phead;
	phead->next = newnode;

}
void SListPopBack(ListNode* phead)//尾删
{
	ListNode* last = phead->prev->prev;
	phead->prev = last;
	last->next = phead;

}
void SListPopFront(ListNode* phead)//头删
{
	ListNode* next = phead->next;
	phead->next = next->next;
	next->next->prev = phead;
}
void SListInsert(ListNode* pos, int x)//pos前插
{
	ListNode* newnode = ListCreate(x);
	

	newnode->next = pos;
	newnode->prev = pos->prev;
	pos->prev->next=newnode;
	pos->prev = newnode;
}
void SListErase(ListNode* pos)//删除pos;
{
	ListNode* last = pos->prev;
	ListNode* next = pos->next;
	last->next = next;
	next->prev = last;

}
ListNode* SListFind(ListNode* phead,int x)
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;



		}
		cur = cur->next;





	}
	return NULL;
}
//双链表的销毁
void Destroy(ListNode* phead)
{
	
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}
int main()
{
	ListNode* list;
	list = createhead();
	printf("尾插:");
    ListPushBack(list,1);
	ListPushBack(list,2);
	ListPushBack(list,3);
	ListPushBack(list,4);
	SListPrint(list);
	printf("头插:");
	SListPushFront(list, 8);
	SListPushFront(list, 7);
	SListPushFront(list, 6);
	SListPushFront(list, 5);
	SListPrint(list);
	printf("尾删:");
	SListPopBack(list);
	SListPopBack(list);
	SListPrint(list);
	printf("头删:");
	SListPopFront(list);
	SListPopFront(list);
	SListPopFront(list);
	SListPrint(list);
	printf("插入pos指向结点前面:");
	SListInsert(list->next->next, 1000);
	SListPrint(list);
	printf("删除pos指向的结点:");
	SListErase(list->next->next);
	SListPrint(list);
	printf("查找第一个x的位置并打印出来:");
	ListNode* p=SListFind(list, 8);
	printf("%d", p->data);
	printf("修改查找到的结点:\n");
	p->data = 10000;
	SListPrint(list);
	printf("销毁打印销毁后哨兵位的下一个结点的数据,如果为随机值说明已经被销毁:");
	Destroy(list);
	printf("%d",list->data);
	
	
}

13.编译运行

在这里插入图片描述

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

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

相关文章

Pytorch Advanced(一) Generative Adversarial Networks

生成对抗神经网络GAN&#xff0c;发挥神经网络的想象力&#xff0c;可以说是十分厉害了 参考 1、AI作家 2、将模糊图变清晰(去雨&#xff0c;去雾&#xff0c;去抖动&#xff0c;去马赛克等)&#xff0c;这需要AI具有“想象力”&#xff0c;能脑补情节&#xff1b; 3、进行数…

JavaScript Promise 的真正工作原理

Promise 是处理异步代码的一种技术,也称为脱离回调地狱的头等舱门票。 3 承诺状态 待定状态 已解决状态 拒绝状态 理解 JavaScript Promis 什么是承诺? 通常,承诺被定义为最终可用的值的代理。 Promise 多年来一直是 JavaScript 的一部分(在 ES2015 中标准化并引入)。最…

【数据结构】前言概况 - 树

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;数据结构 &#x1f525;该文章针对树形结构作出前言&#xff0c;以保证可以对树初步认知。 目录&#xff1a; &#x1f30d;前言:&#x1f3…

Python语义分割与街景识别(4):程序运行

前言 本文主要用于记录我在使用python做图像识别语义分割训练集的过程&#xff0c;由于在这一过程中踩坑排除BUG过多&#xff0c;因此也希望想做这部分内容的同学们可以少走些弯路。 本文是python语义分割与街景识别第四篇&#xff0c;关于程序的内容&#xff0c;也是差不多最…

【Unity编辑器扩展】| GameView面板扩展

前言【Unity编辑器扩展】| GameView面板扩展未运行时在Game视图进行绘制总结前言 前面我们介绍了Unity中编辑器扩展的一些基本概念及基础知识,还有编辑器扩展中用到的相关特性Attribute介绍。后面就来针对Uniity编辑器扩展中比较常用的模块进行学习介绍。本文就来详细介绍一下…

JAVA版的数据结构——链表

目录 1.单向不带头链表 1.1 链表的概念及结构 1.2 代码部分 1.3 完整的全部代码 2. 双向不带头链表 2.1 代码部分 2.2 完整的代码 3. MySingleList与MyLinkedList代码上的区别 4. LinkedList的使用 4.1 什么是LinkedList 4.2 LinkedList的使用 4.2.1 LinkedList的构…

【数据结构】堆的向上调整和向下调整以及相关方法

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3; 文章目录 一、堆的概念二、堆的性质…

github上创建分支并合并到master

github上创建分支并合并到master 目录概述需求&#xff1a; 设计思路实现思路分析1.创建分支2.commit changes3.create pull request按钮4.网页解析器5.数据处理器 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,ful…

[deeplearning]深度学习框架torch的概念以及数学内容

&#xff08;提前声明&#xff1a;这边的操作系统为ubuntn22.04,至于window上如何进行安装和导入按这边不是很理解&#xff09; &#xff08;另外代码样例基本不使用notebook&#xff0c;paddle等等在线工具&#xff0c;而是使用本机安装好的python环境&#xff0c;和pytorch框…

IDEA中maven的设置以及相关功能

Maven 项目介绍 学习前提 相对于传统的项目&#xff0c;Maven 下管理和构建的项目真的非常好用和简单&#xff0c;所以这里也强调下&#xff0c;尽量使用此类工具进行项目构建。 ## Maven 常用设置介绍 如上图标注 1 所示&#xff0c;我们可以指定我们本地 Maven 的安装目录…

模块化开发_groupby查询think PHP5.1

要求按照分类的区别打印出不同类别的数据计数 如张三&#xff0c;做了6件事情 这里使用原生查询先测试 SELECT cate_id, COUNT(*) AS order_count FROM tp_article GROUP BY cate_id;成功 然后项目中实现 public function ss(){$sql "SELECT cate_id, COUNT(*) AS orde…

RCNA 锐捷培训

第一章 网络基础入门 1.1 OSI参考模型及TCP/IP协议栈 数据是如何传输的&#xff1f; 数据在计算机网络中传输通常依赖于TCP/IP协议模型。 什么是网络&#xff1f; 网络是一种连接多个计算机、设备或系统的通信基础设施&#xff0c;其目的是实现资源共享、信息传递、接收和共享…

14.Xaml ProgressBar控件 进度条控件

1.运行效果 2.运行源码 a.Xaml源码 <Grid Name="Grid1"><!--Orientation="Horizontal" 进度条的方向 水平的还是垂直的Value="40" 进度的数值Minimum="0" 最小值Maximum

17. 线性代数 - 矩阵的逆

文章目录 矩阵的转置矩阵的逆Hi, 您好。我是茶桁。 我们已经学习过很多关于矩阵的知识点,今天依然还是矩阵的相关知识。我们来学一个相关操作「矩阵的转置」,更重要的是我们需要认识「矩阵的逆」 矩阵的转置 关于矩阵的转置,咱们导论课里有提到过。转置实际上还是蛮简单…

淘宝京东扣库存怎么实现的

1. 使用kv存储实时的库存&#xff0c;直接在kv里扣减&#xff0c;避免用分布式锁 2. 不要先查再扣&#xff0c;直接扣扣扣&#xff0c;扣到负数&#xff0c;&#xff08;增改就直接在kv里做&#xff09;&#xff0c;就说明超卖了&#xff0c;回滚刚才的扣减 3. 同时写MQ&…

小白也可以玩转CMake之常用必备

目录 1.设置编译器flags2.设置源文件属性3.链接器标志4.Debug与Release包 今天&#xff0c;分享一篇工作中经常用到的一些CMake命令&#xff0c;看完就学会了哦&#xff0c;更多CMake与C内容也期待加入星球与我一起学习呀~ 1.设置编译器flags 例如&#xff1a;设置C标准&#x…

论文笔记《3D Gaussian Splatting for Real-Time Radiance Field Rendering》

项目地址 原论文 Abstract 最近辐射场方法彻底改变了多图/视频场景捕获的新视角合成。然而取得高视觉质量仍需神经网络花费大量时间训练和渲染&#xff0c;同时最近较快的方法都无可避免地以质量为代价。对于无边界的完整场景&#xff08;而不是孤立的对象&#xff09;和 10…

C高级day4循环语句

1&#xff0c;思维导图 运行结果为&#xff1a; 运行结果为&#xff1a;

【基础计算机网络1】认识计算机网络体系结构,了解计算机网络的大致模型(下)

前言 在上一篇我们主要介绍了有关计算机网络概述的内容&#xff0c;下面这一篇我们将来介绍有关计算机网络体系结构与参考模型的内容。这一篇博客紧紧联系上一篇博客。 这一篇博客主要内容是&#xff1a;计算机网络体系结构与参考模型&#xff0c;主要是计算机网络分层结构、协…

search_engine:搜索引擎实现

目录 一.项目背景及原理 1.背景 2.原理 二.技术栈及项目环境 1.技术栈 2.项目环境 3.环境准备 三.模块划分 四. 遇到的问题及其解决方法 1.搜索结果出现重复文档的问题 2.实现httplib功能的问题 五. 项目特点 1.文档记录 2.竞价排名 3.去掉暂停词 4.模拟实现http…