【数据结构 05】双链表

一、原理

双链表又称双向链表,通常情况下是带头循环结构,在C++STL标准模板库中封装的<list.h>头文件就是带头双向循环链表。

特性:增删灵活且高效,支持随机增删但不支持随机访问

设计思路:

  1. 链表包含一个头节点head,不存储数据,用于链表的维护,提高数据增删效率
  2. 每一个链表节点Node都包含一个数据和两个指针(前驱指针prev和后继指针next)
  3. 前驱指针prev指向前一个节点,后继指针next指向后一个节点
  4. 当链表为空时,头结点head的prev指针和next指针都指向head自身
  5. 节点的增删通过前后指针指向的改变即可完成,无需数据移动,效率高

二、DoubleList.h

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int DataType;

typedef struct Node
{
	DataType data;
	struct Node* next;
	struct Node* prev;
}Node;

typedef struct List
{
	Node* head;
}List;

void Init(List* plist)
{
	plist->head = (Node*)malloc(sizeof(Node));
	plist->head->prev = plist->head;
	plist->head->next = plist->head;
}

bool Empty(List* plist)
{
	return plist->head == plist->head->next;
}

Node* BuyNode(DataType x)
{
	Node* node = (Node*)malloc(sizeof(Node));
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
}

void PushFront(List* plist, DataType x)
{
	Node* node = BuyNode(x);

	if (Empty(plist))
	{
		node->next = plist->head;
		node->prev = plist->head;
		plist->head->next = node;
		plist->head->prev = node;
	}
	else
	{
		Node* next = plist->head->next;
		node->prev = plist->head;
		node->next = next;
		next->prev = node;
		plist->head->next = node;
	}
}

void PushBack(List* plist, DataType x)
{
	Node* node = BuyNode(x);

	if (Empty(plist))
	{
		node->next = plist->head;
		node->prev = plist->head;
		plist->head->next = node;
		plist->head->prev = node;
	}
	else
	{
		Node* prev = plist->head->prev;
		node->next = plist->head;
		node->prev = prev;
		prev->next = node;
		plist->head->prev = node;
	}
}

void PopFront(List* plist)
{
	if (Empty(plist))
	{
		printf("双链表为空,头删失败\n");
		return;
	}

	Node* cur = plist->head->next;
	plist->head->next = cur->next;
	cur->next->prev = plist->head;

	free(cur);
	cur = NULL;
}

void PopBack(List* plist)
{
	if (Empty(plist))
	{
		printf("双链表为空,尾删失败\n");
		return;
	}

	Node* cur = plist->head->prev;
	plist->head->prev = cur->prev;
	cur->prev->next = plist->head;

	free(cur);
	cur = NULL;
}

Node* Find(List* plist, DataType x)
{
	Node* cur = plist->head->next;
	while (cur != plist->head)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

void InsertFront(Node* pos, DataType x)
{
	if (pos == NULL)
	{
		printf("pos为空,插入失败\n");
		return;
	}

	Node* node = BuyNode(x);

	Node* prev = pos->prev;
	prev->next = node;
	node->prev = prev;
	node->next = pos;
	pos->prev = node;
}

void Delete(Node* pos)
{
	if (pos == NULL)
	{
		printf("pos为空,Delete失败\n");
		return;
	}
	
	Node* next = pos->next;
	Node* prev = pos->prev;

	next->prev = prev;
	prev->next = next;

	free(pos);
	pos = NULL;
}

void Destroy(List* plist)
{
	while (!Empty(plist))
	{
		PopFront(plist);
	}
	free(plist->head);
	plist->head = NULL;
	printf("双链表销毁成功\n");
}

void Print(List* plist)
{
	if (plist->head == NULL)
	{
		printf("双链表不存在\n");
		return;
	}

	Node* cur = plist->head->next;
	printf("head -> ");
	while (cur != plist->head)
	{
		printf("%2d -> ", cur->data);
		cur = cur->next;
	}
	printf("head\n");
}

三、test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "DoubleList.h"

int main()
{
	List list;
	Init(&list);
	Print(&list);	// head -> head

	// 尾插数据
	PushBack(&list, 1);
	PushBack(&list, 3);
	PushBack(&list, 5);
	PushBack(&list, 7);
	Print(&list); // head -> 1 -> 3 -> 5 -> 7 -> head

	// 头插数据
	PushFront(&list, 2);
	PushFront(&list, 4);
	PushFront(&list, 6);
	PushFront(&list, 8);
	Print(&list); // head -> 8 -> 6 -> 4 -> 2 -> 1 -> 3 -> 5 -> 7->head

	// 尾删数据
	PopBack(&list);
	PopBack(&list);
	PopBack(&list);
	Print(&list); // head -> 8 -> 6 -> 4 -> 2 -> 1 -> head

	// 头删数据
	PopFront(&list);
	PopFront(&list);
	PopFront(&list);
	Print(&list); // head -> 2 -> 1 -> head

	// 在查询的节点前插入数据
	InsertFront(Find(&list, 1), 11);
	InsertFront(Find(&list, 11), 111);
	Print(&list); // head -> 2 -> 111 -> 11 -> 1 -> head

	// 删除查询的节点
	Delete(Find(&list, 1));
	Delete(Find(&list, 11));
	Delete(Find(&list, 111));
	Print(&list); // head -> 2 -> head

	// 销毁链表
	Destroy(&list); // 链表销毁成功
	Print(&list); // 链表不存在
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何提高工业数据采集的效率和准确性-天拓四方

随着工业4.0和智能制造的兴起&#xff0c;工业数据采集的重要性日益凸显。通过数据采集&#xff0c;企业能够实时监控生产过程&#xff0c;优化资源配置&#xff0c;提高生产效率。在实时监控、生产优化、质量控制等方面&#xff0c;有效的数据采集系统能够为企业提供宝贵的洞察…

Pinely Round 2 F. Divide, XOR, and Conquer

F. Divide, XOR, and Conquer 题意 给定一个非负整数数组 a a a&#xff0c;定义操作&#xff1a; 对于区间 [ l , r ] [l,r] [l,r]&#xff0c;选择一个分界点 l ≤ k < r l \leq k < r l≤k<r&#xff0c;将其分成 [ l , k ] [l,k] [l,k] 和 [ k 1 , r ] [k…

系统架构设计师教程(十六)嵌入式系统架构设计理论与实践

嵌入式系统架构设计理论与实践 16.1 嵌入式系统概述16.1.1 嵌入式系统发展历程16.1.2 嵌人式系统硬件体系结构16.2 嵌入式系统软件架构原理与特征16.2.1 两种典型的嵌入式系统架构模式16.2.2 嵌入式操作系统16.2.3 嵌入式数据库16.2.4 嵌入式中间件16.2.5 嵌入式系统软件开发环…

[GN] 设计模式—— 创建型模式

文章目录 创建型模式单例模式 -- 确保对象唯一性例子优化饿汉式懒汉式 优缺点使用场景 简单工厂模式例子&#xff1a;优化优缺点适用场景 工厂方法模式 -- 多态工厂的实现例子优缺点优化适用场景 抽象工厂模式 -- 产品族的创建例子优缺点适用场景 总结 创建型模式 单例模式 –…

嵌入式系统设计师之任务管理

目录 一、任务划分(II) 二、任务控制块&#xff08;TCB)(II) 三、任务的状态及状态转换(II) 四、任务队列(II) 五、任务管理机制(II) 六、任务调度(II) 6.1 调度时机 6.2 调度方式 6.3 调度算法性能指标和分类 6.4 任务调度算法&#xff08;II) 1、先来…

OpenHarmony—环境准备

JS SDK安装失败处理指导 问题现象 下载JS SDK时&#xff0c;下载失败&#xff0c;提示“Install Js dependencies failed”。解决措施 JS SDK下载失败&#xff0c;一般情况下&#xff0c;主要是由于npm代理配置问题&#xff0c;或未清理npm缓存信息导致&#xff0c;可按照如…

【Docker】linux、nginx、容器镜像三者基本概念

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Docker容器》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

ISCTF wp

web 圣杯战争 题目源码 <?php highlight_file(__FILE__); error_reporting(0);class artifact{public $excalibuer;public $arrow;public function __toString(){echo "为Saber选择了对的武器!<br>";return $this->excalibuer->arrow;} }class pre…

第九篇【传奇开心果系列】beeware的toga开发移动应用示例:人口普查手机应用

传奇开心果博文系列 系列博文目录beeware的toga开发移动应用示例系列博文目录一、项目目标二、安装依赖三、实现应用雏形示例代码四、扩展功能和组件的考量五、添加更多输入字段示例代码六、添加验证功能示例代码七、添加数据存储功能示例代码八、添加数据展示功能示例代码九、…

Java基于SpringBoot+Vue的电影影城管理系统,附源码,文档

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

TypeScript实战系列之合理运用类型

目录 介绍any 和 unknownerve 的用途断言type 和 interfacedeclare 关键字的作用联合类型 和 类型守卫交叉类型 介绍 这篇主要介绍下ts 常用的基本类型和一些常用的技巧性技能 any 和 unknow any 和 unknown 是两个类型关键字&#xff0c;它们用于处理类型不确定或未知的情况…

AI绘画:PhotoMaker Win11本地安装记录!

昨天介绍一个叫PhotoMaker的AI绘画开源项目。挺不错的&#xff01; 通过这个项目可以快速制作特定人脸的AI绘画作品&#xff0c;相比传统的技术效果会好很多&#xff0c;效率也高很多。 今天趁热打铁&#xff0c;本地电脑装装看&#xff0c;并且记录&#xff0c;分享一下&#…

高效摄入英语信息的独门武器

经常有读者问我&#xff1a;日常看的都是什么样的信息&#xff1f; 简单来说&#xff0c;大致是这些&#xff1a;比较前沿的科研成果&#xff0c;心理学和神经科学的最新文献&#xff0c;一些综述性和总结性的文章&#xff0c;以及跟心理、大脑和其他科学领域相关的期刊杂志&am…

数据结构—栈实现前缀表达式的计算

前缀表达式计算 过程分析 中缀表达式&#xff1a;&#xff08;1 5&#xff09;*3 > 前缀表达式&#xff1a;*153 &#xff08;可参考这篇文章&#xff1a;中缀转前缀&#xff09; 第一步&#xff1a;从右至左扫描前缀表达式&#xff08;已存放在字符数组中&#xff09;&a…

最近公共祖先

最近公共祖先 概念 给定一棵有n个节点的树&#xff0c;树中的两个节点u和v的最近公共祖先lca&#xff0c;有以下定义 &#xff08;1&#xff09;lca既是u的祖先&#xff0c;又是v的祖先 &#xff08;2&#xff09;lca是所有u和v的公共祖先中深度最深的祖先&#xff0c;也就…

Linux第38步_编译“正点原子移植好的uboot”

uboot的全称是Universal Boot Loader&#xff0c;uboot是一个遵循GPL协议的开源软件&#xff0c;uboot是一个裸机代码&#xff0c;可以看作是一个裸机综合例程。现在的 uboot 已经支持液晶屏、网络、USB等高级功能。 uboot官方的uboot源码是给所有的半导体厂商准备的。ST公司会…

CSS自适应分辨率 postcss-pxtorem(适用于 Vite)

前言 此篇是基于 Vite Vu3 项目的 CSS 自适应分辨率&#xff01; 如果想知道基于 Webpack Vue2 可移步 《CSS自适应分辨率 amfe-flexible 和 postcss-pxtorem&#xff08;适用于 Webpack&#xff09;》 项目对应的主要插件版本如下&#xff1a; "vite": "^4…

使用Win32API实现贪吃蛇小游戏

目录 C语言贪吃蛇项目 基本功能 需要的基础内容 Win32API 介绍 控制台程序部分指令 设置控制台窗口的长宽 设置控制台的名字 控制台在屏幕上的坐标位置结构体COORD 检索指定标准设备的句柄&#xff08;标准输入、标准输出或标准错误&#xff09; 光标信息结构体类型CONSOLE_CUR…

人人都可配置的大屏可视化

大屏主要是为了展示数据和酷炫的效果&#xff0c;布局大部分是9宫格&#xff0c;或者在9宫格上做的延伸&#xff0c;现在介绍下 泛积木-低代码 提供的大屏可视化配置。 首先查看效果展示 泛积木-低代码大屏展示。 创建页面之后&#xff0c;点击进入编辑页面&#xff0c;在可视…

电子液晶屏幕生产厂污废水处理需要哪些工艺设备

随着电子液晶屏幕行业的不断发展&#xff0c;污废水处理成为了一个重要的环保问题。为了达到合规性排放要求&#xff0c;并保护环境&#xff0c;厂家需要采取一系列工艺设备来处理污废水。 首先&#xff0c;常见的一种处理工艺是物理与化学处理。物理处理包括预处理与固液分离&…