C#實現數據結構——線性表(下)


線性表鏈式存儲結構

看了線性表的順序存儲,你肯定想線性表簡是挺簡單,但是我一開始怎么會知道有多少人排隊?要分配多大的數組?而且插入和刪除一個元素也太麻煩了,所有元素都要前移/后移,效率又低。
那怎么辦呢?
這里就要用到我們的鏈式存儲結構。
這就和我們的鏈條一樣,一環接着一環,只要上一環知道下一環就行了。
你肯定覺得像食堂打飯那樣太麻煩了,每個人都要傻傻地站在那里等,排隊的位置又不夠,人多了排隊的地方都沒有。那有沒有別的方式?


也許我們可以換種方式。每個人都不需要站在那里等,也不需要知道隊伍有多長前面有多少人,只需要知道你的下一個人(也就是后繼)是誰就行了。比如第一個人去了打飯窗口,食堂大媽要他留下電話號碼,隨便去哪都行,到了打飯的時間點就電話通知他。第二個人也過來打飯,食堂大媽也讓第二個人留下手機號,並且將他的手機號告訴第一個人:如果第一個人飯打完了就打電話叫第二個人來打飯。然后再將第三個人的號碼告訴第二個人,由第二個人去通知第三個人,以此類推。這樣每個人都不用排隊了。
第一個人也就是頭節點(頭指針)。


當然,這在現實生活中是不可能的,那樣子大媽會累死,再要碰到誰忘記通知下一個人,整個鏈表就斷了。

初始化

每個人都不用呆在固定的地點排隊,排隊窗口的大小自然也就無所謂了,初始化時只需要知道第一個人的號碼就可以了。當然鏈式結構存儲的方式也和順序不一樣,不需要定義一個數組(因為沒人排隊了),但是需要保存一組數據及它的后繼引用(指針):

public class LinkNode<T>
{
    public T Node
    {
        get; set;
    }

    public LinkNode<T> NextNode
    {
        get; set;
    }
}

然后定義頭節點:

    private LinkNode<T> firstNode;

獲取線性表的長度

想知道有多少人排隊數一下就可以了,但是現在大家都沒排隊,怎么知道總共有多少人在等呢?那只有找到第一個人,再通過第一個人找到第二個,以此類推,直到找到沒有后繼的最后一個為止:

    public int Length
    {
        get
        {
            var node = firstNode;
            int length = 0;
            while(node != null)
            {
                length++;
                node = node.NextNode;
            }
            return length;
        }
    }

添加元素

每來一個人打飯,就需要將新來那個人的號碼告訴之前的最后一個人:

    public void Add(T item)
    {
        if(firstNode == null)
        {
            firstNode = new LinkNode<T> { Node = item };
        }
        else
        {
            LinkNode<T> node = firstNode;
            while(node.NextNode != null)
            {
                node = node.NextNode;
            }
            node.NextNode = new LinkNode<T> { Node = item };
        }
    }

刪除元素

如果你女朋友(程序員竟然也能找到女朋友!)突然臨時約你出去吃飯,你當然覺得食堂這么難吃的飯給程序員吃就可以了,怎么可以給女朋友吃呢?但是作為一名有素質的程序員,你自然會想辦法通知前一個人你不在食堂吃飯了,然后把你下一位的號碼告訴你前一個人。但是你也不知道你前一個人是誰,於是找到食堂大媽,大媽找到第一個人,然后一層層找下去,終於找到你前一個人是誰(想一想這里可以怎樣優化,讓你直接找到前一個人):

    public void Delete(int index)
    {
        if(firstNode == null)
        {
            throw new Exception("List is empty");
        }
        if(index < 0)
        {
            throw new Exception("Location exception");
        }
        var node = firstNode.NextNode;
        var prevNode = firstNode;
        int i;
        for(i = 1; i < index && node != null; i++)
        {
            prevNode = node;
            node = node.NextNode;
        }
        if(i == index)
        {
            if(node.NextNode != null)
            {
                prevNode.NextNode = node.NextNode;
            }
            else
            {
                prevNode.NextNode = null;
            }
        }
        else
        {
            throw new Exception("Locaion exception");
        }
    }

在指定位置插入元素

你女朋友約你去吃飯,於是你正打算放棄排隊帶她到外面吃,但是她是個勤儉持家的好女孩,認為外面吃飯太貴,還不如一起在食堂吃,省下來的錢給她買個包。正有此意又不好意思說的你一聽趕緊把女朋友插在你后面。也就是你打完飯不再去通知你的下一位而是通知你女朋友,然后你女朋友再去通知他。是不是覺得鏈表的方式插隊太方便了?神不知鬼不覺就能無痛插隊,而且你女朋友通知他時,他還得說謝謝,這就是鏈式存儲的優勢:

    public void Insert(T item, int index)
    {
        if (firstNode == null)
        {
            throw new Exception("List is empty");
        }
        if (index < 0)
        {
            throw new Exception("Location exception");
        }
        var node = firstNode.NextNode;
        int i;
        for (i = 1; i < index && node != null; i++)
        {
            node = node.NextNode;
        }
        if (i == index)
        {
            var nextNode = node.NextNode == null ? null : node.NextNode;
            node.NextNode = new LinkNode<T> { Node = item, NextNode = nextNode };
        }
        else
        {
            throw new Exception("Locaion exception");
        }
    }

鏈式存儲完整代碼

/// <summary>
/// 線性表鏈式結構
/// </summary>
public class LinkList<T> : ILinearList<T>
{
    private LinkNode<T> firstNode;

    public int Length
    {
        get
        {
            var node = firstNode;
            int length = 0;
            while(node != null)
            {
                length++;
                node = node.NextNode;
            }
            return length;
        }
    }

    public void Add(T item)
    {
        if(firstNode == null)
        {
            firstNode = new LinkNode<T> { Node = item };
        }
        else
        {
            LinkNode<T> node = firstNode;
            while(node.NextNode != null)
            {
                node = node.NextNode;
            }
            node.NextNode = new LinkNode<T> { Node = item };
        }
    }

    public void Clear()
    {
        firstNode = null;
    }

    public void Delete(int index)
    {
        if(firstNode == null)
        {
            throw new Exception("List is empty");
        }
        if(index < 0)
        {
            throw new Exception("Location exception");
        }
        var node = firstNode.NextNode;
        var prevNode = firstNode;
        int i;
        for(i = 1; i < index && node != null; i++)
        {
            prevNode = node;
            node = node.NextNode;
        }
        if(i == index)
        {
            if(node.NextNode != null)
            {
                prevNode.NextNode = node.NextNode;
            }
            else
            {
                prevNode.NextNode = null;
            }
        }
        else
        {
            throw new Exception("Locaion exception");
        }
    }

    public T GetItem(int index)
    {
        if (firstNode == null)
        {
            throw new Exception("List is empty");
        }
        if (index < 0)
        {
            throw new Exception("Location exception");
        }
        var node = firstNode.NextNode;
        int i;
        for (i = 1; i < index && node != null; i++)
        {
            node = node.NextNode;
        }
        if (i == index)
        {
            return node.Node;
        }
        else
        {
            throw new Exception("Locaion exception");
        }
    }

    public void Insert(T item, int index)
    {
        if (firstNode == null)
        {
            throw new Exception("List is empty");
        }
        if (index < 0)
        {
            throw new Exception("Location exception");
        }
        var node = firstNode.NextNode;
        int i;
        for (i = 1; i < index && node != null; i++)
        {
            node = node.NextNode;
        }
        if (i == index)
        {
            var nextNode = node.NextNode == null ? null : node.NextNode;
            node.NextNode = new LinkNode<T> { Node = item, NextNode = nextNode };
        }
        else
        {
            throw new Exception("Locaion exception");
        }
    }

    public bool IsEmpty()
    {
        return firstNode == null;
    }

    public int LocateItem(T t)
    {
        var index = 0;
        var node = firstNode;
        while(!node.Node.Equals(t))
        {
            index++;
            if(node.NextNode == null)
            {
                return -1;
            }
            node = node.NextNode;
        }
        return index;
    }
}

public class LinkNode<T>
{
    public T Node
    {
        get; set;
    }

    public LinkNode<T> NextNode
    {
        get; set;
    }
}

其它鏈表

之前說了,如果你想放棄排隊,就要將排在你后面的人的手機號碼告訴前一個人。但是由於不知道排在你前一個人是誰,所以只能去找食堂大媽,食堂大媽去找排在第一個的人,然后通過一層層找下去,才知道你前一個人是誰,這樣就很麻煩,能不能讓你不但知道后一個人的號碼,也知道前一個人的呢?
當然可以,那就是雙向鏈表。
雙向鏈表就是在單鏈表的每個節點中,再設置一個指向其前驅節點的指針域。
用C#代碼表示就是:

public class LinkNode<T>
{
    public T Node
    {
        get; set;
    }

    public LinkNode<T> NextNode
    {
        get; set;
    }

    public LinkNode<T> PrevNode
    {
        get; set;
    }
}

除了雙向鏈表,還有循環鏈表、靜態鏈表等,大家都可以自己實現。

單鏈表結構與順序存儲結構的優缺點

每種數據結構都不是萬能的(不然還要這么多數據結構干嘛),單鏈表和順序存儲結構都有各自的優缺點和適用場景。

順序存儲結構的優缺點

優點:

  • 無須為表示表中元素之間的邏輯關系而增加額外的存儲空間(鏈表就需要額外存儲一個指向后繼的指針域)
  • 可以快速地存取表中任意位置的元素(直接通過下標返回即可,鏈表需要循環去取)

缺點:

  • 插入和刪除操作需要移動大量元素
  • 當線性表長度變化較大時,難以確定存儲空間的容量(也可以通過達到一定長度時進行擴容的方式來改善)
  • 造成存儲空間的碎片(可以通過少於一定長度時進行壓縮的方式來改善)

單鏈表的優缺點

對於插入或刪除越是頻繁的操作,單鏈表的效率優勢就越是明顯,但是獲取元素的效率非常低。

適用場景

  • 若線性表需要頻繁查找,很少進行插入和刪除操作,使用順序存儲結構。若需要頻繁插入和刪除,使用單鏈表結構。
  • 若線性表中的元素個數變化較大或者根本不知道有多大時,最好使用單鏈表結構,這樣可以不考慮存儲空間的大小問題。而如果事先知道線性表的長度,使用順序存儲結構效率會高很多。

完整數據結構C#實現代碼

Github地址——DataStructure


免責聲明!

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



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