本文实例讲述了C#数据结构之单链表(LinkList)实现方法。分享给大家供大家参考,具体如下:
这里我们来看下“单链表(LinkList)”。在上一篇《C#数据结构之顺序表(SeqList)实例详解》的最后,我们指出了:顺序表要求开辟一组连续的内存空间,而且插入/删除元素时,为了保证元素的顺序性,必须对后面的元素进行移动。如果你的应用中需要频繁对元素进行插入/删除,那么开销会很大。
而链表结构正好相反,先来看下结构:
每个元素至少具有二个属性:data和next。data用来存放数据,而next用来指出它后面的元素是谁(有点“指针”的意思)。
链表中的元素,通常也称为节点Node,下面是泛型版本的Node.cs
namespace 线性表 { public class Node{ private T data; private Node next; public Node(T val, Node p) { data = val; next = p; } public Node(Node p) { next = p; } public Node(T val) { data = val; next = null; } public Node() { data = default(T); next = null; } public T Data { get { return data; } set { data = value; } } public Node Next { get { return next; } set { next = value; } } } }
链表在存储上并不要求所有元素按顺序存储,因为用节点的next就能找到下一个节点,这好象一根“用珠子串成的链子”,要找到其中的某一颗珠子,只要从第一颗节点(通常称为Head节点)开始,不断根据next指向找到下一个,直到找到需要的节点为止。
链表中需要有一个Head节点做为开始,这跟顺序表有所不同,下面是单链表的实现:
using System; using System.Text; namespace 线性表 { public class LinkList: IListDS { private Node head; public Node Head { get { return head; } set { head = value; } } public LinkList() { head = null; } /// /// 类索引器 /// /// ///public T this[int index] { get { return this.GetItemAt(index); } } /// /// 返回单链表的长度 /// ///public int Count() { Node p = head; int len = 0; while (p != null) { len++; p = p.Next; } return len; } /// /// 清空 /// public void Clear() { head = null; } ////// 是否为空 /// ///public bool IsEmpty() { return head == null; } /// /// 在最后附加元素 /// /// public void Append(T item) { Noded = new Node (item); Node n = new Node (); if (head == null) { head = d; return; } n = head; while (n.Next != null) { n = n.Next; } n.Next = d; } //前插 public void InsertBefore(T item, int i) { if (IsEmpty() || i < 0) { Console.WriteLine("List is empty or Position is error!"); return; } //在最开头插入 if (i == 0) { Node q = new Node (item); q.Next = Head;//把"头"改成第二个元素 head = q;//把自己设置为"头" return; } Node n = head; Node d = new Node (); int j = 0; //找到位置i的前一个元素d while (n.Next != null && j < i) { d = n; n = n.Next; j++; } if (n.Next == null) //说明是在最后节点插入(即追加) { Node q = new Node (item); n.Next = q; q.Next = null; } else { if (j == i) { Node q = new Node (item); d.Next = q; q.Next = n; } } } /// /// 在位置i后插入元素item /// /// /// public void InsertAfter(T item, int i) { if (IsEmpty() || i < 0) { Console.WriteLine("List is empty or Position is error!"); return; } if (i == 0) { Nodeq = new Node (item); q.Next = head.Next; head.Next = q; return; } Node p = head; int j = 0; while (p != null && j < i) { p = p.Next; j++; } if (j == i) { Node q = new Node (item); q.Next = p.Next; p.Next = q; } else { Console.WriteLine("Position is error!"); } } /// /// 删除位置i的元素 /// /// ///public T RemoveAt(int i) { if (IsEmpty() || i < 0) { Console.WriteLine("Link is empty or Position is error!"); return default(T); } Node q = new Node (); if (i == 0) { q = head; head = head.Next; return q.Data; } Node p = head; int j = 0; while (p.Next != null && j < i) { j++; q = p; p = p.Next; } if (j == i) { q.Next = p.Next; return p.Data; } else { Console.WriteLine("The node is not exist!"); return default(T); } } /// /// 获取指定位置的元素 /// /// ///public T GetItemAt(int i) { if (IsEmpty()) { Console.WriteLine("List is empty!"); return default(T); } Node p = new Node (); p = head; if (i == 0) { return p.Data; } int j = 0; while (p.Next != null && j < i) { j++; p = p.Next; } if (j == i) { return p.Data; } else { Console.WriteLine("The node is not exist!"); return default(T); } } //按元素值查找索引 public int IndexOf(T value) { if (IsEmpty()) { Console.WriteLine("List is Empty!"); return -1; } Node p = new Node (); p = head; int i = 0; while (!p.Data.Equals(value) && p.Next != null) { p = p.Next; i++; } return i; } /// /// 元素反转 /// public void Reverse() { LinkListresult = new LinkList (); Node t = this.head; result.Head = new Node (t.Data); t = t.Next; //(把当前链接的元素从head开始遍历,逐个插入到另一个空链表中,这样得到的新链表正好元素顺序跟原链表是相反的) while (t!=null) { result.InsertBefore(t.Data, 0); t = t.Next; } this.head = result.head;//将原链表直接挂到"反转后的链表"上 result = null;//显式清空原链表的引用,以便让GC能直接回收 } public override string ToString() { StringBuilder sb = new StringBuilder(); Node n = this.head; sb.Append(n.Data.ToString() + ","); while (n.Next != null) { sb.Append(n.Next.Data.ToString() + ","); n = n.Next; } return sb.ToString().TrimEnd(','); } } }
下面是单链表插入和删除的算法图解:
可以看到:链表在元素插入/删除时,无需对后面的元素进行移动,只需要修改自身以及相邻节点的next指向即可,所以插入/删除元素的开销要比顺序表小得多。但是也应该注意到,其它操作比如:查找元素,反转倒置链表等,有可能需要遍历整个链表中的所有元素。
测试代码片断:
Console.WriteLine("-------------------------------------"); Console.WriteLine("单链表测试开始..."); LinkListlink = new LinkList (); link.Head = new Node ("x"); link.InsertBefore("w", 0); link.InsertBefore("v", 0); link.Append("y"); link.InsertBefore("z", link.Count()); Console.WriteLine(link.Count());//5 Console.WriteLine(link.ToString());//v,w,x,y,z Console.WriteLine(link[1]);//w Console.WriteLine(link[0]);//v Console.WriteLine(link[4]);//z Console.WriteLine(link.IndexOf("z"));//4 Console.WriteLine(link.RemoveAt(2));//x Console.WriteLine(link.ToString());//v,w,y,z link.InsertBefore("x", 2); Console.WriteLine(link.ToString());//v,w,x,y,z Console.WriteLine(link.GetItemAt(2));//x link.Reverse(); Console.WriteLine(link.ToString());//z,y,x,w,v link.InsertAfter("1", 0); link.InsertAfter("2", 1); link.InsertAfter("6", 5); link.InsertAfter("8", 7); link.InsertAfter("A", 10);//Position is error! Console.WriteLine(link.ToString()); //z,1,2,y,x,w,6,v,8
至于具体在实际应用中应该选用顺序表 or 链表,主要是看:对于元素插入/删除的频繁程度以及对于内存分配的苛刻程度。 如果不要求一开始就分配一组连续的内存区域,可以根据元素的增加而自动加大内存的使用量,或者插入/删除的次数很多,那么建议使用链表,反之用顺序表。
最后指出:可以给节点再添加一个prev元素,用于指出前一个节点是谁,即同时有next和prev二个指向,这种改进后的链表称为“双向链表”,它有助于某些情况下减少遍历循环的次数,本文中的这种仅有一个next指向的链表,称为“单链表”。
希望本文所述对大家C#程序设计有所帮助。