鏈表思想(我是如何理解鏈表)


  鏈表是一種重要的數據結構,是一種數據的存儲方式。鏈表由多個鏈表元素組成,每個元素稱為節點。鏈表存儲的物理結構可能是連續的,但也可能是無序的。但是鏈表之間的元素(節點)是有序的邏輯相連。

  鏈表分為:單(向)鏈表、循環鏈表、雙向鏈表。

  雖然有三種不同的鏈表,但是其中心思想(存儲的邏輯結構)是一樣的。筆者以單鏈表來分享其思想。

  首先來看鏈表的節點是怎么定義的:代碼如下

    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;
        }
    }


}
View Code

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM