資料結構C#版筆記–單鏈表(LinkList)

來源:互聯網
上載者:User

上一篇學習了"順序表(SeqList)",這一篇來看下“單鏈表(LinkList)”。在上一篇的最後,我們指出了:順序表要求開闢一組連續的記憶體空間,而且插入/刪除元素時,為了保證元素的順序性,必須對後面的元素進行移動。如果你的應用中需要頻繁對元素進行插入/刪除,那麼開銷會很大。

 

而鏈表結構正好相反,先來看下結構:

每個元素至少具有二個屬性:data和next。data用來存放資料,而next用來指出它後面的元素是誰(有點“指標”的意思)。

鏈表中的元素,通常也稱為節點Node,下面是泛型版本的Node.cs

namespace 線性表{    public class Node<T>    {        private T data;        private Node<T> next;        public Node(T val, Node<T> p)         {            data = val;            next = p;        }        public Node(Node<T> 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<T> Next         {            get { return next; }            set { next = value; }        }    }}

鏈表在儲存上並不要求所有元素按順序儲存,因為用節點的next就能找到下一個節點,這好象一根“用珠子串成的鏈子”,要找到其中的某一顆珠子,只要從第一

顆節點(通常稱為Head節點)開始,不斷根據next指向找到下一個,直到找到需要的節點為止。

鏈表中需要有一個Head節點做為開始,這跟順序表有所不同,下面是單鏈表的實現:

using System;using System.Text;namespace 線性表{    public class LinkList<T> : IListDS<T>    {        private Node<T> head;        public Node<T> Head        {            get { return head; }            set { head = value; }        }        public LinkList()        {            head = null;        }        /// <summary>        /// 類索引器        /// </summary>        /// <param name="index"></param>        /// <returns></returns>        public T this[int index]         {            get             {                return this.GetItemAt(index);            }        }        /// <summary>        /// 返回單鏈表的長度        /// </summary>        /// <returns></returns>        public int Count()        {            Node<T> p = head;            int len = 0;            while (p != null)            {                len++;                p = p.Next;            }            return len;        }        /// <summary>        /// 清空        /// </summary>        public void Clear()        {            head = null;        }        /// <summary>        /// 是否為空白        /// </summary>        /// <returns></returns>        public bool IsEmpty()        {            return head == null;        }        /// <summary>        /// 在最後附加元素        /// </summary>        /// <param name="item"></param>        public void Append(T item)        {            Node<T> d = new Node<T>(item);            Node<T> n = new Node<T>();            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<T> q = new Node<T>(item);                q.Next = Head;//把"頭"改成第二個元素                head = q;//把自己設定為"頭"                return;            }            Node<T> n = head;            Node<T> d = new Node<T>();            int j = 0;            //找到位置i的前一個元素d            while (n.Next != null && j < i)            {                d = n;                n = n.Next;                j++;            }            if (n.Next == null) //說明是在最後節點插入(即追加)            {                Node<T> q = new Node<T>(item);                n.Next = q;                q.Next = null;            }            else            {                if (j == i)                {                    Node<T> q = new Node<T>(item);                    d.Next = q;                    q.Next = n;                }            }        }        /// <summary>        /// 在位置i後插入元素item        /// </summary>        /// <param name="item"></param>        /// <param name="i"></param>        public void InsertAfter(T item, int i)        {            if (IsEmpty() || i < 0)            {                Console.WriteLine("List is empty or Position is error!");                return;            }            if (i == 0)            {                Node<T> q = new Node<T>(item);                q.Next = head.Next;                head.Next = q;                return;            }            Node<T> p = head;            int j = 0;            while (p != null && j < i)            {                p = p.Next;                j++;            }            if (j == i)            {                Node<T> q = new Node<T>(item);                q.Next = p.Next;                p.Next = q;            }            else                         {                Console.WriteLine("Position is error!");            }        }        /// <summary>        /// 刪除位元置i的元素        /// </summary>        /// <param name="i"></param>        /// <returns></returns>        public T RemoveAt(int i)        {            if (IsEmpty() || i < 0)            {                Console.WriteLine("Link is empty or Position is error!");                return default(T);            }            Node<T> q = new Node<T>();            if (i == 0)            {                q = head;                head = head.Next;                return q.Data;            }            Node<T> 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);            }        }        /// <summary>        /// 擷取指定位置的元素        /// </summary>        /// <param name="i"></param>        /// <returns></returns>        public T GetItemAt(int i)        {            if (IsEmpty())            {                Console.WriteLine("List is empty!");                return default(T);            }            Node<T> p = new Node<T>();            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<T> p = new Node<T>();            p = head;            int i = 0;            while (!p.Data.Equals(value) && p.Next != null)            {                p = p.Next;                i++;            }            return i;        }        /// <summary>        /// 元素反轉        /// </summary>        public void Reverse()        {            LinkList<T> result = new LinkList<T>();                        Node<T> t = this.head;            result.Head = new Node<T>(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<T> 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("單鏈表測試開始...");            LinkList<string> link = new LinkList<string>();            link.Head = new Node<string>("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指向的鏈表,稱為“單鏈表”。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.