链表是一种重要的数据结构,是一种数据的存储方式。链表由多个链表元素组成,每个元素称为节点。链表存储的物理结构可能是连续的,但也可能是无序的。但是链表之间的元素(节点)是有序的逻辑相连。
链表分为:单(向)链表、循环链表、双向链表。
虽然有三种不同的链表,但是其中心思想(存储的逻辑结构)是一样的。笔者以单链表来分享其思想。
首先来看链表的节点是怎么定义的:代码如下
class Node<T> //定义为泛型,根据用户存储需求决定存储的数据类型 { public T Date { get; set; } //存储的数据 public Node<T> NodeFront { get; set; } //前驱 public Node<T> NodeNext { get; set; } //后继 public Node(T t) //构造函数 { Date = t; NodeFront = NodeNext = null; //刚开始初始化的节点没有前驱和后继 } public Node() //无参构造函数 { Date = default(T); //泛型默认的初始化方式 NodeFront = NodeNext = null; } }
节点类有三个属性成员,分别是Date、NodeFront、NodeNext。其中NodeFront、NodeNext的类型也是Node<T>的引用类型,也就相当于NodeFront、NodeNext也是属于节点的。由于是单向链表,我们只用到后继NodeNext
比如我们创建三个节点:node1、node2、node3,然后输出note2的值,代码如下:
static void Main(string[] args) { Node<int> node1 = new Node<int>(1); Node<int> node2 = new Node<int>(2); Node<int> node3 = new Node<int>(3); node1.NodeNext = node2; node2.NodeNext = node3; //输出node2的值 Console.WriteLine(node2.Date); Console.WriteLine(node1.NodeNext.Date); Console.ReadKey(); }
结果是输出的值都是2。
所以说NodeNext也是属于节点的,它就相当于node2节点。但是,我们在用单向链表的时候只有头节点和尾节点,那么NodeNext就很有用了。只要我们知道头节点和下标,就可以一步一步算出指定位置的值。
画个图帮助理解下:
单向链表存储的逻辑结构其实有点像嵌套。node1的NodeNext成员保存了node2,node2的NodeNext成员又保存了node3,node3的NodeNext成员又保存了下一个节点直到尾节点的NodeNext保存的是null。
清楚了单向链表,实现其功能就很简单了,用来用去就是用到NodeNext这个成员。
先创建一个MyList类,代码如下:
class MyList<T> { private int listSize = 0; private Node<T> ListHead; //存储链表的头 private Node<T> ListTail; //存储链表的尾 //为listSize添加一个只读属性 public int ListSize { get { return listSize; } } }
笔者在写添加一个listSize只读属性的时候当时是这样写的:
public int ListSize { get; }
Tip:当时想把ListSize即当字段用,又当属性用。但是发现,在类里面,字段ListSize也是不可写入的。所以在写只读属性时,字段和属性还是分开写好。
接下来,笔者分享其中一个功能的实现思想(有点懒= =),其实也算是最复杂的一个功能,会了这个其他都不是事儿了:在指定位置插入指定的元素:
代码如下:
//向链表插入指定位置插入一个元素(前插) public bool Insert_Front(T date, uint position) { if (IsEmpty()) //链表为空 return false; else if (position > listSize) //超出链表长度或则为链表尾巴 return false; if (position == 1) //在链表头插入 { Node<T> newNode = new Node<T>(date); //创建一个节点 newNode.NodeNext = ListHead; //新节点的后继指向头节点 ListHead = newNode; //新节点变成头节点 ++listSize; return true; } else { Node<T> tempNode = ListHead; //创建一个临时节点保存链表头 Node<T> tempFrontNode = new Node<T>(); //创建一个节点,保存tempNode的上一个节点 uint ui = 1; while (ui < position && tempNode.NodeNext != null) { tempFrontNode = tempNode; //保存tempNode的上一个节点tempFrontNode(前插的时候会用到这个节点) tempNode = tempNode.NodeNext; //tempNode自身变成了下一个节点 ui++; } Node<T> newNode = new Node<T>(date); //创建新节点 tempFrontNode.NodeNext = newNode; //tempFrontNode的后继等于新节点 newNode.NodeNext = tempNode; //新节点的后继节点等于tempNode节点 ++listSize; //链表长度加1 return true; } }
画张图来脑补一下:(不表示在头节点前面插入)
只要理解了节点链接的逻辑,所有的功能都会很好实现的。下面贴一下完整的代码:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 线性链表 { class Node<T> //定义为泛型,根据用户存储需求决定存储的数据类型 { public T Date { get; set; } //存储的数据 public Node<T> NodeFront { get; set; } //前驱 public Node<T> NodeNext { get; set; } //后继 public Node(T t) //构造函数 { Date = t; NodeFront = NodeNext = null; //刚开始初始化的节点没有前驱和后继 } public Node() //无参构造函数 { Date = default(T); //泛型默认的初始化方式 NodeFront = NodeNext = null; } } class MyList<T> { private int listSize = 0; private Node<T> ListHead; //存储链表的头 private Node<T> ListTail; //存储链表的尾 //为listSize添加一个只读属性 public int ListSize { get { return listSize; } } //判断线性链表是否为空 public bool IsEmpty() { return ListHead == null ? true : false; } //向链表里添加一个元素 public void Push_In(T date) { Node<T> newNode = new Node<T>(date); //创建一个节点 if (IsEmpty()) //链表是否为空 ListHead = newNode; //链表为空时,添加的元素为头节点 else ListTail.NodeNext = newNode; //否者原先的尾节点的NodeNext成员(后继)指向新元素。 ListTail = newNode; //新元素变成尾节点 ++listSize; } //向链表插入指定位置插入一个元素(前插) public bool Insert_Front(T date, uint position) { if (IsEmpty()) //链表为空 return false; else if (position > listSize) //超出链表长度或则为链表尾巴 return false; if (position == 1) //在链表头插入 { Node<T> newNode = new Node<T>(date); //创建一个节点 newNode.NodeNext = ListHead; //新节点的后继指向头节点 ListHead = newNode; //新节点变成头节点 ++listSize; return true; } else { Node<T> tempNode = ListHead; //创建一个临时节点保存链表头 Node<T> tempFrontNode = new Node<T>(); //创建一个节点,保存tempNode的上一个节点 uint ui = 1; while (ui < position && tempNode.NodeNext != null) { tempFrontNode = tempNode; //保存tempNode的上一个节点tempFrontNode(前插的时候会用到这个节点) tempNode = tempNode.NodeNext; //tempNode自身变成了下一个节点 ui++; } Node<T> newNode = new Node<T>(date); //创建新节点 tempFrontNode.NodeNext = newNode; //tempFrontNode的后继等于新节点 newNode.NodeNext = tempNode; //新节点的后继节点等于tempNode节点 ++listSize; //链表长度加1 return true; } } //向链表插入指定位置插入一个元素(后插) public void Insert_Back(T date, uint position) { if (IsEmpty()) Console.WriteLine("链表为空"); else if (position > listSize) Console.WriteLine("越界了"); else { Node<T> newNode = new Node<T>(date); Node<T> tempNode = ListHead; Node<T> tempFrontNode = new Node<T>(); uint ui = 1; if (position == listSize) //如果在表尾 { ListTail.NodeNext = newNode; ListTail = newNode; } else { while (ui <= position && tempNode.NodeNext != null) { tempFrontNode = tempNode; tempNode = tempNode.NodeNext; ui++; } tempFrontNode.NodeNext = newNode; newNode.NodeNext = tempNode; } ++listSize; } } //在链表删除一个指定元素 public bool Delete_Out(T date) { if (IsEmpty()) { Console.WriteLine("列表为空,无法删除"); return false; } else { Node<T> tempNode = ListHead; //创建临时节点,保存表头 Node<T> tempFrontNode = new Node<T>(); //保存临时节点的前驱 Node<T> tempNextNode = new Node<T>(); //保存临时节点的后驱 uint ui = 1; while (!tempNode.Date.Equals(date) && ui <= listSize) { tempFrontNode = tempNode; if (tempNode.NodeNext != null) //如果不为表尾 { tempNode = tempNode.NodeNext; tempNextNode = tempNode.NodeNext; } ui++; }//end while if (ui > listSize) { Console.WriteLine("没有找到指定元素"); return false; } else { if (ui == listSize) //删除表尾 ListTail = tempFrontNode; else { tempFrontNode.NodeNext = tempNextNode; tempNextNode.NodeFront = tempFrontNode; } --listSize; GC.Collect(); //强制垃圾回首 return true; } }//end else } //输出列表的元素 public void Print_Element() { if (IsEmpty()) { Console.WriteLine("列表为空"); } else { Node<T> tempNode = ListHead; uint i = 1; Console.Write("元素为:"); do { Console.Write("{0} ", tempNode.Date); tempNode = tempNode.NodeNext; i++; } while (i <= listSize); Console.WriteLine(); } } //查找指定位置的元素 public T GetElement(uint index) { T item = default(T); //默认初始化泛型 if (IsEmpty()) Console.WriteLine("链表为空"); else if (index > listSize) Console.WriteLine("越界了"); else { uint uCount = 1; Node<T> tempNode = ListHead; while (uCount != index && tempNode.NodeNext != null) { tempNode = tempNode.NodeNext; uCount++; } item = tempNode.Date; } return item; } } }