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

posted @ 2015-08-27 17:04  Shoring  阅读(1936)  评论(5编辑  收藏  举报