<数据结构>NO4.带头双向循环链表

news2024/11/27 18:34:47

文章目录

  • 前言
  • 1. 头文件
  • 2. 函数实现
    • 1)创建哨兵位节点
    • 2)新增一个节点
    • 3)打印链表
    • 4)头插
    • 5)尾插
    • 6)头删
    • 7)尾删
    • 8)查找
    • 9)pos前插入
    • 10)删除pos处节点
    • 11)销毁
  • 3. 测试用例

在这里插入图片描述

前言

链表的实现有多种,带头、不带头的,单向、双向的,非循环、循环的,前面我们已经实现了不带头单向非循环链表,这次我们实现带头双线循环链表(这个结构巧妙的设计造成了很容易实现)
关于单链表可以看这篇文章syseptembera的个人博客:单链表

依然分DobuleLinkList.h,DoubleLinkList.c,test.c三个文件实现

头文件放类型定义,函数声明。
源文件存放函数实现
主函数编写测试用例

1. 头文件

既然双链表是数据结构的一种,那么对它的操作无非是增、删、查、改等,而双链表的节点由3部分组成:数据域指向前一个结点的指针域指向后一个节点的指针域

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int DataType;
typedef struct DLTNode
{
	DataType val;
	struct DLTNode* next;
	struct DLTNode* prev;
}DLTNode;
//新增节点
extern DLTNode* BuyOneNode(DataType x);
//创建哨兵节点
extern DLTNode* DLTCreat(void);
//打印链表
extern void DLTPrint(DLTNode* phead);
//判断链表是否为空
extern bool DLTEmpty(DLTNode* phead);
//头插
extern void DLTPushFront(DLTNode* phead, DataType x);
//尾插
extern void DLTPushBack(DLTNode* phead, DataType x);
//头删
extern void DLTPopFront(DLTNode* phead);
//尾删
extern void DLTPopBack(DLTNode* phead);
//查找
extern DLTNode* DLTFind(DLTNode* phead, DataType x);//返回指针方便后续插入删除等操作
//pos前插入
extern void DLTInsert(DLTNode* pos, DataType x);
//删除pos处节点
extern void DLTErase(DLTNode* pos);
//销毁(传一级指针不能置空实参)
extern void DLTDestory(DLTNode* phead);

为什么所有的参数传递的是一级指针?
在无头单链表的实现中,我们插入删除等操作有可能改变实参的值,所以需要传递二级指针,但是对于有头双链表而言,我们的传递的实参phead是指向头的(头是哨兵节点,不属于链表有效节点),所以对有头双链表进行插入删除等操作,不存在改变实参,所以不需要传递二级指针.

为什么查找函数返回的是地址?
和单链表类似,原因之一是我们可以通过该返回值直接修改该节点的数据,原因之二是可以通过返回的地址进行后续插入删除等操作而不需要再传递哨兵节点地址给插入删除函数。


2. 函数实现

1)创建哨兵位节点

和之前实现的单链表不同点之一是这里实现的双链表是带哨兵节点的,所以我们需要先创建一个哨兵节点,哨兵节点满足的条件是next指向自己,pre也指向自己,这样一个节点也是循环节点,数值域可以是任意值(这里定义为-1)。

//创建哨兵位节点
DLTNode* DLTCreat()
{
	DLTNode* head = (DLTNode*)malloc(sizeof(DLTNode));
	assert(head);
	//哨兵位节点next,prev指向自己
	head->next = head;
	head->prev = head;
	head->val = -1;
	return head;
}

2)新增一个节点

每次插入时都需要新增一个节点,所以直接将新增节点封装为一个函数减少代码冗余

//新增节点
DLTNode* BuyOneNode(DataType x)
{
	DLTNode* newnode = (DLTNode*)malloc(sizeof(DLTNode));
	assert(newnode);//检查是否成功创建
	newnode->val = x;
	newnode->next = newnode->prev = NULL;
	assert(newnode);
	return newnode;
}

3)打印链表

总体来说和单链表类似,但是需要注意的有2点:
❗单链表是无哨兵位节点的,传递的实参是指向第一个有效节点的,双链表是有头节点的,传递的实参是指向哨兵节点的,因此开始打印时需要从实参的后一个节点开始打印
❗单链表是不循环的,打印到NULL节点停止,双链表是循环的,没有空节点,打印到哨兵节点停止。

//打印链表
void DLTPrint(DLTNode* phead)
{
	assert(phead);
	DLTNode* cur = phead->next;//从哨兵节点后一个节点开始打印
	printf("guard<==>");
	while (cur != phead)//遍历到哨兵节点
	{
		printf("%d<==>", cur->val);
		cur = cur->next;
	}
	puts("");
}

4)头插

在这里插入图片描述
方便后续操作,我们保存头节点的地址

//头插
void DLTPushFront(DLTNode* phead, DataType x)
{
	assert(phead);
	DLTNode* first = phead->next;
	DLTNode* newnode = BuyOneNode(x);
	newnode->next = first;
	newnode->prev = phead;
	phead->next = newnode;
	first->prev = newnode;
}

注意:这段代码当链表为空时仍然成立,链表为空时只有哨兵节点,所以哨兵节点就是first

5)尾插

在这里插入图片描述

方便后续操作,我们保留tail的地址

void DLTPushBack(DLTNode* phead, DataType x)
{
	assert(phead);
	DLTNode* tail = phead->prev;
	DLTNode* newnode = BuyOneNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

注意:尾插也适用于只存在哨兵节点的情况,哨兵节点就是tail

6)头删

在这里插入图片描述
方便后续操作,头删保存second的节点
头删需要判断链表是否为空(只有哨兵一个节点)

//判断链表是否为空
bool DLTEmpty(DLTNode* phead)
{
	return phead == phead->next;
}
void DLTPopFront(DLTNode* phead)
{
	assert(phead);
	assert(!DLTEmpty(phead));//链表为空不可以删除

	DLTNode* first = phead->next;
	DLTNode* second = first->next;
	phead->next = second;
	second->prev = phead;
	free(first);
}

注意:头删可以用于只存在一个节点的情况,即哨兵节点就是second

7)尾删

在这里插入图片描述
为了方便操作,保留倒数第2个节点tailPrev
当链表为空时不能尾删

//尾删
void DLTPopBack(DLTNode* phead)
{
	assert(phead);
	assert(!DLTEmpty(phead));//链表为空不可以删除

	DLTNode* tail = phead->prev;
	DLTNode* tailPrev = tail->prev;
	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
}

注意:尾删可以用于只存在一个节点的情况,即哨兵节点就是tailPrev

8)查找

//查找
DLTNode* DLTFind(DLTNode* phead, DataType x)
{
	assert(phead);
	DLTNode* cur = phead->next;//从哨兵节点下一个节点开始寻找
	while (cur != phead)//遍历到哨兵节点
	{
		if (cur->val == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

9)pos前插入

在这里插入图片描述
方便操作,我们将pos指向节点的前一个结点posPrev保存下来

//pos前插入
void DLTInsert(DLTNode* pos, DataType x)
{
	assert(pos);
	DLTNode* posPrev = pos->prev;
	DLTNode* newnode = BuyOneNode(x);
	newnode->prev = posPrev;
	posPrev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}

头插和尾插可以直接复用该代码
头插时pos是第一个有效节点的地址DLTInsert(phead->next, x);
尾插时pos是哨兵节点的地址DLTInsert(phead, x);

10)删除pos处节点

在这里插入图片描述
方便后续操作,我们保留posPrev和posNext节点

//删除pos处节点
void DLTErase(DLTNode* pos)
{
	DLTNode* posNext = pos->next;
	DLTNode* posPrev = pos->prev;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

头删和尾删可以直接复用该代码
头删时pos是第一个有效节点的地址DLTErase(phead->next, x);
尾删时pos是尾节点的地址DLTErase(phead->prev, x);

11)销毁

在这里插入图片描述

方便后续操作,每次保存下一个结点的地址

//销毁(传一级指针不能置空实参)
void DLTDestory(DLTNode* phead)
{
	assert(phead);
	DLTNode* cur = phead->next;//从第一个节点开始
	while (cur != phead)
	{
		DLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);//销毁哨兵节点
}

3. 测试用例

在主函数中写出对应的测试函数测试双链表功能是否正常

#define _CRT_SECURE_NO_WARNINGS 1
#include "DoubleLinkList.h"
//测试头插
void test1()
{
	DLTNode* plist = DLTCreat();
	DLTPushFront(plist, 1);
	DLTPushFront(plist, 2);
	DLTPushFront(plist, 3);
	DLTPrint(plist);
}

//测试尾插
void test2()
{
	DLTNode* plist = DLTCreat();
	DLTPushBack(plist, 1);
	DLTPushBack(plist, 2);
	DLTPushBack(plist, 3);
	DLTPrint(plist);
}

//测试头删
void test3()
{
	DLTNode* plist = DLTCreat();
	DLTPushBack(plist, 1);
	DLTPushBack(plist, 2);
	DLTPushBack(plist, 3);
	DLTPopFront(plist);
	DLTPopFront(plist);
	DLTPrint(plist);
}

//测试尾删
void test4()
{
	DLTNode* plist = DLTCreat();
	DLTPushBack(plist, 1);
	DLTPushBack(plist, 2);
	DLTPushBack(plist, 3);
	DLTPopBack(plist);
	DLTPopBack(plist);
	DLTPrint(plist);
}

//测试查找
void test5()
{
	DLTNode* plist = DLTCreat();
	DLTPushBack(plist, 1);
	DLTPushBack(plist, 2);
	DLTPushBack(plist, 3);
	DLTPushBack(plist, 4);
	DLTNode* pos = DLTFind(plist, 4);
	//找到了
	if (pos)
		pos->val *= 10;
	DLTPrint(plist);
}

//测试pos前插入
void test6()
{
	DLTNode* plist = DLTCreat();
	DLTPushBack(plist, 1);
	DLTPushBack(plist, 2);
	DLTPushBack(plist, 3);
	DLTPushBack(plist, 4);
	DLTInsert( DLTFind(plist, 1), 11);
	DLTInsert(DLTFind(plist, 4), 11);
	DLTPrint(plist);
}

//测试删除pos处节点
void test7()
{
	DLTNode* plist = DLTCreat();
	DLTPushBack(plist, 1);
	DLTPushBack(plist, 2);
	DLTPushBack(plist, 3);
	DLTPushBack(plist, 4);
	DLTErase(DLTFind(plist, 1));
	DLTErase(DLTFind(plist, 4));
	DLTPrint(plist);
}

//测试销毁
void test8()
{
	DLTNode* plist = DLTCreat();
	DLTPushBack(plist, 1);
	DLTPushBack(plist, 2);
	DLTPushBack(plist, 3);
	DLTPushBack(plist, 4);
	DLTDestory(plist);
}
int main()
{
	test1();
	test2();
	test3();
	test4();
	test5();
	test6();
	test7();
	test8();
	return 0;
}

在这里插入图片描述


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

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

相关文章

Redis 缓存穿透、缓存击穿与缓存雪崩

文章目录 1. 缓存穿透解决方法 2. 缓存击穿解决方法 3. 缓存雪崩解决方法 在 redis 的应用场景中&#xff0c;需要考虑缓存在某些场景下可能出现的问题&#xff1a; 缓存穿透 缓存击穿 缓存雪崩 以下缓存问题的讨论都是基于以下应用架构讨论的&#xff1a; 1. 缓存穿透 对应…

数据备份系列:Rsync 备份实战记录(二)

一、Rsync Cron 场景使用 在对数据备份要求实时性不高的情况下&#xff0c;可优先考虑该场景&#xff0c;选择一个合适的时间&#xff0c;对数据进行定时远程增量同步。 在《数据备份系列&#xff1a;Rsync 备份详解&#xff08;一&#xff09;》中我们已经对服务搭建以及远程…

DAD-DAS模型

DAD-DAS模型 文章目录 DAD-DAS模型[toc]1 产品服务:需求方程2 实际利率:费雪方程3 通货膨胀:菲利普斯方程4 预期通货膨胀&#xff1a;适应性预期5 货币政策规则&#xff1a;泰勒方程6 动态总供给-总需求方程&#xff08;DAS-DAD&#xff09;7 总供给冲击模拟 1 产品服务:需求方…

【JavaEE初阶】文件操作——IO

摄影分享~ 文章目录 文件文件路径&#xff08;Path&#xff09; 文件的类型Java中操作文件File概述 文件内容的读写——数据流字节流InputStream概述OutputStream 概述字符流FileInputStream 概述利用 Scanner 进行字符读取 实例练习 文件 文件&#xff1a;File这个概念&…

PostSQL内存管理之内存上下文

瀚高数据库 目录 环境 文档用途 详细信息 环境 系统平台&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;14 文档用途 了解pg内存分配 详细信息 1.MemoryContex机制 内存上下文是pg相关的内存控制结构&#xff0c;树形结构组织下的内存上下文能在频繁的…

SNMPc软件的下载和安装教程,计算机网络管理,网络工程师

⬜⬜⬜ &#x1f430;&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;(*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;&#x1f430;⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &am…

vue 改变数据后,数据变化页面不刷新

文章目录 导文文章重点方法一&#xff1a;使用this.$forceUpdate()强制刷新方法二&#xff1a;Vue.set(object, key, value)方法三&#xff1a;this.$nextTick方法四&#xff1a;$set方法 导文 在vue项目中&#xff0c;会遇到修改完数据&#xff0c;但是视图却没有更新的情况 v…

让开发者成为创新主体 | 阿里云云原生4月动态

作者&#xff1a;云原生内容小组 云原生月度动态 ✦ 云原生是企业数字创新的最短路径。 《阿里云云原生每月动态》&#xff0c;从趋势热点、产品新功能、服务客户、开源与开发者动态等方面&#xff0c;为企业提供数字化的路径与指南。 本栏目每月更新。 01 趋势热点 &…

vue - 实现登录后用户无操作后自动退出登录功能,当用户鼠标不动、键盘不动、无窗口滚动时自动清除登录状态(可自定义删减条件,详细示例源码一键复制开箱即用)

需求 很多教程都是无效而且有bug。。很难用索性自己搞了最健壮的解决方案。 在vue项目中,实现自动检测用户没有【移动鼠标】【操作键盘】【窗口滚动】时,自动清除登录信息强制退出登录下线,支持自定义触发时间(比如无操作10分钟就执行),自定义条件(比如只监听用户鼠标是…

匿名对象以及临时空间

目录 大纲 1.何为匿名对象 2.产生匿名对象的四种情况&#xff1a; 1&#xff09;给初始化对象时 2&#xff09;以值的方式给函数传参&#xff1b; 3&#xff09;类型转换&#xff1b; 4&#xff09;函数返回时&#xff1b; 3.编译器优化 I.在同一行代码的优化 II.在函…

电脑关机很慢怎么办?这5个方法很有用!

案例&#xff1a;电脑关机很慢怎么办&#xff1f; 【我的电脑才买来不久&#xff0c;现在每次关机都很慢&#xff0c;有时甚至一直在转圈圈无法关机&#xff0c;怎么处理这种情况呢&#xff1f;】 如果使用电脑时间长了&#xff0c;我们可能会发现电脑的各项性能都会有所下降…

Vue3(5)插槽Slots

目录 一、插槽内容与出口 二、渲染作用域 三、默认内容 四、具名插槽 五、作用域插槽 六、具名作用域插槽 一、插槽内容与出口 在之前的博文中&#xff0c;我们已经了解到组件能够接收任意类型的JS值作为props&#xff0c;但组件要如何接收模板内容呢&#xff1f;在某些…

图片堆叠、多重聚焦的几种办法

当拍摄的物品较小&#xff0c;景深较深时&#xff0c;相机的焦点只能放在较近或者较远的一处&#xff0c;图片的整个画面就不能保证完全清晰&#xff0c;多重聚焦的原理其实就是拼合&#xff0c;在画幅的不同处拍摄聚焦图片&#xff0c;将各个聚焦的内容拼合在一起&#xff0c;…

杂记 2023.5.11

目录 come across(as).. 与异性对话经验和理论、策略 单词记忆 机器学习 come across(as).. 这个用法在口语里超级高频&#xff0c;表示「给人.印象&#xff0c;让人觉得..」&#xff0c;s后面可跟名词、形容词、 being形容词。 我们再来看几个例子&#xff1a; ◆He comes ac…

【Leetcode -455.分发饼干 -459.重复的字符串】

Leetcode Leetcode -455.分发饼干Leetcode - 459.重复的字符串 Leetcode -455.分发饼干 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩…

多语言的APP外包开发流程

很多创业的人希望把APP做成多国家使用的模式&#xff0c;尤其是一些小游戏开发&#xff0c;很多小游戏玩法全世界都是一样的&#xff0c;这样开发一次就可以在全球推广。在开发这种类型软件的过程中需要注意哪些呢&#xff0c;今天和大家分享这方面的知识。北京木奇移动技术有限…

C语言入门篇——字符串,内存函数

目录 1、字符串函数 1.1求字符串长度 1.1.1strlen函数 1.2长度不受限制的字符串函数 1.2.1strcpy函数 1.2.2strcat函数 1.2.3strcmp函数 1.3长度受限制的字符串函数介绍 1.3.1strncpy函数 1.3.2strncat函数 1.3.3strncmp函数 1.4字符串查找 1.4.1strstr函数 1.4.…

JavaScript实现输入年龄来判断年龄阶段是青年/中年/老年人的代码

以下为实现输入年龄来判断年龄阶段是青年/中年/老年人的程序代码和运行截图 目录 前言 一、实现输入年龄来判断年龄阶段是青年/中年/老年人 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找…

open3d 裁剪点云

目录 1. crop_point_cloud 2. crop 3. crop_mesh 1. crop_point_cloud 关键函数 chair vol.crop_point_cloud(pcd) # vol: SelectionPolygonVolume import open3d as o3dif __name__ "__main__":# 1. read pcdprint("Load a ply point cloud, crop it…

哪些蓝牙耳机戴久不疼?长时间佩戴不疼的蓝牙耳机推荐

现在的真无线耳机已经成为了人们的标配之一了&#xff0c;各个厂家也紧随其后&#xff0c;生产出了多种无线耳机&#xff0c;不少人的选购蓝牙耳机一般都是对音质、佩戴和连接&#xff0c;但通常人们佩戴蓝牙耳机都是在半天左右&#xff0c;小编专门整理了一期舒适度高的耳机&a…