概念介绍:

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素

链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据

由图可知:

  1. 链表在进行添加/删除时,只需要修改 当前节点和相邻节点 的next,更新效率高
  2. 遍历数据,需要根据节点按顺序访问,导致查询速度慢,时间复杂度为O(n)
  3. 每个节点中都保存了下一个节点的指针【Next,最后一个节点的next为null】,所以长度是动态变化的,且不是连续内存空间

相关代码:

MyLinkedListNode:自定义链表节点类

     /// <summary>
/// 自定义链表节点类: 单链表
/// </summary>
public class MyLinkedListNode<T>
{
/// <summary>
/// 当前节点
/// </summary>
public T Node { get; set; } /// <summary>
/// 下一个节点
/// </summary>
public MyLinkedListNode<T> Next { get; set; } /// <summary>
/// 构造函数: 无参构造函数
/// </summary>
/// <param name="Node"></param>
public MyLinkedListNode()
{
this.Node = default;
this.Next = null;
} /// <summary>
/// 构造函数: 指定当前节点,常用于 新增根节点和最后一个节点
/// </summary>
/// <param name="node"></param>
public MyLinkedListNode(T node)
{
this.Node = node;
this.Next = null;
} /// <summary>
/// 构造函数: 指定当前节点和下一节点,常用于 新增内部节点/确定下一节点 的情况
/// </summary>
/// <param name="next"></param>
/// <param name="Next"></param>
public MyLinkedListNode(T node, MyLinkedListNode<T> next)
{
this.Node = node;
this.Next = next;
}
}

MyLinkedList:自定义链表

     /// <summary>
/// 自定义链表
/// 功能:
/// 1.添加: 添加到集合最后面
/// 2.添加: 添加到集合最前面
/// 3.添加: 添加索引后面
/// 4.添加: 添加索引前面
/// 5.删除: 删除T
/// 6.删除: 删除指定索引
/// 7.删除: 删除第一个
/// 8.删除: 删除最后一个
/// 9.删除: 删除所有
/// </summary>
public class MyLinkedList<T>
{
/// <summary>
/// 存储链表集合-根节点:
/// 框架自带了双向链表 System.Collections.Generic.LinkedList,链表集合保存在 MyLinkedListNode 中
/// 考虑到存在clear方法,链表默认值为null更方便
/// </summary>
private MyLinkedListNode<T> _rootNode = null; // { get; set; } /// <summary>
/// 索引索引器,从0开始,根据索引返回指定索引节点信息
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T this[int index]
{
get
{
var node = GetNodeAt(index).Node;
Console.WriteLine($"this[int {index}] = {node}\r\n");
return node;
}
} #region 查询方法 /// <summary>
/// 根据索引返回指定索引节点信息
/// </summary>
/// <param name="index">索引,从0开始</param>
/// <returns></returns>
private MyLinkedListNode<T> GetNodeAt(int index) => GetNodeTupleAt(index)?.Item1; /// <summary>
/// 根据索引返回指定索引:节点元组
/// </summary>
/// <param name="index">索引,从0开始</param>
/// <returns>item1:当前节点;item2:上一节点;item3:下一节点;</returns>
private Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>> GetNodeTupleAt(int index)
{
if (index < ) return null;
if (_rootNode == null) throw new Exception("自定义链表为空!"); var num = ;
// 当前节点
MyLinkedListNode<T> currentNode = _rootNode;
// 上一节点
MyLinkedListNode<T> prevNode = _rootNode;
// while循环会在 currentNode == 倒数第二个节点时就会停止循环,所以while后面需要再做一次判断
while (currentNode.Next != null)
{
// 如果当前索引 和 查找索引相同,则返回担负起节点
if (num == index) return GetValidNodeTuple(index, currentNode, prevNode);
// 重置:上一节点
prevNode = currentNode;
// 重置:当前节点
currentNode = currentNode.Next;
num++;
}
if (num < index) throw new Exception("索引超过链表长度!");
return num == index ? GetValidNodeTuple(index, currentNode, prevNode) : null;
} /// <summary>
/// 获取有效的节点元组
/// </summary>
/// <param name="index">索引</param>
/// <param name="currentNode">当前节点</param>
/// <param name="prevNode">上一节点【如果索引 == 0 ? null :上一节点 】</param>
/// <returns>item1:当前节点;item2:上一节点;item3:下一节点;</returns>
private Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>> GetValidNodeTuple(int index, MyLinkedListNode<T> currentNode, MyLinkedListNode<T> prevNode)
{
return new Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>>(currentNode, index == ? null : prevNode, currentNode.Next);
} #endregion #region 添加方法 /// <summary>
/// 1.添加: 添加到集合最后面
/// </summary>
/// <param name="item"></param>
public void Append(T item)
{
MyLinkedListNode<T> node = new MyLinkedListNode<T>(item);
// 如果链表集合为空,则讲当前 元素当作跟节点
if (_rootNode == null)
{
_rootNode = node;
return;
} // 循环得到最末节点
MyLinkedListNode<T> currentNode = _rootNode;
while (currentNode.Next != null) currentNode = currentNode.Next; // 添加到集合最后面
currentNode.Next = node;
} /// <summary>
/// 2.添加: 添加到集合最前面
/// </summary>
/// <param name="item"></param>
public void AddFirst(T item)
{
MyLinkedListNode<T> node = new MyLinkedListNode<T>(item);
// 如果链表集合为空,则讲当前 元素当作跟节点
if (_rootNode == null)
{
_rootNode = node;
return;
} _rootNode = new MyLinkedListNode<T>(item, _rootNode); // 显示链表中的所有数据
Console.Write($"AddFirst({item})\t");
Show();
} /// <summary>
/// 3.添加: 在索引后面添加
/// 3.1.获取到当前索引的节点
/// 3.2.根据item创建新节点,把 当前节点的 下一节点指给 新节点的下一节点
/// 3.3.把新节点当作当前节点的下一节点
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void AddAtAfter(int index, T item)
{
MyLinkedListNode<T> node = new MyLinkedListNode<T>(item);
// 如果链表集合为空,则讲当前 元素当作跟节点
if (_rootNode == null)
{
_rootNode = node;
return;
}
// 3.1.获取到当前索引的节点
var currentNode = GetNodeAt(index);
// 如果链表集合为空,则讲当前 元素当作跟节点
if (currentNode == null)
{
_rootNode = node;
return;
} // 3.2.根据item创建新节点
var newNode = new MyLinkedListNode<T>(item, currentNode.Next); // 3.3.把新节点当作当前节点的下一节点
currentNode.Next = newNode; // 显示链表中的所有数据
Console.Write($"AddAtAfter(int {index},T {item})\t");
Show();
} /// <summary>
/// 4.添加: 在索引前面添加
/// 4.1.获取到 当前索引 和 上一索引 的节点
/// 4.2.根据item创建新节点,把当前节点当作新节点的下一节点
/// 4.3.把新节点当作上一节点的下一节点
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void AddAtBefore(int index, T item)
{
var nodeTuple = GetNodeTupleAt(index);
if (nodeTuple == null) throw new Exception("索引超过链表长度!"); // 4.1.获取到 当前索引 和 上一索引 的节点
var currentNode = nodeTuple.Item1;
var prevtNode = nodeTuple.Item2; // 4.2.根据item创建新节点,把当前节点当作新节点的下一节点
var newNode = new MyLinkedListNode<T>(item, currentNode); // 4.3.把新节点当作上一节点的下一节点:如果索引是0,则新节点作为链接根节点
if (index == ) _rootNode = newNode;
else prevtNode.Next = newNode; // 显示链表中的所有数据
Console.Write($"AddAtBefore(int {index},T {item})\t");
Show();
} #endregion #region 删除方法 /// <summary>
/// 5.删除: 删除T
/// 5.1.得到 当前节点/上一节点/下一节点
/// 5.2.把 上一节点的下一节点 更新为 下一节点
/// </summary>
/// <param name="item"></param>
public void Remove(T item)
{
if (_rootNode == null) return;
// 当前节点
var currentNode = _rootNode;
// 上一节点
MyLinkedListNode<T> prevNode = null;
while (currentNode.Next != null)
{
if (currentNode.Node.Equals(item))
{
// 根据 当前节点 的 上一节点和下一节点 删除 当前节点
Remove(prevNode, currentNode.Next); // 显示链表中的所有数据
Console.Write($"Remove({item})\t");
Show();
return;
}
// 重置 上一节点 和 当前节点
prevNode = currentNode;
currentNode = currentNode.Next;
}
// 如果需要删除的是最后一个节点,则while循环在 currentNode == 倒数第二个节点时就会停止循环
Remove(prevNode, null); // 显示链表中的所有数据
Console.Write($"Remove({item})\t");
Show();
} /// <summary>
/// 根据 当前节点 的 上一节点和下一节点 删除 当前节点:把上一节点的next 指向 下一节点
/// </summary>
/// <param name="prevNode"></param>
/// <param name="nextNode"></param>
private void Remove(MyLinkedListNode<T> prevNode, MyLinkedListNode<T> nextNode)
{
if (prevNode == null) _rootNode = nextNode;
else prevNode.Next = nextNode;
} /// <summary>
/// 6.删除: 删除指定索引
/// 6.1.得到 当前/上一/下一节点
/// 6.2.把当前节点的下一节点 更新为 下一节点
/// </summary>
/// <param name="index"></param>
public void RemoveAt(int index)
{
var nodeTuple = GetNodeTupleAt(index); // 上一节点
var prevNode = nodeTuple.Item2;
// 判断上一节点是不是null ? 当前节点为根节点,需要把下一节点更新为更节点
if (prevNode == null) _rootNode = nodeTuple.Item3;
else prevNode.Next = nodeTuple.Item3; // 显示链表中的所有数据
Console.Write($"RemoveAt({index})\t");
Show();
} /// <summary>
/// 7.删除: 删除第一个
/// </summary>
public void RemoveFirst()
{
if (_rootNode == null) return;
_rootNode = _rootNode.Next; // 显示链表中的所有数据
Console.Write($"RemoveFirst()\t");
Show();
} /// <summary>
/// 8.删除: 删除最后一个
/// </summary>
public void RemoveLast()
{
if (_rootNode == null) return;
// 如果链表只存在根节点,则把根节点删除
if (_rootNode.Next == null)
{
_rootNode = null;
return;
}
// while循环获得最后一个节点
var currentNode = _rootNode;
// 上一节点/倒数第二个节点
MyLinkedListNode<T> prevNode = null;
while (currentNode.Next != null)
{
prevNode = currentNode;
currentNode = currentNode.Next;
}
prevNode.Next = null; // 显示链表中的所有数据
Console.Write($"RemoveLast()\t");
Show();
} /// <summary>
/// 9.删除: 删除所有
/// </summary>
public void Clear()
{
_rootNode = null; // 显示链表中的所有数据
Console.Write($"Clear()\t");
Show();
} #endregion /// <summary>
/// 显示链表中的所有数据
/// </summary>
public void Show()
{
if (_rootNode == null)
{
Console.WriteLine($"链表中的数据为空!\r\n");
return;
}
StringBuilder builder = new StringBuilder(); MyLinkedListNode<T> currentNode = _rootNode;
while (currentNode.Next != null)
{
builder.Append($"{currentNode.Node}\t");
currentNode = currentNode.Next;
}
// 最后一个节点next为null,不会进入while
builder.Append($"{currentNode.Node}\t"); Console.WriteLine($"链表中的数据为:\r\n{builder.ToString()}\r\n");
}
}

测试代码:

         /// <summary>
/// 测试单链表
/// </summary>
public static void RunLinkedList()
{
Console.WriteLine("======= 链表测试 Start ======="); MyLinkedList<string> myLinkedList = new MyLinkedList<string>();
myLinkedList.Append("张三");
myLinkedList.Append("李四");
myLinkedList.Append("王五");
myLinkedList.Append("六麻子");
myLinkedList.Append("田七");
myLinkedList.Show(); // 张三 李四 王五 六麻子 田七 // 异常测试
var a = myLinkedList[]; // 张三
a = myLinkedList[]; // 六麻子
//a = myLinkedList[11]; // 测试添加功能
myLinkedList.AddFirst("郭大爷"); // 郭大爷 张三 李四 王五 六麻子 田七
myLinkedList.AddAtAfter(, "海大爷"); // 郭大爷 海大爷 张三 李四 王五 六麻子 田七
myLinkedList.AddAtBefore(, "Robot"); // Robot 郭大爷 海大爷 张三 李四 王五 六麻子 田七
myLinkedList.AddAtBefore(, "Robot"); // Robot 郭大爷 Robot 海大爷 张三 李四 王五 六麻子 田七
myLinkedList.AddAtBefore(, "Robot"); // Robot 郭大爷 Robot 海大爷 Robot 张三 李四 王五 六麻子 田七 // 测试删除功能
myLinkedList.Remove("Robot"); // 郭大爷 Robot 海大爷 Robot 张三 李四 王五 六麻子 田七
myLinkedList.Remove("Robot"); // 郭大爷 海大爷 Robot 张三 李四 王五 六麻子 田七
myLinkedList.Remove("田七"); // 郭大爷 海大爷 Robot 张三 李四 王五 六麻子
myLinkedList.RemoveAt(); // 海大爷 Robot 张三 李四 王五 六麻子
myLinkedList.RemoveAt(); // 海大爷 张三 李四 王五 六麻子
myLinkedList.RemoveFirst(); // 张三 李四 王五 六麻子
myLinkedList.RemoveFirst(); // 李四 王五 六麻子
myLinkedList.RemoveLast(); // 李四 王五
myLinkedList.RemoveLast(); // 李四
myLinkedList.Clear(); // 链表中的数据为空! Console.WriteLine("======= 链表测试 End =======");
}

NET 数据结构-单链表的更多相关文章

  1. python实现数据结构单链表

    #python实现数据结构单链表 # -*- coding: utf-8 -*- class Node(object): """节点""" ...

  2. C语言数据结构-单链表的实现-初始化、销毁、长度、查找、前驱、后继、插入、删除、显示操作

    1.数据结构-单链表的实现-C语言 typedef struct LNode { int data; struct LNode* next; } LNode,*LinkList; //这两者等价.Li ...

  3. 数据结构——单链表java简易实现

    巩固数据结构 单链表java实现 单链表除了表尾 每个几点都有一个后继 结点有数据和后继指针组成  通过构建表头和表尾(尾部追加需要)两个特殊几点 实现单链表的一些操作,代码如下 package co ...

  4. C# 数据结构--单链表

    什么是单链表 这两天看到很多有关单链表的面试题,对单链表都不知道是啥的我.经过学习和整理来分享一下啥是单链表和单链表的一些基本使用方法.最后看些网上有关单链表的面试题代码实例. 啥是单链表? 单链表是 ...

  5. 数据结构-------单链表(C++)

    相关信息: /** * @subject 数据结构 实验2 * @author 信管1142班 201411671210 赖俊杰 * @project 单链表 * @time 2015年10月29日1 ...

  6. C# 数据结构 - 单链表 双链表 环形链表

    链表特点(单链表 双链表) 优点:插入和删除非常快.因为单链表只需要修改Next指向的节点,双链表只需要指向Next和Prev的节点就可以完成插入和删除操作. 缺点:当需要查找某一个节点的时候就需要一 ...

  7. 数据结构—单链表(类C语言描写叙述)

    单链表 1.链接存储方法 链接方式存储的线性表简称为链表(Linked List). 链表的详细存储表示为: ① 用一组随意的存储单元来存放线性表的结点(这组存储单元既能够是连续的.也能够是不连续的) ...

  8. python算法与数据结构-单链表(38)

    一.链表 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成.每个结点包括 ...

  9. 数据结构——单链表(singly linked list)

    /* singlyLinkedList.c */ /* 单链表 */ /* 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素. */ #include <stdio ...

  10. 数据结构-单链表-类定义2-C++

    上一次的C++链表实现两个单链表的连接不太理想,此次听了一些视频课,自己补了个尾插法,很好的实现了两个链表的连接,当然了,我也是刚接触,可能是C++的一些语法还不太清楚,不过硬是花了一些时间尽量在数据 ...

随机推荐

  1. JavaScript几种继承方式的总结

    1.原型链继承 直接将子类型的原型指向父类型的实例,即"子类型.prototype = new 父类型();",实现方法如下: //父类构造函数 function father(n ...

  2. 生成随机字符串 php

    /** +---------------------------------------------------------- * 生成随机字符串 +------------------------- ...

  3. Rocket - debug - TLDebugModuleInner - Abstract Command Decoding & Generation

    https://mp.weixin.qq.com/s/0zKSTktxgzo5uCUphqaWSQ 介绍抽象命令的解码和生成. 1. accessRegisterCommandReg accessRe ...

  4. Flutter 动画鼻祖之CustomPaint

    老孟导读:CustomPaint可以称之为动画鼻祖,它可以实现任何酷炫的动画和效果.CustomPaint本身没有动画属性,仅仅是绘制属性,一般情况下,CustomPaint会和动画控制配合使用,达到 ...

  5. ASP.NET通过更改Url进行页面传值

    这里,通过假数据,手动创建的一个类,然后创建的一个集合,放入下拉框,选好值以后,点确定 会在另一个页面产生对应的id,有不懂的欢迎评论 创建一个类: using System; using Syste ...

  6. Java实现 蓝桥杯VIP 算法提高 前10名

    算法提高 前10名 时间限制:1.0s 内存限制:256.0MB 问题描述 数据很多,但我们经常只取前几名,比如奥运只取前3名.现在我们有n个数据,请按从大到小的顺序,输出前10个名数据. 输入格式 ...

  7. 【asp.net core 系列】5 布局页和静态资源

    0. 前言 在之前的4篇的内容里,我们较为详细的介绍了路由以及控制器还有视图之间的关系.也就是说,系统如何从用户的HTTP请求解析到控制器里,然后在控制器里处理数据,并返回给视图,在视图中显示出来.这 ...

  8. Spring源码之自动装配

    我们使用Spring开发过程中经常会用到Autowired注解注入依赖的bean,这部分也是面试的热点问题之一.今天咱们一起来深入研究下自动注入的背后实现原理.首先上一个例子,如下所示: @RestC ...

  9. 一次性搞懂 PHP 中面向对象的所有知识点。

    OOP是什么? OOP是面向对象编程,面向对象编程是一种计算机编程架构. OOP的基本原则是计算机程序是由单个能起到子程序作用的单元或对象组合而成. 基本概念: 类:定义了事务的抽象特点.包含了数据的 ...

  10. LR脚本信息函数-lr_get_vuser_ip

    lr_get_vuser_ip 返回Vuser的IP地址. char * lr_get_vuser_ip(); lr_get_vuser_ip函数返回Vuser的IP地址. 当执行IP欺骗时,每个Vu ...