链表剖析及自己手撸“单链表“实现基本操作(初始化、增、删、改等)

news2024/11/26 2:22:02

一. 基础

1. 前言

  链式存储结构,又叫链表,逻辑上相邻,但是物理位置可以不相邻,因此对链表进行插入和删除时不需要移动数据元素,但是存取数据的效率却很低,分为三类:

(1).单(向)链表:存储数据元素时,除了存储数据元素本身的信息外,还有一个指针,指向他的后继节点,最后一个元素的指针不指向任何元素。

【C#中的代表:ListDictionary,基本上已经被弃用了,官方称建议存10个元素以下,很鸡肋】

(2).双(向)链表:在单链表的基础上加一个前驱指针,指向前一个元素,是链表可以双向查找,这种结构成为双链表。

【C#中的代表:LinkedList<T>】

(3).循环链表:将链表最后一个节点的指针指向第一个节点,这样就形成了一个环,这种数据结构叫做单向循环列表.另外还有双向循环列表.

PS:循环链表结构中从表的任一节点出发均可以找到表中的其他节点如果从表头节点出发,访问链表的最后一个节点,必须扫描表中所有节点。若循环链表的头指 针改用尾指针代替,则从尾指针出发不仅可以立即访问到最后一个节点,而且十分方便的找到第一个节点。

2. LinkedList<T>核心分析

  首先LinkedList是一个双向链表!!,节点的存储类型为LinkedListNode<T>,可以通过node.Next和node.Previous获取下一个节点或前一个节点,添加节点的时候主要是基于 一个节点往前加或者往后加(AddBefore,AddAfter).

(1).增加:AddFirst、AddLast、AddBefore、AddAfter

(2).删除:Remove、RemoveFirst、RemoveLast; 其中Remove是根据值反删节点(内部循环,慢啊),也可以查出节点进行删除

(3).修改: node.Value = xxx;

(4).查询:

  A. 获取第1个/最后一个节点:First 、Last (.Value 就获取值了)

  B. 根据值反查节点: Find、FindLast (内部遍历,慢啊,链表的通病,查询慢)

  C. 获取下一个节点: node.Next (node.Next.Value; 获取值了)

  D. 获取前一个节点: node.Previous (node.Previous.Value; 获取值了)

  E. 输出所有值: Foreach

(5).其它:Contains、Count

代码分享:

 1 {
 2                 LinkedList<int> list1 = new LinkedList<int>();
 3                 list1.AddFirst(1);
 4                 list1.AddLast(10);
 5                 //找到节点
 6                 LinkedListNode<int> node1 = list1.Find(10);
 7                 Console.WriteLine($"node1的节点的值为:{node1.Value}");
 8                 list1.AddBefore(node1, 9);
 9                 list1.AddAfter(node1, 11);
10 
11                 //此时在获取node1的前一个节点和后一个节点
12                 var node1Pre = node1.Previous;
13                 Console.WriteLine($"node1的前一个节点的值为:{node1Pre.Value}");
14                 var node1Next = node1.Next;
15                 Console.WriteLine($"node1的的后一个节点的值为:{node1Next.Value}");
16 
17                 //修改节点的值
18                 node1Next.Value = 111;
19 
20                 //继续增加
21                 list1.AddLast(14);
22                 list1.AddLast(15);
23                 list1.AddLast(16);
24                 list1.AddLast(17);
25                 list1.AddLast(18);
26 
27                 //删除节点
28                 list1.Remove(16);
29                 var dNode = list1.Find(17);
30                 list1.Remove(dNode);
31                 //删除最后一个节点
32                 list1.RemoveLast();
33 
34                 Console.WriteLine("全部输出");
35                 foreach (var item in list1)
36                 {
37                     Console.WriteLine(item);
38                 }
39             }

运行结果:

更多C++后台开发技术点知识内容包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,音视频开发,Linux内核,TCP/IP,协程,DPDK多个高级知识点。

C/C++Linux服务器开发高级架构师/C++后台开发架构师​免费学习地址

【文章福利】另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以点击领取

二. 提高

1.手撸单链表

 (1).首先要有一个节点类Node,需要能记录该节点的值Data,并且根据该节点能获取下一个节点Next.实例化的时候可以传值,也可以不传值

 (2).要有一个头结点作为基础、节点的个数;实例化的时候可以传值,也可以不传值.

 (3).先要编写一个方法GetNodeByIndex,根据索引来获取节点,实质上只能遍历,其它方法基本上都要基于它.

 (4).然后实现Add、Insert、RemoveAt、this[int index]、ToString等方法,需要注意的是越界问题、index=0问题、头结点为空的问题.

最终达到:获取一个节点,可以通过Data获取节点值,通过Next获取下一个节点,然后可以增删改查.

PS:单链表也好,双链表也罢,只要不是循环列表,尾节点的指针都是指向null.

Node节点代码

    /// <summary>
    /// 节点类
    /// </summary>
    public class Node<T>
    {
        public T _data;  // 存放节点数据
        public Node<T> _next; //存放下一个节点

        public Node()
        {

        }
        public Node(T data)
        {
            _data = data;
        }

        /// <summary>
        /// 数据的输出
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return _data.ToString();
        }

        //当前节点的数据
        public T Data
        {
            get
            {
                return _data;
            }
            set
            {
                _data = value;
            }
        }
        //下一个节点
        public Node<T> Next
        {
            get
            {
                return _next;
            }
            set
            {
                _next = value;
            }
        }
    }

单链表代码

  1 /// <summary>
  2     /// 手写单链表
  3     /// </summary>
  4     public class MySingleLinkedList<T>
  5     {
  6         private Node<T> _header;  //头结点
  7         private int _count;  //元素个数
  8         public MySingleLinkedList()
  9         {
 10 
 11         }
 12         public MySingleLinkedList(T data)
 13         {
 14             _header = new Node<T>(data);
 15             _count++;
 16         }
 17         //只能读
 18         public int Count
 19         {
 20             get
 21             {
 22                 return _count;
 23             }       
 24         }
 25         /// <summary>
 26         /// 根据索引获取元素
 27         /// </summary>
 28         /// <param name="index"></param>
 29         /// <returns></returns>
 30         public Node<T> GetNodeByIndex(int index)
 31         {
 32             if (index < 0 || index>=_count)
 33             {
 34                 throw new ArgumentOutOfRangeException("索引越界");
 35             }
 36             Node<T> tempNode = _header;
 37             //当index为0的时候,不进入for循环
 38             for (int i = 0; i < index; i++)
 39             {
 40                 tempNode = tempNode.Next;
 41             }
 42             return tempNode;
 43         }
 44 
 45         //索引器获取和设置数据
 46         public T this[int index]
 47         {
 48             get
 49             {
 50                 return GetNodeByIndex(index).Data;
 51             }
 52             set
 53             {
 54                 GetNodeByIndex(index).Data = value;
 55             }
 56         }
 57 
 58         /// <summary>
 59         /// 添加元素(最后)
 60         /// </summary>
 61         /// <param name="data"></param>
 62         public void Add(T data)
 63         {
 64             Node<T> newNode = new Node<T>(data);
 65             if (_header==null)  //表示添加的是第一个节点
 66             {
 67                 _header = newNode;
 68             }
 69             else
 70             {
 71                 var lastNode = GetNodeByIndex(_count - 1);
 72                 lastNode.Next = newNode;
 73             }       
 74             _count++;
 75         }
 76 
 77         /// <summary>
 78         /// 插入元素
 79         /// </summary>
 80         /// <param name="index">索引</param>
 81         /// <param name="data">数据</param>
 82         public void Insert(int index,T data)
 83         {
 84             if (index < 0||index>_count)
 85             {
 86                 throw new ArgumentOutOfRangeException("索引越界");
 87             }
 88             var newNode = new Node<T>(data); //新节点
 89             if (index==0)
 90             {
 91                 //头结点为空,直接插入头结点即可
 92                 if (_header==null)
 93                 {
 94                     _header = newNode;
 95                 }
 96                 //头结点有元素,需要把头结点的位置让出来
 97                 else
 98                 {
 99                     newNode.Next = _header;
100                     _header = newNode;
101                 }
102             }
103             else
104             {
105                 var preNode = GetNodeByIndex(index-1);  //查找插入位置的前驱节点
106                 var nextNode = preNode.Next;            //插入位置的后继节点
107                 preNode.Next = newNode;        //前驱结点的后继节点为新节点
108                 newNode.Next = nextNode;       //新节点的后继节点执行原来前驱的后继
109             }
110             _count++;             
111         }
112 
113         /// <summary>
114         /// 根据索引删除元素
115         /// </summary>
116         /// <param name="index">索引</param>
117         public void RemoveAt(int index)
118         {
119             if (index < 0 || index >= _count)
120             {
121                 throw new ArgumentOutOfRangeException("索引越界");
122             }
123             if (index==0)  //删除头结点
124             {
125                 if (_header==null)
126                 {
127                     throw new ArgumentOutOfRangeException("索引越界");
128                 }
129                 _header = _header.Next;
130             }
131             else
132             {
133                 var preNode = GetNodeByIndex(index - 1); //删除节点的前驱节点
134                 if (preNode.Next==null)
135                 {
136                     throw new ArgumentOutOfRangeException("索引越界");
137                 }
138                 preNode.Next = preNode.Next.Next;     //如果删除的是最后一个节点,那么它的前一个节点指向null
139             }
140             _count--;
141         }
142         /// <summary>
143         /// 元素输出
144         /// </summary>
145         /// <returns></returns>
146         public override string ToString()
147         {
148             string s = "";
149             Node<T> temp = _header;
150             while (temp != null)
151             {
152                 s += temp.ToString() + " ";
153                 temp = temp.Next;
154             }
155             return s;
156         }
157 
158     }

测试代码

 1             {
 2                 Console.WriteLine("--------------------------手撸单链表测试---------------------------");
 3                 MySingleLinkedList<int> mlist = new MySingleLinkedList<int>();
 4                 mlist.Add(0);
 5                 mlist.Add(1);
 6                 mlist.Add(3);
 7                 mlist.Add(4);
 8                 mlist.Add(5);
 9                 mlist.Add(6);
10                 mlist.Insert(0, 100);
11                 Console.WriteLine(mlist.ToString());
12                 mlist.RemoveAt(4);
13                 mlist[1] = 999;
14                 Console.WriteLine(mlist.ToString());
15                 for (int i = 0; i < mlist.Count; i++)
16                 {
17                     Console.WriteLine(mlist[i]);
18                 }
19                 mlist.Insert(5, 555);
20                 Console.WriteLine(mlist.ToString());
21                 mlist.RemoveAt(0);
22                 Console.WriteLine(mlist.ToString());
23             }

运行结果

2.链表优缺点

优点

  (1).需要空间就申请,不需要就释放,空间有效利用。

  (2).插入和删除只需要修改几个指针,方便快捷。

  (3).双链表双向搜索,简化算法复杂度。

缺点

  (1).算法实现复杂抽象,需要额外内存空间保存节点的逻辑关系。

  (2).只能顺序访问,访问速度慢。

  (3).可能会把节点存放到托管堆的任意角落,垃圾回收需要更多开销。

原文链接:第五节:链表剖析及自己手撸"单链表"实现基本操作(初始化、增、删、改等) - Yaopengfei - 博客园

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

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

相关文章

【iconfont图标】vue引入并使用阿里巴巴iconfont图标流程

前言 为什么要使用阿里图标库&#xff1a; 图标现在是很多地方都会用到的 一般我使用的时候都是直接在ui库中比如elementul自带的一些 有时候哪怕是感觉图标不是非常适合也是用的elementul图标&#xff0c;主要原因是懒 因为能直接复制的&#xff0c;就懒得再去阿里图标库下载…

如何让Java项目兼容更多的客户端设备(一)

如何让Java项目兼容更多的客户端设备&#xff08;一&#xff09; 引入 HTTP访问是无状态的&#xff0c;&#xff08;服务器不知道是不是你访问的&#xff09;所以我们不知道每次登录的是谁 如果想实现每次登录不用重复登录&#xff0c;最简单的就是让浏览器记住用户名和密码…

球面距离计算方式(杭州到各城市的球面距离计算球面距离)

1&#xff09;杭州到各城市的球面距离 1、数据来源&#xff1a;自主计算 2、时间跨度&#xff1a;至今 3、区域范围&#xff1a;368个城市 4、指标说明&#xff1a;利用城市经纬度&#xff0c;计算球面距离 部分数据如下&#xff1a; &#xff08;2&#xff09;计算两个点之…

Sentinel配置持久化到Nacos实现流控熔断

控制台 jar 下载&#xff1a;github.com/alibaba/Sen… 启动参数 # 将控制台自身接入到sentinel nohup java -jar -Dproject.namesentinel-dashboard -Dcsp.sentinel.dashboard.serverlocalhost:8181 sentinel-dashboard-1.8.5.jar --server.port8181 &> sentinel.log …

单商户商城系统功能拆解38—分销应用—分销订单

c 下面以likeshop单商户高级版 商城系统为例进行功能拆解&#xff0c;likeshop单商户高级版商城系统可以实现快速部署&#xff0c;文档齐全&#xff0c;代码全开源&#xff0c;无加密&#xff0c;极易二次开发&#xff0c;助力企业以极低的成本上线电商业务。并且likeshop以其…

Java项目模块占用CPU过高问题分析

背景&#xff1a;近期一个模块&#xff08;暂且称为A&#xff09;新上了一个需求&#xff0c;改动了些代码&#xff0c;在测试环境部署之后&#xff0c;该模块系统CPU使用率动不动就飙升到90%&#xff0c;甚至200%&#xff0c;严重影像其他业务执行。排查过程 1.使用top查看当…

【LeetCode】813. 最大平均值和的分组

题目描述 给定数组 nums 和一个整数 k 。我们将给定的数组 nums 分成 最多 k 个相邻的非空子数组 。 分数 由每个子数组内的平均值的总和构成。 注意我们必须使用 nums 数组中的每一个数进行分组&#xff0c;并且分数不一定需要是整数。 返回我们所能得到的最大 分数 是多少。答…

eclipse中创建的Maven Module和Maven Project及其间区别

eclipse中创建Maven Module和Maven Project及其间的区别Maven Module和Maven ProjectProject和Module的关系及用法Maven Module工程必须有父工程Maven Module和Maven Project Maven Project可以理解为父工程&#xff0c;是最上级的项目&#xff0c;一般没有其他parent项目。是一…

Word控件Spire.Doc 【图像形状】教程(6): 如何在 C#、VB.NET 的 Word 文档中插入形状和形状组

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

天河超算,安装Hypre

1)下载&#xff0c;解压 略 得到目录&#xff1a;hypre-master 源文件在src目录下面 src包含如下目录&#xff1a; 2&#xff09;cmake编译 版本比较新 HYPRE_RELEASE_NAME hypre HYPRE_RELEASE_VERSION 2.26.0 HYPRE_RELEASE_DATE 2022/10/14 HYPRE_RELEASE_TIM…

CT正投影算法归纳

一、CT重建基本原理 CT 系统成像基本模型可以描述成&#xff1a;   f为待重建图像&#xff0c;P为投影数据&#xff0c;W为系统矩阵&#xff0c;其中&#xff0c;投影矩阵反映探测器上的投影与重建物体的关系&#xff0c;其模型刻画对于重建速度和精度有着重要影响。 二、…

Java:多线程基础(一)-创建线程的两种方式

目录 线程与进程 Thread类 创建线程的两种方式 方式1&#xff1a;继承Thread类 方式2&#xff1a;实现Runnable接口 * 方式2延伸&#xff1a;实现Callable接口 Callable接口 Futuretask类 返回线程计算结果&#xff1a;get() Callable&#xff1a;创建子线程…

Redis实战之共享session + jwt 实现登录拦截、刷新token

共享session问题 每个tomcat中都有一份属于自己的session,假设用户第一次访问第一台tomcat&#xff0c;并且把自己的信息存放到第一台服务器的session中&#xff0c;但是第二次这个用户访问到了第二台tomcat&#xff0c;那么在第二台服务器上&#xff0c;肯定没有第一台服务器…

百度边止血边扩张

在经过一系列的“内部调整”之后&#xff0c;百度交出了一份超预期的财报。北京时间11月22日&#xff0c;百度发布了截至2022年9月30日的第三季度财务报告。第三季度&#xff0c;百度实现营收325.4亿元&#xff0c;同比增长2%&#xff1b;归属百度的净利润&#xff08;non-GAAP…

在线杂志小程序开发,开启在线阅读时代

互联网技术的快速发展&#xff0c;让人们越来越依赖从网络上查看各种新闻资讯。传统的纸质杂志已经很难顺应时代的发展脚步&#xff0c;但是人们对于杂志的需求并没有减少。因此为了更好的满足众多读者对于杂志的需求&#xff0c;开发在线杂志小程序是十分必要的。在线杂志阅读…

时光倒流-第12届蓝桥杯Scratch选拔赛真题精选

[导读]&#xff1a;超平老师计划推出Scratch蓝桥杯真题解析100讲&#xff0c;这是超平老师解读Scratch蓝桥真题系列的第88讲。 蓝桥杯选拔赛每一届都要举行4~5次&#xff0c;和省赛、国赛相比&#xff0c;题目要简单不少&#xff0c;再加上篇幅有限&#xff0c;因此我精挑细选…

医院预约小程序源码,挂号陪护就医功能,提供全方位服务

随着人口老龄化形式加剧&#xff0c;年轻人工作压力大&#xff0c;没有闲余时间陪伴父母老人&#xff0c;因此针对解决独自去医院排队以及现代化设备需要等要求&#xff0c;而衍生出来的一个新型行业-挂号陪护。医院预约小程序源码开发的出现无非就是解决了这一难题&#xff0c…

教培行业迎来重大变局,三大方向或成新机遇

“双减”政策落地&#xff0c;教培行业迎来重大变局。校内教育深化改革正在路上&#xff0c;而学科类机构或将踏上转型之路&#xff0c;结合政策和市场来看&#xff0c;素质教育类、职业教育类、教育数字化3大方向或成新机遇。 “双减”的总体思路是什么呢&#xff1f; 教育部有…

[附源码]计算机毕业设计SpringbootON-FIT

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…