数据结构——双向链表的实现

news2024/12/23 8:36:55

一、双向链表的结构

注意:双向链表又称带头双向循环链表
这⾥的“带头”跟前⾯我们说的“头节点”是两个概念,实际前⾯的在单链表阶段称呼不严
谨,但是为了同学们更好的理解就直接称为单链表的头节点。
带头链表⾥的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这⾥“放哨 的”
哨兵位”存在的意义: 遍历循环链表避免死循环。
双向链表每个节点储存一个有效数据+前驱指针+后继指针

二、. 双向链表的实现

2.1 创建&初始化

2.2.1  List.h

#pragma once
typedef struct ListNode
{
	int val;
	struct ListNode* next;
	struct  ListNode* prev;
	

}LTNode; 


//初始化
LTNode* LTInit();

2.2.2  List.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
LTNode* LTInit()//哨兵位初始化
{
	LTNode* head = (LTNode*)malloc(sizeof(LTNode));
	head->val = -1;
	head->next = head->prev =head;
	return head;
}

2.2.3 text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	

	return 0;
}

代码运行测试:

2.2尾插&头插

分析:

尾插
1.往d3节点的后面插入数据叫做尾插  

 2.往哨兵位head之前插入数据也叫尾插 

 

头插

在哨兵位和头节点之间插入

2.2.1  List.h

//尾插
//1.往d3节点的后面插入数据叫做尾插    2.往哨兵位head之前插入数据也叫尾插
void LTPushBack(LTNode* head, int x);

//打印
void LTPrint(LTNode* head);
//头插
void LTPushFront(LTNode* head, int x);

2.2.2  List.c

//创建新节点
LTNode* Listbuynode(int x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	node->val = x;
	node->next = node->prev = NULL;
	return node;
}
void LTPushBack(LTNode* head, int x)
{
	LTNode* node = Listbuynode(x);
	//对新节点进行操作
	node->next = head;
	node->prev = head->prev;

	//对原来的尾节点和哨兵位进行操作
	head->prev->next = node;
	head->prev = node;
}
void LTPrint(LTNode* head)
{
	assert(head);
	LTNode* pcur = head->next;
	while (pcur != head)
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
	printf("\n");
}

void LTPushFront(LTNode* head, int x)
{
	LTNode* node = Listbuynode(x);
	//对新节点进行操作
	node->next = head->next;
	node->prev = head;

	//对哨兵位和头节点进行操作
	head->next->prev = node;
	head->next = node;
}

2.2.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);
    LTPushBack(head, 3);
    LTPushFront(head,4);//4->1->2->3
	LTPrint(head);
	

	return 0;
}

2.3  头删&尾删

2.3.1  List.h

//尾删
void LTPopBack(LTNode* head);

//头删
void LTPopFront(LTNode* head);

2.3.2  List.c

void LTPopBack(LTNode* head)
{
	//链表为空不能删除
	assert(head);
	assert(head->next != head);
	//将尾节点进行保存
	LTNode* del = head->prev;
	//连接次尾节点和哨兵位
	del->prev->next = head;
	head->prev = del->prev;
	free(del);
	del = NULL;

}
void LTPopFront(LTNode* head)
{
	//链表为空不能删除
	assert(head);
	assert(head->next != head);

	//将头节点进行保存
	LTNode* del = head->next;
	//连接哨兵位和次头节点
	head->next = del->next;
	del->next->prev = head;
	free(del);
		del = NULL;
}

2.3.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);

	LTPushBack(head, 3);
	LTPushFront(head, 4);
	LTPrint(head);//4->1->2->3
	LTPopFront(head);
	
	LTPrint(head);//1->2->3
	LTPopBack(head);
	LTPrint(head);1->2
	

	return 0;
}

2.4  查找数据&在pos节点后插入数据&删除pos节点

2.4.1  List.h


//在pos位置之后插入数据
void LTInsert(LTNode* pos, int x);
//删除pos节点
void LTErase(LTNode* pos);

//查找数据
LTNode* LTFind(LTNode* head, int x);

2.4.2  List.c

void LTInsert(LTNode* pos, int x)
{
	assert(pos);
	LTNode* node = Listbuynode(x);
	//先处理新节点
	node->prev = pos;
	node->next = pos->next;
	//在处理前后节点
	pos->next = node;
	node->next->prev = node;
}

LTNode* LTFind(LTNode* head, int x)
{
	assert(head);
	assert(head->next!=head);
	LTNode* pcur = head->next;
	while (pcur != head)
	{
		if (pcur->val == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

2.4.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);

	LTPushBack(head, 3);
    LTPushBack(head, 4);
	
	
	//LTPopBack(head);
	//LTPrint(head);
	//LTPopBack(head);
	LTNode* find = LTFind(head, 4);
	LTInsert(find, 11);
    LTPrint(head);//1->2->3->4->11
	LTErase(find);//1->2->3->11


	LTPrint(head);
	

	return 0;
}

2.5  销毁

若销毁接口用二级指针接受,传哨兵位指针的地址,那么可以改变哨兵位(指针指向),使哨兵位指向NULL;

若销毁接口用一级指针接受,传一级指针(哨兵位指针),传过去的形参(是指针存储的地址),不能够改变指针的指向,在对形参操作,可以释放哨兵位指向的地址空间(形参的值为空间地址),但是不能改变实参指针的指向(实参依然指向原来被释放的地址空间),需要手动将实参置为NULL.

简而言之,若需要改变一级指针指向,需要传二级指针。

前面都是用一级指针传参,为了保持接口的一致性,我们用一级指针传参

2.5.1  List.h

//销毁
void LTDestroy(LTNode* phead);

2.5.2  List.c

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* next = pcur->next;
	while (pcur != phead)
	{
		free(pcur);
		pcur = next;
		next = next->next;
	}
	free(phead);
	phead = NULL;
}

2.5.3  text.c 

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);

	LTPushBack(head, 3);
	LTPushFront(head, 4);
	/*LTPrint(head);
	LTPopFront(head);*/
	
	LTPrint(head);
	//LTPopBack(head);
	//LTPrint(head);
	//LTPopBack(head);
	LTNode* find = LTFind(head, 4);
	/*LTInsert(find, 11);*/
	LTErase(find);


	LTPrint(head);
	LTDestroy(head);
	head = NULL;

	return 0;
}

2.6  完整代码

2.6.1  List.h

#pragma once
typedef struct ListNode
{
	int val;
	struct ListNode* next;
	struct  ListNode* prev;
	

}LTNode; 


//初始化
LTNode* LTInit();
//销毁
void LTDestroy(LTNode* phead);
//尾插
//1.往d3节点的后面插入数据叫做尾插    2.往哨兵位head之前插入数据也叫尾插
void LTPushBack(LTNode* head, int x);

//打印
void LTPrint(LTNode* head);
//头插
void LTPushFront(LTNode* head, int x);

//尾删
void LTPopBack(LTNode* head);

//头删
void LTPopFront(LTNode* head);

//在pos位置之后插入数据
void LTInsert(LTNode* pos, int x);
//删除pos节点
void LTErase(LTNode* pos);

//查找数据
LTNode* LTFind(LTNode* head, int x);

2.6.2  List.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
LTNode* LTInit()//哨兵位初始化
{
	LTNode* head = (LTNode*)malloc(sizeof(LTNode));
	head->val = -1;
	head->next = head->prev =head;
	return head;
}
//创建新节点
LTNode* Listbuynode(int x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	node->val = x;
	node->next = node->prev = NULL;
	return node;
}
void LTPushBack(LTNode* head, int x)
{
	LTNode* node = Listbuynode(x);
	//对新节点进行操作
	node->next = head;
	node->prev = head->prev;

	//对原来的尾节点和哨兵位进行操作
	head->prev->next = node;
	head->prev = node;
}
void LTPrint(LTNode* head)
{
	assert(head);
	LTNode* pcur = head->next;
	while (pcur != head)
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
	printf("\n");
}

void LTPushFront(LTNode* head, int x)
{
	LTNode* node = Listbuynode(x);
	//对新节点进行操作
	node->next = head->next;
	node->prev = head;

	//对哨兵位和头节点进行操作
	head->next->prev = node;
	head->next = node;
}
void LTPopBack(LTNode* head)
{
	//链表为空不能删除
	assert(head);
	assert(head->next != head);
	//将尾节点进行保存
	LTNode* del = head->prev;
	//连接次尾节点和哨兵位
	del->prev->next = head;
	head->prev = del->prev;
	free(del);
	del = NULL;

}
void LTPopFront(LTNode* head)
{
	//链表为空不能删除
	assert(head);
	assert(head->next != head);

	//将头节点进行保存
	LTNode* del = head->next;
	//连接哨兵位和次头节点
	head->next = del->next;
	del->next->prev = head;
	free(del);
		del = NULL;
}

void LTInsert(LTNode* pos, int x)
{
	assert(pos);
	LTNode* node = Listbuynode(x);
	//先处理新节点
	node->prev = pos;
	node->next = pos->next;
	//在处理前后节点
	pos->next = node;
	node->next->prev = node;
}

LTNode* LTFind(LTNode* head, int x)
{
	assert(head);
	assert(head->next!=head);
	LTNode* pcur = head->next;
	while (pcur != head)
	{
		if (pcur->val == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* next = pcur->next;
	while (pcur != phead)
	{
		free(pcur);
		pcur = next;
		next = next->next;
	}
	free(phead);
	phead = NULL;
}

2.6.3  text.c

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
#include<stdio.h>
int main()
{
	LTNode* head;
	head=LTInit();
	LTPushBack(head, 1);
	LTPushBack(head, 2);

	LTPushBack(head, 3);
	LTPushFront(head, 4);
	/*LTPrint(head);
	LTPopFront(head);*/
	
	LTPrint(head);
	//LTPopBack(head);
	//LTPrint(head);
	//LTPopBack(head);
	LTNode* find = LTFind(head, 4);
	/*LTInsert(find, 11);*/
	LTErase(find);


	LTPrint(head);
	LTDestroy(head);
	head = NULL;

	return 0;
}

三、顺序表和双向链表的优缺点分析

不同点
顺序表
链表(单链表)
存储空间上
物理上一定连续
逻辑上连续,但物理上不⼀定连续
随机访问
⽀持O(1)
不支持,O(n)
任意位置插⼊或者删除元素
可能需要搬移元素,效率低O(N)
只需修改指针指向
插入
动态顺序表,空间不够时需要扩容
没有容量的概念
应⽤场景
元素⾼效存储+频繁访问
任意位置插⼊和删除频繁

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

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

相关文章

计算机毕业设计选题推荐-美术馆微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

260亿!每部手机赚数千元,手机行业太赚钱了,难怪都想做手机

日前H公司公布了前三季度的业绩&#xff0c;净利润大幅增长两倍多&#xff0c;而营收仅增长2.4%&#xff0c;这显示出三季度仅是多卖了数百万部高端手机&#xff0c;利润就飙升了数百亿元&#xff0c;平均每部手机赚数千元。 该公司公布的业绩显示&#xff0c;前三季度营收增长…

【波形图】将波形图或波形图表的数据导出至文件

LabVIEW 提供了几种方法使用户能够轻松地以不同格式类型保存波形图、波形图形或二维图片。 在本文中&#xff0c;您将了解如何将波形图、波形图表或二维图片另存为图像的三种不同方法。 您可以使用以下三种方法将波形图、波形图表或二维图片保存为图像文件&#xff1a;​​​​…

1.3 安全攻击

思维导图&#xff1a; 1.3 安全攻击 定义&#xff1a; 安全攻击可以分为被动攻击和主动攻击。被动攻击&#xff1a;尝试获取或利用系统信息但不影响系统资源。主动攻击&#xff1a;尝试修改系统资源或影响系统的正常运行。 1.3.1 被动攻击 特点&#xff1a; 主要对传输数据…

【软件安装环境配置】VsCode安装和配置各种环境(保姆级)

一、VsCode 下载 1.官网下载 网站&#xff1a;Visual Studio Code - Code Editing. Redefined 打开网站 点击Download 根据操作系统&#xff08;macOS、Windows、Linux&#xff09;和版本下载 下载到本地 注意&#xff1a; 若下载很慢&#xff0c;或者下着下着就暂停了 可…

红队专题-从零开始VC++C/S远程控制软件RAT-MFC-远程桌面屏幕监控

红队专题 招募六边形战士队员[24]屏幕监控-(1)屏幕查看与控制技术的讲解图像压缩算法图像数据转换其他 [25]---屏幕监控(2)查看屏幕的实现7.1 屏幕抓图显示7.7 完善主控端 招募六边形战士队员 一起学习 代码审计、安全开发、web攻防、逆向等。。。 私信联系 [24]屏幕监控-(1…

2023/10/29总结

总结 踩坑记录 写代码的时候遇到了一个错误大概是这样的 io.jsonwebtoken.security.WeakKeyException: The signing keys size is 48 bits which is not secure enough for the HS256 algorithm. The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used…

【设计模式】第14节:结构型模式之“代理模式”

一、简介 代理模式&#xff08;Proxy Design Pattern&#xff09;在不改变原始类&#xff08;或叫被代理类&#xff09;代码的情况下&#xff0c;通过引入代理类来给原始类附加功能。 二、优点 关注点分离访问控制延迟实例化远程访问缓存增加附加功能 三、应用场景 访问控…

metaRTC集成flutter ui demo编译指南

概要 Flutter是由Google开发的开源UI工具包&#xff0c;用于构建跨平台应用程序&#xff0c;支持linux/windows/mac/android/ios等操作系统。 metaRTC新增flutter demo&#xff0c;支持linux/windows/mac/android/ios操作系统&#xff0c;此demo在ubuntu桌面环境下测试成功。…

Linux常用命令——chmod命令

在线Linux命令查询工具 chmod 用来变更文件或目录的权限 补充说明 chmod命令用来变更文件或目录的权限。在UNIX系统家族里&#xff0c;文件或目录权限的控制分别以读取、写入、执行3种一般权限来区分&#xff0c;另有3种特殊权限可供运用。用户可以使用chmod指令去变更文件…

http协议/https协议

http协议&#xff08;HyperText Transfer Protocol - 超文本传输协议&#xff09;&#xff1a; 在网络层中被广为人知的协议&#xff0c;想必没有学过计算机的人都知道http是有关网站&#xff0c;网络的&#xff0c;那么究竟其中有哪些东西是需要计算机相关人员掌握的&#xff…

2023年CCF中国开源大会“大模型时代的智能化软件工程新范式”分论坛成功举行...

2023年CCF中国开源大会“大模型时代的智能化软件工程新范式”分论坛于10月21日在湖南长沙成功举行。本次论坛聚焦大模型时代的智能化软件新生态以及相应的软件工程新范式&#xff0c;邀请了多位来自学术界和工业界的专家进行分享和交流&#xff0c;共设置了5个主题报告和1个Pan…

2023年中国多功能电子书包产量、销量及市场规模分析[图]

多功能电子书包是一种便携式电子设备&#xff0c;旨在提供类似于传统纸质书包的功能&#xff0c;但具有更多的数字功能。它可以存储、阅读、编辑和分享电子书籍、文档、图片和音频/视频文件等。 多功能电子书包行业的优点 资料来源&#xff1a;共研产业咨询&#xff08;共研网…

如何取消磁盘的BitLocker加密?

C盘被加密了&#xff0c;该怎么取消加密呢&#xff1f; 下面提供详细步骤供参考&#xff1a; 步骤1. 打开开始菜单&#xff0c;并点击齿轮图标&#xff0c;打开[设置] 步骤2. 在Windows设置视窗中点击[更新和安全] 步骤3. 点击左侧的[设备加密]&#xff0c;然后点击视窗右侧的…

在Qt中List View和List Widget的区别是什么,以及如何使用它们

2023年10月29日&#xff0c;周日晚上 目录 List View和List Widget的区别 如何使用QListView 如何使用QListWidget List View和List Widget的区别 在Qt中&#xff0c;QListView 和 QListWidget 是用于显示列表数据的两个常用控件&#xff0c;它们有一些区别和特点。 1. 数…

基于SpringBoot的图书电子商务网站设计与实现

目录 前言 一、技术栈 二、系统功能介绍 管理员功能实现 用户管理 图书分类管理 图书信息管理 订单管理 用户功能实现 图书信息 购物车 确认下单 我的收藏 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 社会发展日新月异&#xff0c;用计算机应用…

【蓝桥杯选拔赛真题05】C++超级素数 青少年组蓝桥杯C++选拔赛真题 STEMA比赛真题解析

目录 C/C++超级素数 一、题目要求 1、编程实现 2、输入输出 二、算法分析 <

BAT035.【工作常用批处理专栏】批处理功能说明及下载

引言:本文主要提供本专栏中练习的批处理功能进行说明和下载。 一、本专栏练习的批处理下载地址 链接:https://pan.baidu.com/s/1L_V-_LojpbfFcUFbvBK1_A 提取码:vady 二、本专栏练习的批处理汇总如下 【工作常用批处理专栏】批处理目录树: │ BAT001.CMD命令速查手册.h…

脚本木马编写

PHP小马编写 小马用waf扫描&#xff0c;没扫描出来有风险。 小马过waf之后用echo $_SERVER[DOCUMENT_ROOT]获得当前运行脚本所在的文档根目录。&#xff0c;然后在上传大马工具。 $_SERVER&#xff0c;参考&#xff1a;PHP $_SERVER详解 小马编写二次加密 现在是可以被安全…

uniapp中APP端使用echarts用formatter设置y轴保留2位小数点不生效

uniapp使用echarts&#xff0c;在内置浏览器中&#xff0c;设置保留2位小数能正常显示&#xff08;代码如下&#xff09;&#xff0c;但是在APP端这个设置不起作用。 yAxis: {type: value,axisLabel: {formatter: function (val) {return val.toFixed(2); //y轴始终保留小数点…