双向链表的功能实现

news2024/11/23 21:55:24

      前言:我们已经学习并知道了单链表的实现,链表的实现方式不只是单链表一种,今天我们就来介绍一种新的结构,就是双链表结构,本质上是将节点间进行双向链接,从而使一些操作更加容易实现。

目录

1.双向链表的简介

2.双向链表的实现

带头节点的哨兵位的创建和初始化

为什么要使用哨兵位?

节点的头、尾插和头、尾删

插入和删除函数

完整程序代码及测试代码

List.h

List.cpp

Test.cpp

3.金句频道


1.双向链表的简介

        双向链表(Doubly Linked List)是一种常见的线性数据结构,它相比于单向链表多了一个指针,可以支持双向遍历。

       每个节点在双向链表中都有两个指针,一个指向前一个节点,一个指向后一个节点,因此可以从任意一个节点开始,依次遍历前一个节点和后一个节点。双向链表可以对数据进行插入和删除操作,这些操作相比较单向链表来说更加方便和高效。

 双向链表的特点包括:

1. 每个节点都有两个指针,一个指向前一个节点,一个指向后一个节点。
2. 可以从任意一个节点开始,依次遍历前一个节点和后一个节点。
3. 可以对数据进行插入和删除操作,保证了操作的高效性和方便性。
4. 相对于单向链表来说,占用更多的存储空间,因为需要多一个指针来指向前一个节点。

     

      需要注意的是,当插入或删除一个节点时,需要同时修改其前后两个节点的指针,才能保证链表结构的完整性。

2.双向链表的实现

带头节点的哨兵位的创建和初始化

为什么要使用哨兵位?

       链表中哨兵位(Sentinel)是一种特殊的节点,位于链表头部或尾部,不存储任何实际的数据。引入哨兵位的主要目的是为了简化链表的操作,提高代码的可读性和可维护性。

使用哨兵位的优点主要包括:

1. 简化代码逻辑:引入哨兵位后,链表的头尾节点都指向哨兵位而不是NULL,这样在任何情况下,我们都不需要对头尾节点进行特判,也不需要用二级指针进行传址了(链表中引入二级指针的目的,其实就是为了在头结点为空的情况下,节点还能正常创建并使用,说白了就是为了“对NULL指针进行解引用操作”)。这样能够简化代码逻辑,减少代码复杂度,提高代码可读性和可维护性。

2. 避免空指针异常:使用哨兵位可以避免在链表为空或链表操作时出现空指针异常。如果没有哨兵位,我们需要在头尾节点为空时进行特判,否则可能会导致程序崩溃。

LTNode* LTInit()
{
	Listnode head = (Listnode)malloc(sizeof(LTNode));
	assert(head);//断言,防止开辟不成功
	head->data = -1;
	//初始让头结点的前驱和后继都指向自己,这样可以便与判断链表是否为空并且与尾结点指向哨兵位的操作上统一
	head->next = head->prev = head;
	return head;
}

节点的头、尾插和头、尾删

        这些操作基本与单链表的操作大同小异,只是多了一个前驱结点的操作,这里需要注意以下几点:

1.注意对可能为NULL的指针进行断言避免出现野指针和堆错误问题;

2.对不再使用的空间进行及时的释放,避免内存泄漏。

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	
	assert(phead);
	LTNode* newnode = new LTNode(x);
	LTNode* first = phead->next;

	phead->next = newnode;
	newnode->prev = phead;

	newnode->next = first;
	first->prev = newnode;
	

	//LTInsert(phead->next, x);
}

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	Listnode newnode = new LTNode(x);

	//这里最好将两个及以上解引用的操作分开
	LTNode *first = phead->prev;
	first->next = newnode;
	newnode->next = phead;
	newnode->prev = first;
	phead->prev = newnode;

	//或者可以直接调用插入函数
	//LTInsert(phead, x);
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	
	LTNode* p = phead->next;
	LTNode* ppnext = p->next;

	free(p);
	phead->next=ppnext;
	ppnext->prev=phead;
	
	//LTErase(phead->next);
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

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

	//LTErase(phead->prev);
}

       细心地朋友可能已经注意到了,这些不同的函数内部都有相同的LTInsertLTErase函数,这不是偶然,我们将不同方式插入或删除的函数进行对比,就会发现,它们的代码具有一定的逻辑重复性,存在逻辑上互通的部分,我们可以将这个部分归纳为一个函数,像头插和尾插,我们可以写一个在任意节点前或后插入的函数,再将不同的节点需要的参数分别传入,就能实现不同的功能,尾删和头删也是同理,这样就大大简化了代码,减少了使用指针出错的可能性。

插入和删除函数

// 在pos之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);//断言,判断pos是否合法

	Listnode newnode = new LTNode(x);
	Listnode pre = pos->prev;
	pre->next = newnode;
	newnode->next = pos;
	newnode->prev = pre;
	pos->prev = newnode;

}

// 删除pos位置的值
void LTErase(LTNode* pos)
{
	assert(pos);
	Listnode front = pos->prev;
	Listnode behind = pos->next;
	front->next = behind;
	behind->prev = front;
	free(pos);
	pos = NULL;
}

完整程序代码及测试代码

List.h

#pragma once

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

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
	//C++构造函数//可不写
	ListNode(LTDataType d = 0, ListNode* pr = NULL, ListNode* ne = NULL)
	{
		data = d, prev = pr, next = ne;
	}
}LTNode,*Listnode;

//创建并初始化头结点
LTNode* LTInit();

//打印带头结点的单链表
void LTPrint(LTNode* phead);

//判断链表是否为空
bool LTEmpty(LTNode* phead);

//尾插
void LTPushBack(LTNode* phead, LTDataType x);

//头插
void LTPushFront(LTNode* phead, LTDataType x);

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

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

//查找值为x的节点
LTNode* LTFind(LTNode* phead, LTDataType x);

// 在pos之前插入
void LTInsert(LTNode* pos, LTDataType x);

// 删除pos位置的值
void LTErase(LTNode* pos);

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

List.cpp

#include <bits/stdc++.h>
#include "List.h"
using namespace std;

LTNode* LTInit()
{
	Listnode head = (Listnode)malloc(sizeof(LTNode));
	assert(head);//断言,防止开辟不成功
	head->data = -1;
	//初始让头结点的前驱和后继都指向自己
	head->next = head->prev = head;
	return head;
}
void LTPrint(LTNode* phead)
{
	assert(phead);
	printf("guard");
	Listnode p = phead->next;
	while (p!= phead)
	{
		printf("->%d", p->data);
		p = p->next;
	}
	printf("\n");
}

// 在pos之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);//断言,判断pos是否合法

	Listnode newnode = new LTNode(x);
	Listnode pre = pos->prev;
	pre->next = newnode;
	newnode->next = pos;
	newnode->prev = pre;
	pos->prev = newnode;

}
bool LTEmpty(LTNode* phead)
{
	return phead->next == phead;
}
void LTPushBack(LTNode* phead, LTDataType x)
{
	Listnode newnode = new LTNode(x);

	//这里最好将两个及以上解引用的操作分开
	LTNode *first = phead->prev;
	first->next = newnode;
	newnode->next = phead;
	newnode->prev = first;
	phead->prev = newnode;

	//或者可以直接调用插入函数
	//LTInsert(phead, x);
}
void LTPushFront(LTNode* phead, LTDataType x)
{
	
	assert(phead);
	LTNode* newnode = new LTNode(x);
	LTNode* first = phead->next;

	phead->next = newnode;
	newnode->prev = phead;

	newnode->next = first;
	first->prev = newnode;
	

	//LTInsert(phead->next, x);
}
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

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

	//LTErase(phead->prev);
}
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	
	LTNode* p = phead->next;
	LTNode* ppnext = p->next;

	free(p);
	phead->next=ppnext;
	ppnext->prev=phead;
	
	//LTErase(phead->next);
}

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	Listnode p = phead->next;
	while (p!= phead)
	{
		if (p->data == x)
			return p;
		p = p->next;
	}
	return NULL;
}


// 删除pos位置的值
void LTErase(LTNode* pos)
{
	assert(pos);
	Listnode front = pos->prev;
	Listnode behind = pos->next;
	front->next = behind;
	behind->prev = front;
	free(pos);
	pos = NULL;
}
void LTDestroy(LTNode* phead)
{
	
	assert(phead);
	LTNode* cur = (phead)->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;//此处传的是一级指针,在外部函数内修改没有作用,应该传二级指针或者在主函数内进行销毁,这里我们选择在主函数内单独进行置空
}

Test.cpp

#include <bits/stdc++.h>
#include "List.h"
using namespace std;

void TestList1()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);

	LTPrint(plist);

	LTPopBack(plist);
	LTPrint(plist);

	LTPopBack(plist);
	LTPrint(plist);

	LTPopBack(plist);
	LTPrint(plist);

	LTPopBack(plist);
	LTPrint(plist);

	//LTPopBack(plist);
	//LTPrint(plist);

	LTDestroy(plist);
	plist = NULL;
}

void TestList2()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	/*LTPopFront(plist);
	LTPrint(plist);*/

	LTDestroy(plist);
	plist = NULL;
}

void TestList3()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);

	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		LTInsert(pos, 30);
	}
	LTPrint(plist);

	LTDestroy(plist);
	plist = NULL;
}

int main()
{
	TestList1();
	TestList2();
	TestList3();

	return 0;
}

3.金句频道

      最近看到这样一句话:“当我真正开始爱自己,我睡的越来越早,也越来越喜欢锻炼。我不在纠结和焦虑,变得自信满满,去追求有意义的人和事,并为之燃烧自己的热情。我发现,人生才刚刚开始.”

 

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

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

相关文章

OpenCL编程指南-3.3类型转换

隐式类型转换 隐式类型转换是一种自动的类型转换&#xff0c;只要混合使用不同的类型&#xff0c;编译器就会完成这种隐式类型转换。这里支持表4-1中定义的标量类型&#xff08;除void、double和half以外&#xff09;的隐式转换。完成隐式转换时&#xff0c;并不只是重新解释一…

MySQL:数据库的增删查改

我们这一篇主要介绍数据库的增删查改~ 增&#xff1a;insert into 表名 value (); 删&#xff1a;delete from 表名; 查&#xff1a;select from 表名; 改&#xff1a;update 表名; 目录 1.insert&#xff08;增&#xff09; 2.select&#xff08;查询&#xff09; 2.1 全列…

Goby 漏洞更新 |Telesquare TLR-2005Ksh 路由器 ExportSettings.sh 文件下载漏洞(CVE-2021-46423)

漏洞名称&#xff1a;Telesquare TLR-2005Ksh 路由器 ExportSettings.sh 文件下载漏洞&#xff08;CVE-2021-46423&#xff09; English Name&#xff1a;Telesquare TLR-2005Ksh ExportSettings.sh file download (CVE-2021-46423) CVSS core: 7.5 影响资产数&#xff1a;2…

勒索病毒“顽疾”,没有“特效药”吗?

基础设施瘫痪、企业和高校重要文件被加密、毕业论文瞬间秒没……这就是六年前的今天&#xff0c;WannaCry勒索攻击爆发时的真实场景。攻击导致150多个国家数百万台计算机受影响&#xff0c;也让勒索病毒首次被全世界广泛关注。 六年后&#xff0c;勒索攻击仍是全球最严重的网络…

bootp引导程序协议

bootp又称为引导程序协议,我们来简单了解一下这个协议以及他的用法。 1&#xff0c;BOOTP 请求和应答均被封装在 U D P数据报中 &#xff1b; 2&#xff0c;B O O T P使用 U D P&#xff0c;且通常需与 T F T P协同工作&#xff1b; 3&#xff0c;B O O T P有两个熟知端口&a…

选择合适的 MQTT 云服务:一文了解 EMQX Cloud Serverless、Dedicated 与 BYOC 版本

引言 EMQX Cloud 是基于 EMQX Enterprise 构建的一款全托管云原生 MQTT 消息服务。为了满足不同客户的需求&#xff0c;EMQX Cloud 提供了三种版本供客户选择&#xff1a;Serverless 版、专有版和 BYOC 版。 本文将简要介绍这三个版本的核心区别&#xff0c;并通过三个用户故…

【ChatGPT】体验一下ChatGPT

体验一下ChatGPT 可以帮你写代码、写邮件、编故事的神器 最近OpenAI 发布了备受期待的原型通用 ChatGPT&#xff0c;这是一种基于对话的 AI 聊天界面&#xff0c;算是GPT-3(Generative Pre-trained Transformer 3)的继承者&#xff0c;今天记录一下体验的过程&#xff0c;以前…

详解set/map的底层结构——AVL树和红黑树

目录 前文 一&#xff0c;AVL树 1.1 什么是AVL树&#xff1f; 1.2 AVL树节点的定义 1.3 insert—插入(重点) 1.4 旋转(重点) 1.4.1 右单旋 1.4.2 左单旋 1.4.3 左右双旋 1.4.4 右左双旋 1.5 IsBalanc(平衡判断) 1.6 中序遍历 1.7 测试 二&#xff0c;红黑树 2.1 什么…

非常提效的7款原型工具推荐

原型图工具允许在开发前进行测试和迭代过程&#xff0c;可以帮助节省大量的开发时间和成本。在本文中&#xff0c;我们盘点了7个易于使用的原型图工具&#xff0c;以提高您的生产力&#xff01; 1.即时设计 即时设计是一款免费的在线 UI 设计工具&#xff0c;无系统限制&…

自学黑客,一般人我劝你还是算了吧!

我为啥说自学黑客&#xff0c;一般人我还是劝你算了吧&#xff01;因为我就是那个不一般的人。 首先我谈下对黑客&网络安全的认知&#xff0c;其实最重要的是兴趣热爱&#xff0c;不同于网络安全工程师&#xff0c;他们大都是培训机构培训出来的&#xff0c;具备的基本都是…

【Python入门】Python的判断语句(if elif else语句)

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于Python零基础入门系列&#xff0c;本专栏主要内容为Python基础语法、判断、循环语句、函…

从0开始学习数据库

一个数据库最重要的部分是什么&#xff1f; 关系型数据库mysql有着四大特性&#xff0c;原子性&#xff0c;隔离性&#xff0c;一致性&#xff0c;持久性。 kv数据库有着原子性&#xff0c;持久性&#xff0c;弱一致性。 可见&#xff0c;不管数据库的存储引擎是什么&#xff0…

【计算机网络】第一章 计算机网络基础(期末急救包)

目录 前言 正文 考点 1.1 计算机网络组成 1.2 计算机网络的分类 ——4种 1.3计算机网络的性能指标 1.4 计算机网络标准化工作及相关组织 2.计算机网络体系结构与模型 结语 前言 期末将至&#xff0c;相信有的同学们还在为怎么过期末而发愁吧&#xff01;不用担心&#…

谈「效」风生 | 「自动化」聊起来简单,做起来难

#第4期&#xff1a;“自动化”聊起来简单&#xff0c;做起来难# 在上一期《如何找到现有研发体系的「内耗问题」》中&#xff0c;我们聊了评估现有研发体系&#xff0c;正确的找到“体系内耗问题”&#xff0c;是改变研发体系的第一步。本期我们继续聊下一个关键点就是研发体系…

多USB工业相机的使用

USB相机的使用 USB3.0引入了“SuperSpeed”(SS)传输速率。理论传输速度高达625 MByte/s, SuperSpeed传输可以在短时间内传输大量数据&#xff0c;适用于许多视觉应用。给出的带宽上限是一个理想化的理论值。对于实际应用&#xff0c;主机控制器&#xff08;Host Controller&am…

红黑树下岗,内核新数据结构上场:maple tree!

在外界看来&#xff0c;Linux 内核的内部似乎变化很少&#xff0c;尤其是像内存管理子系统&#xff08;memory-management subsystem&#xff09;这样的子系统。然而&#xff0c;开发人员时常需要更换内部接口来解决某些长期存在的问题。比如&#xff0c;其中一个问题就是用来保…

五个程序员必要的在线绘图工具

说到程序员&#xff0c;每个人的第一反应都是敲代码。事实上&#xff0c;画图也是程序员必备的技能之一。各种流程图、架构图、UML类图、线框图等多种多样。 1.即时设计 即时设计是一款免费的在线 UI 设计工具&#xff0c;无系统限制&#xff0c;浏览器打开即可使用&#xff…

算法修炼之练气篇——练气七层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

Recoil在React中完整实践方案

先让我吐槽一下&#xff0c;Recoil这个玩意文档是真的不友好&#xff0c;另外发现国内很少有人去用Recoil&#xff0c;然后好多文章都是照搬官网文档&#xff0c;我特喵的要是出了问题直接看官方不就行了。如果你碰巧看到这个文章了&#xff0c;就细心看完吧&#xff0c;绝对的…

从一文不值到数字黄金 诞生于极客圈的比特币,究竟经历了什么?

比特币作为技术性很强的神奇发明&#xff0c;从一文不值到数字黄金&#xff0c;在发展过程中不仅为金融范式转变奠定了基础&#xff0c;改变了人们感知和交易价值的方式&#xff0c;也为无数数字资产开辟了一条可追随的道路。 比特币之所以复杂&#xff0c;是因为技术属性、金融…