概念介绍:

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

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

由图可知:

  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. 【MOOC操作系统】测试题大题-进程调度 先入先服务算法例题 【某多道程序系统供用户使用的主存为100K,磁带机2台,打印机1台,采用可变分区存储管理,静态方式分配外围设备(进程获得所需全部设备才能进入内容),忽略用户作业的I/O时间。采用动态分区、首次匹配法(从低地址区开始)分配主存,一个作业创建一个进程,且运行中不紧缩内存。作业调度采用FCFS算法,在主存中的进程采用剩余时间最短调度算法。】

    分析图: 答案: (1) 8 : 00作业1到达,占有资源并调入主存运行. 8: 20作业2和3同时到达,但作业2因分不到打印机,只能在后备队列等待.作业3资源满足,可进主存运行,并与作业1平分CPU ...

  2. harbor越权漏洞(CVE-2019-16097)

    漏洞介绍 这个漏洞可以在注册发送post包时,加入has_admin_role:true就可以直接注册成为管理员,下图可以看看user的结构: 有很多属性,此处我们关注的是"HasAdmin ...

  3. 服务器ip地址 服务器ip登录方法

    服务器是指保存有该网络中所有主机的域名和对应IP地址,并具有将域名转换为IP地址功能的服务器.其中域名必须对应一个IP地址,一个域名可以有多个IP地址,而IP地址不一定有域名.   简单的解释就是:服 ...

  4. VUE 子组件向父组件传值 , 并且触发父组件方法(函数)

    目标:封装一个  搜索组件 <子组件需要传一个或者多个搜索参数到父组件,然后父组件执行列表查询函数> 1.子组件 <div> <input v-model="l ...

  5. Java实现 LeetCode 754 到达终点数字(暴力+反向)

    754. 到达终点数字 在一根无限长的数轴上,你站在0的位置.终点在target的位置. 每次你可以选择向左或向右移动.第 n 次移动(从 1 开始),可以走 n 步. 返回到达终点需要的最小移动次数 ...

  6. Java实现 LeetCode 528 按权重随机选择(TreeMap)

    528. 按权重随机选择 给定一个正整数数组 w ,其中 w[i] 代表位置 i 的权重,请写一个函数 pickIndex ,它可以随机地获取位置 i,选取位置 i 的概率与 w[i] 成正比. 说明 ...

  7. Java中环境变量PATH与CLASSPATH的区别

    在安装JDK时需要添加环境变量,经常使用的环境变量有两个: PATH与CLASSPATH 下面总结一下环境变量的作用. PATH是系统用来指定可执行文件的完整路径.当在CMD中执行命令时,如果执行的可 ...

  8. 源码分析(1)-HashMap(JDK1.8)

    UML类图 java.util.Map<K, V>接口,有4个实现类:HashMap.Hashtable.LinkedHashMap和TreeMap. 1.说明 (1)HashMap除允许 ...

  9. android-sdk-window的环境搭建以及appium简单录制脚本的使用

    大家好,今天给大家带来的是appium的环境搭建以及简单的录制脚本,自学的过程中入了不少坑,下面给大家开始分享! 使用Appium录制脚本必备三大金刚:Appium-desktop(至于为什么用这个, ...

  10. @Autowired 注解详解

    前言 我们平时使用 Spring 时,想要 依赖注入 时使用最多的是 @Autowired 注解了,本文主要讲解 Spring 是如何处理该注解并实现 依赖注入 的功能的. 正文 首先我们看一个测试用 ...