c# 数据结构 链表篇 有关双向链表的一切

        本人能力有限,如有不足还请斧正

目录

0.双向链表的好处

1.双向链表的分类

2.不带头节点的标准双向链表

节点类:有头有尾

链表类:也可以有头有尾 也可以只有头

头插

尾插

遍历

全部代码

3.循环双向链表

节点类

链表类

头插

尾插

遍历

全部代码


 

0.双向链表的好处

优势维度具体好处说明 / 示例对比单向链表的核心差异
双向遍历能力支持正向(next)和反向(prev)遍历,可灵活选择遍历方向- 可从任意节点出发,向前后两个方向遍历链表
- 例如:实现浏览器历史记录的 “前进 / 后退” 功能,直接通过 prev/next 指针操作
单向链表仅能单向遍历,反向操作需从头重新遍历
插入 / 删除效率已知当前节点时,插入 / 删除操作时间复杂度为 O (1),无需提前获取前驱节点- 插入时,通过当前节点的 prev 指针直接找到前驱,更新前后节点的指针即可
- 删除时,直接通过 prev 和 next 指针连接前后节点,无需遍历查找前驱
单向链表删除 / 插入节点时,若无前驱节点引用,需 O (n) 时间遍历查找前驱
定位便利性可直接通过节点的 prev 指针反向定位前驱节点,无需额外存储或遍历- 在链表中间节点操作时,无需维护额外变量记录前驱
- 例如:实现双向队列(双端队列)的头尾插入 / 删除操作,可直接通过指针快速定位
单向链表需从头遍历才能找到前驱节点,定位效率低
边界操作简化处理头节点和尾节点的插入 / 删除时更简单,无需特殊处理头指针(若带头节点)- 带头节点的双向链表中,头节点和尾节点的操作与中间节点逻辑一致
- 例如:删除头节点时,直接通过头节点的 next 找到第一个数据节点,更新其 prev 为 null(非循环情况)
单向链表删除头节点需单独处理头指针,边界条件易出错
应用场景适配适合需要双向操作或频繁前后移动的场景- 操作系统进程调度队列(需快速调整进程优先级,前后移动节点)
- LRU 缓存淘汰算法(需快速删除最近最少使用节点并插入到头部)
单向链表无法高效支持反向操作,需额外数据结构辅助
数据一致性指针操作更安全,减少空指针异常风险(尤其在循环双向链表中)- 循环双向链表中,头节点 prev 指向尾节点,尾节点 next 指向头节点,避免首尾指针为 null 的情况
- 适合对稳定性要求高的场景(如内核数据结构)
单向链表尾节点 next 为 null,反向遍历时易触发空指针错误
算法灵活性支持更复杂的算法逻辑,如双向搜索、回退操作- 在双向链表中实现 “双指针搜索”(如从头尾同时向中间遍历)
- 支持撤销操作(如文本编辑器的撤销 / 重做,通过双向指针回退历史版本)
单向链表需额外栈结构记录历史节点,增加空间复杂度

1.双向链表的分类

分类标准类型核心特点示意图(简化)典型应用场景
是否带头节点带头节点双向链表- 头部有一个固定的头节点(不存储数据),头节点的next指向第一个数据节点
- 尾节点的prev指向头节点(非循环时头节点prevnull
头节点(H) <-> 数据节点1 <-> 数据节点2 <-> ... <-> 尾节点(T)〔T.prev=H,H.next=数据节点1〕频繁进行插入 / 删除操作的场景(如链表初始化、边界操作更便捷)
(本文演示一)不带头节点双向链表- 直接以第一个数据节点作为头节点,头节点prevnull
- 尾节点nextnull
数据节点1 <-> 数据节点2 <-> ... <-> 尾节点(T)〔T.next=null,数据节点1.prev=null〕内存资源敏感场景(节省头节点空间)
是否循环非循环双向链表- 头节点prevnull,尾节点nextnull
- 链表头尾不相连
null <-> 头节点 <-> ... <-> 尾节点 <-> null单向遍历需求不高,但需双向操作的场景(如文件系统目录结构)
 循环双向链表- 头节点prev指向尾节点,尾节点next指向头节点
- 形成一个环形结构,可从任意节点出发遍历整个链表
头节点(H) <-> ... <-> 尾节点(T) <-> H循环数据处理(如循环缓冲区、操作系统进程调度队列)
节点结构扩展标准双向链表(和第一行重复)- 每个节点包含prev(前驱指针)和next(后继指针)
- 存储单一数据元素
节点: prev <-> data <-> next通用双向操作场景(如浏览器历史记录的前进 / 后退)
(本文不做演示)双向链表带附加属性- 节点额外包含其他属性(如优先级、时间戳等)
- 结构上仍保持双向指针
节点: prev <-> data <-> next <-> extra_attr复杂数据管理(如任务调度链表、带权重的链表)

2.不带头节点的标准双向链表

图解模型

每一个节点类Node 都有三个元素:前项 数据 后项

注意因为c#不用指针 所以所谓Prev和Next指向的都是节点类Node 

节点类:有头有尾

public class Node {public int data;public Node prev;public Node next;public Node(int data, Node prev = null, Node next=null) { this.data = data;this.prev = prev;this.next = next;}
}

链表类:也可以有头有尾 也可以只有头

        这里你可能会有一些疑问 怎么又出现了一个HeadNode和一个TailNode呢?
        这是因为链表类需要这两个去抽象的节点以方便管理

public class DoublyLinkedList {public Node headNode;public Node tailNode;public DoublyLinkedList() {headNode = tailNode = null;}

头插

乾坤大挪移

    public void HeadAdd(int data) {//如果链表为空if (headNode == null) { headNode = tailNode = new Node(data);}//双向链表的特殊性: 修改头节点时,需要把新节点的前项 后项 都挂上Node newNode = new Node(data, null, headNode);headNode.prev = newNode;//改变链表头headNode = newNode;}

尾插

    public void TailAdd(int data) {//如果尾巴为空 说明头也没有 所以下面判断头尾都可以if (headNode  == null){   //if (tailNode ==null)headNode = tailNode = new Node(data);}//双向链表的特殊性: 修改尾节点时,需要把新节点的前项 后项 都挂上Node newNode = new Node(data, tailNode, null);tailNode.next = newNode;//改变链表尾tailNode =newNode ;}

无需遍历找前驱节点 找到Target直接调换其前后指针指向即可

 public void DeleteValue(int data){if (headNode == null) return;Node current = headNode;while (current != null){if (current.data == data){//如果匹配到了则可能出现以下情况://1 删除的是头节点if (current.prev == null)headNode = current.next;//2.删除的是尾巴if (current.next == null)tailNode = current.prev;//3.删除的是中间节点current.prev.next = current.next;current.next.prev = current.prev;return;}current = current.next;}}

查询找到的第一个目标

    public bool SearchValue(int data){if (headNode == null) return true;Node current = headNode;while (current != null){if (current.data == data){Console.WriteLine("找到了目标"+data);return true;}current = current.next;}Console.WriteLine("没有目标" + data);return false;}

        关于改就是查的子集 只需要加一两行代码即可 所以不做演示

遍历

可以双向遍历链表哦

 #region 遍历打印/// <summary>/// 正向打印链表:按顺序输出链表中每个节点的数据/// </summary>public void PrintListForward(){// 从链表头节点开始遍历Node current = headNode;while (current != null){// 输出当前节点的数据Console.Write(current.data + " ");// 移动到下一个节点current = current.next;}Console.WriteLine();}/// <summary>/// 反向打印链表:按逆序输出链表中每个节点的数据/// </summary>public void PrintListBackward(){// 从链表尾节点开始遍历Node current = tailNode;while (current != null){// 输出当前节点的数据Console.Write(current.data + " ");// 移动到前一个节点current = current.prev;}Console.WriteLine();}

全部代码

using System;public class Node
{public int data;public Node prev;public Node next;public Node(int data, Node prev = null, Node next = null){this.data = data;this.prev = prev;this.next = next;}
}public class DoublyLinkedList
{public Node headNode;public Node tailNode;public DoublyLinkedList(){headNode = tailNode = null;}#region 增/// <summary>/// 头插法/// </summary>public void HeadAdd(int data){//如果链表为空if (headNode == null){headNode = tailNode = new Node(data);}else{//双向链表的特殊性: 修改头节点时,需要把新节点的前项 后项 都挂上Node newNode = new Node(data, null, headNode);headNode.prev = newNode;//改变链表头headNode = newNode;}}public void TailAdd(int data){//如果尾巴为空 说明头也没有 所以下面判断头尾都可以if (headNode == null){//if (tailNode ==null)headNode = tailNode = new Node(data);}else{//双向链表的特殊性: 修改尾节点时,需要把新节点的前项 后项 都挂上Node newNode = new Node(data, tailNode, null);tailNode.next = newNode;//改变链表尾tailNode = newNode;}}#endregion#region 删public void DeleteValue(int data){if (headNode == null) return;Node current = headNode;while (current != null){if (current.data == data){//如果匹配到了则可能出现以下情况://1 删除的是头节点if (current.prev == null){headNode = current.next;if (headNode != null){headNode.prev = null;}else{tailNode = null;}}//2.删除的是尾巴else if (current.next == null){tailNode = current.prev;tailNode.next = null;}//3.删除的是中间节点else{current.prev.next = current.next;current.next.prev = current.prev;}return;}current = current.next;}}#endregion#region 查询public bool SearchValue(int data){if (headNode == null) return false;Node current = headNode;while (current != null){if (current.data == data){Console.WriteLine("找到了目标" + data);return true;}current = current.next;}Console.WriteLine("没有目标" + data);return false;}#endregion#region 遍历打印/// <summary>/// 正向打印链表:按顺序输出链表中每个节点的数据/// </summary>public void PrintListForward(){// 从链表头节点开始遍历Node current = headNode;while (current != null){// 输出当前节点的数据Console.Write(current.data + " ");// 移动到下一个节点current = current.next;}Console.WriteLine();}/// <summary>/// 反向打印链表:按逆序输出链表中每个节点的数据/// </summary>public void PrintListBackward(){// 从链表尾节点开始遍历Node current = tailNode;while (current != null){// 输出当前节点的数据Console.Write(current.data + " ");// 移动到前一个节点current = current.prev;}Console.WriteLine();}#endregion
}

3.循环双向链表

        循环链表就是将头节点的前项和尾节点的后项连到同一个节点

        简称:貂蝉在一起了 噗噗

  headNode.prev = newNode;tailNode.next = newNode;

节点类

并没有什么区别

public class Node
{public int data;public Node prev;public Node next;public Node(int data){this.data = data;this.prev = null;this.next = null;}
}

链表类

也没有什么区别

public class DoublyCircularLinkedList
{public Node headNode;public Node tailNode;public DoublyCircularLinkedList(){headNode = tailNode = null;}

头插

只是将头节点的前项 和 尾节点的后项 连接在了一起

   /// <summary>/// 头插法/// </summary>public void HeadAdd(int data){Node newNode = new Node(data);if (headNode == null){headNode = tailNode = newNode;newNode.next = newNode;newNode.prev = newNode;}else{newNode.next = headNode;newNode.prev = tailNode;headNode.prev = newNode;tailNode.next = newNode;headNode = newNode;}}

尾插

    public void TailAdd(int data){Node newNode = new Node(data);if (tailNode == null){headNode = tailNode = newNode;newNode.next = newNode;newNode.prev = newNode;}else{newNode.next = headNode;newNode.prev = tailNode;tailNode.next = newNode;headNode.prev = newNode;tailNode = newNode;}}

    public void DeleteValue(int data){if (headNode == null) return;Node current = headNode;do{if (current.data == data){if (current.next == current){headNode = tailNode = null;}else{if (current == headNode){headNode = current.next;}if (current == tailNode){tailNode = current.prev;}current.prev.next = current.next;current.next.prev = current.prev;}return;}current = current.next;} while (current != headNode);}

    public bool SearchValue(int data){if (headNode == null) return false;Node current = headNode;do{if (current.data == data){Console.WriteLine("找到了目标" + data);return true;}current = current.next;} while (current != headNode);Console.WriteLine("没有目标" + data);return false;}

遍历

    #region 遍历打印/// <summary>/// 打印链表:按顺序输出链表中每个节点的数据/// </summary>public void PrintList(){if (headNode == null) return;Node current = headNode;do{Console.Write(current.data + " ");current = current.next;} while (current != headNode);Console.WriteLine();}#endregion

全部代码

public class Node
{public int data;public Node prev;public Node next;public Node(int data){this.data = data;this.prev = null;this.next = null;}
}public class DoublyCircularLinkedList
{public Node headNode;public Node tailNode;public DoublyCircularLinkedList(){headNode = tailNode = null;}#region 增/// <summary>/// 头插法/// </summary>public void HeadAdd(int data){Node newNode = new Node(data);if (headNode == null){headNode = tailNode = newNode;newNode.next = newNode;newNode.prev = newNode;}else{newNode.next = headNode;newNode.prev = tailNode;headNode.prev = newNode;tailNode.next = newNode;headNode = newNode;}}public void TailAdd(int data){Node newNode = new Node(data);if (tailNode == null){headNode = tailNode = newNode;newNode.next = newNode;newNode.prev = newNode;}else{newNode.next = headNode;newNode.prev = tailNode;tailNode.next = newNode;headNode.prev = newNode;tailNode = newNode;}}#endregion#region 删public void DeleteValue(int data){if (headNode == null) return;Node current = headNode;do{if (current.data == data){if (current.next == current){headNode = tailNode = null;}else{if (current == headNode){headNode = current.next;}if (current == tailNode){tailNode = current.prev;}current.prev.next = current.next;current.next.prev = current.prev;}return;}current = current.next;} while (current != headNode);}#endregion#region 查询public bool SearchValue(int data){if (headNode == null) return false;Node current = headNode;do{if (current.data == data){Console.WriteLine("找到了目标" + data);return true;}current = current.next;} while (current != headNode);Console.WriteLine("没有目标" + data);return false;}#endregion#region 遍历打印/// <summary>/// 打印链表:按顺序输出链表中每个节点的数据/// </summary>public void PrintList(){if (headNode == null) return;Node current = headNode;do{Console.Write(current.data + " ");current = current.next;} while (current != headNode);Console.WriteLine();}#endregion
}

 

 

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

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

相关文章

Numba 从零基础到实战:解锁 Python 性能新境界

Numba 从零基础到实战&#xff1a;解锁 Python 性能新境界 一、引言 在 Python 的世界里&#xff0c;性能一直是一个备受关注的话题。Python 以其简洁易读的语法和丰富的库生态&#xff0c;深受开发者喜爱&#xff0c;但在处理一些计算密集型任务时&#xff0c;其执行速度往往…

单位门户网站被攻击后的安全防护策略

政府网站安全现状与挑战 近年来&#xff0c;随着数字化进程的加速&#xff0c;政府门户网站已成为政务公开和服务公众的重要窗口。然而&#xff0c;网络安全形势却日益严峻。国家互联网应急中心的数据显示&#xff0c;政府网站已成为黑客攻击的重点目标&#xff0c;被篡改和被…

Spring Boot 项目三种打印日志的方法详解。Logger,log,logger 解读。

目录 一. 打印日志的常见三种方法&#xff1f; 1.1 手动创建 Logger 对象&#xff08;基于SLF4J API&#xff09; 1.2 使用 Lombok 插件的 Slf4j 注解 1.3 使用 Spring 的 Log 接口&#xff08;使用频率较低&#xff09; 二. 常见的 Logger&#xff0c;logger&#xff0c;…

NI的LABVIEW工具安装及卸载步骤说明

一.介绍 最近接到个转交的项目&#xff0c;项目主要作为上位机工具开发&#xff0c;在对接下位机时&#xff0c;有用到NI的labview工具。labview软件是由美国国家仪器&#xff08;NI&#xff09;公司研制开发的一种程序开发环境&#xff0c;主要用于汽车测试、数据采集、芯片测…

cmd 终端输出乱码问题 |Visual Studio 控制台输出中文乱码解决

在网上下载&#xff0c;或者移植别人的代码到自己的电脑&#xff0c;使用VS运行后&#xff0c;控制台输出中文可能出现乱码。这是因为源代码的编码格式和控制台的编码格式不一致。 文章目录 查看源代码文件编码格式查看输出控制台编码格式修改编码格式修改终端代码页 补充总结 …

A009-基于pytest的网易云自动化测试

题 目 :基于pytest的网易云自动化测试 主要内容 综合应用所学的软件测试理论和方法,实现网易云的功能自动化测试。 (1)自动化测试介绍; (2)自动化功能测试框架介绍; (3)设计功能测试用例 (4)书写自动化测试脚本; (5)测试评价与结论。 任务要求 (1)能…

LVGL Video控件和Radiobtn控件详解

LVGL Video控件和Radiobtn控件详解 一、 Video控件详解1. 概述2. 创建和初始化3. 基本属性设置4. 视频控制5. 回调函数6. 高级功能7. 注意事项 二、Radiobtn控件详解1. 概述2. 创建和初始化3. 属性设置4. 状态控制5. 组管理6. 事件处理7. 样式设置8. 注意事项 三、效果展示四、…

AbortController:让异步操作随时说停就停

AbortController&#xff1a;让异步操作随时说停就停 一、什么是 AbortController&#xff1f; AbortController 是 JavaScript 在浏览器和部分 Node.js 环境中提供的全局类&#xff0c;用来中止正在进行或待完成的异步操作&#xff08;如 fetch() 请求、事件监听、可写流、数…

机器学习 从入门到精通 day_04

1. 决策树-分类 1.1 概念 1. 决策节点 通过条件判断而进行分支选择的节点。如&#xff1a;将某个样本中的属性值(特征值)与决策节点上的值进行比较&#xff0c;从而判断它的流向。 2. 叶子节点 没有子节点的节点&#xff0c;表示最终的决策结果。 3. 决策树的…

C++ Primer (第五版)-第十三章 拷贝控制

文章目录 概述13.1拷贝、赋值与销毁合成拷贝构造函数拷贝初始化参数和返回值拷贝初始化的限制编译器可以绕过拷贝构造函数拷贝运算符析构函数三/五原则使用default阻止拷贝合成的拷贝控制成员可能是删除的 private拷贝控制拷贝控制和资源管理行为像值的类类值拷贝赋值运算符定义…

Vue el-from的el-form-item v-for循环表单如何校验rules(一)

实际业务需求场景&#xff1a; 新增或编辑页面&#xff08;基础信息表单&#xff0c;一个数据列表的表单&#xff09;&#xff0c;数据列表里面的表单数是动态添加的。数据可新增、可删除&#xff0c;在表单保存前&#xff0c;常常需要做表单必填项的校验&#xff0c;校验通过以…

测试100问:http和https的区别是什么?

哈喽&#xff0c;大家好&#xff0c;我是十二&#xff0c;今天给大家分享的问题是&#xff1a;http和https的区别是什么&#xff1f; 首先我们要知道 HTTP 协议传播的数据都是未加密的&#xff0c;也就是明文的&#xff0c;因此呢使用 http协议传输一些隐私信息也就非常不安全&…

YOLOv3超详细解读(三):源码解析:数据处理模块

一、概述 YOLOv3&#xff08;You Only Look Once v3&#xff09;是一种高效的目标检测算法&#xff0c;其数据处理模块是训练和推理流程的核心部分。本文将深入分析Ultralytics团队基于PyTorch实现的YOLOv3源码中的数据处理模块&#xff0c;重点探讨数据加载、预处理和数据增强…

每日算法(双指针算法)(Day 1)

双指针算法 1.算法题目&#xff08;移动零&#xff09;2.讲解算法原理3.编写代码 1.算法题目&#xff08;移动零&#xff09; 2.讲解算法原理 数组划分&#xff0c;数组分块&#xff08;快排里面最核心的一步&#xff09;只需把0改为tmp 双指针算法&#xff1a;利用数组下标来…

2025蓝桥杯python A组省赛 题解

真捐款去了&#xff0c;好长时间没练了&#xff0c;感觉脑子和手都不转悠了。 B F BF BF 赛时都写假了&#xff0c; G G G 也只写了爆搜。 题解其实队友都写好了&#xff0c;我就粘一下自己的代码&#xff0c;稍微提点个人的理解水一篇题解 队友题解 2025蓝桥杯C A组省赛 题…

测试基础笔记第四天(html)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 html介绍1. 介绍2.骨架标签3.常用标签标题标签段落标签超链接标签图片标签换行和空格标签布局标签input标签&#xff08;变形金刚&#xff09;form标签列表标签 htm…

10 穴 汽车连接器的15个设计特点

汽车行业严重依赖卓越的电气系统来确保功能和可靠性。这些系统的关键组件是 10 腔连接器&#xff0c;它为布线和信号传输提供解决方案。制造商和工程师必须仔细评估这些连接器的设计特性&#xff0c;以优化性能和安全性。 本博客研究了汽车 10 腔连接器的 15 个设计特征&#…

Summary

一、数据结构 1.1 哈希 主要是HashMap和HashSet&#xff1b;其中HashSet底层是一个HashMap属性。 // 获取HashMap元素,HashSet均不支持 map.keySet (); // Set<k> map.values (; // Collection<V> map.entrySet();//Set<Map.Entry<K,V>> for (Map.E…

【Leetcode-Hot100】最小覆盖子串

题目 解答 想到使用双指针哈希表来实现&#xff0c;双指针的left和right控制实现可满足字符串。 class Solution(object):def minWindow(self, s, t):""":type s: str:type t: str:rtype: str"""len_s, len_t len(s), len(t)hash_map {}for…

Flutter 播放利器:`media_kit` 的详细介绍与使用指南

在 Flutter 项目中实现音视频播放&#xff0c;开发者过去主要依赖如 video_player、just_audio 等第三方库&#xff0c;但这些库或多或少存在一些局限性&#xff0c;比如平台兼容性差、定制能力不足、播放格式有限等问题。 而 media_kit 是近年崛起的一款全平台音视频播放解决…