链表

LinkedList<T>是一个双向列表,其元素指向它前面和后面的元素。这样一来,通过移动到下一个元素可以正向遍历整个链表,通过移动到前一个元素可以反向遍历整个链表。

链表的优点是,如果将元素插入列表的中间位置,使用链表就会非常的快。在插入一个元素时,只需要修改上一个元素的Next引用和下一个元素的Previous引用,使他们引用所插入的元素。在List<T>类中,插入一个元素时,需要移动该元素后面的所有元素。

当然,链表也有缺点。链表的元素只能一个接一个地访问,这需要较长的时间来查找位于链表中间或尾部的元素。

链表不能在列表中仅存储元素。存储元素时,链表还必须存储每个元素的下一个元素和上一个元素的信息。这就是LinkedList<T>包含LinkedListNode<T>类型的元素的原因。使用LinkedListNode<T>类,可以获得列表中的下一个元素和上一个元素。LinkedListNode<T>定义了属性List、Next、Previous和Value。List属性返回与节点相关的LinkedList<T>对象,Next和Previous属性用于遍历列表,访问当前节点之后和之前的节点。Value返回与节点相关的元素,其类型是T。

LinkedList<T>类定义的成员可以访问链表中的第一个和最后一个元素(First和Last)、在指定位置插入元素(AddAfter()、AddBefore()、AddFirst()和AddLast()方法),删除指定位置的元素(Remove()、RemoveFirst()和RemoveLast()方法)、从链表的开头(Find()方法)、或结尾(FindLast()方法)开始搜索元素。

示例应用程序使用了一个链表和一个列表。链表包含文档,这与上一个队列例子相同,但文档有一个额外的优先级。在链表中,文档按照优先级来排序。如果多个文档的优先级相同,这些元素就按照文档的插入时间来排序。

在链表中添加新文档时,它们应放在优先级相同的最后一个文档后面。集合LinkedList<Document>包含LinkedListNode<Document>类型的元素。LinkedListNode<T>类添加Next和Previous属性,使搜索过程能从一个节点移动到下一个节点上。要引用这类元素,应把List<T>定义为List<LinkedListNode<Document>>。为了快速访问每个优先级的最后一个文档,集合List<LinkedListNode<Document>>应最多包含10个元素,每个元素分别引用每个优先级的最后一个文档。在后面的讨论中,对每个优先级的最后一个文档的引用称为优先级节点。

在示例中,Document类扩展为包含优先级。优先级用类的构造函数设置:

    public class Document{
        public string Title{get;}
        public string Content{get;}
        public byte Priority{get;}
        public Document(string title,string content,byte priority){
            Title = title;
            Content = content;
            Priority = priority;

        }
    }

解决方案的核心是PriorityDocumentManager类。这个类很容易使用。在这个类的公共借口中,可以把新的Document元素添加到链表中,可以检索第一个文档,为了便于测试,他还提供了一个方法,在元素链接到链表中时,该方法可以显示集合中的所有元素。

PriorityDocumentManaget类包含两个集合。LinkedList<Document>类型的集合包含所有的文档。LIst<LinkedListNode<Document>>类型的集合包含最多10个元素的引用,它们是添加指定优先级的新文档的入口点。这两个集合变量都用PriorityDocmentManager类的构造函数来初始化。列表集合也用null初始化:

    public class PriorityDocumentManager{
        private readonly LinkedList<Document> _documenetList;
        private readonly List<LinkedListNode<Document>> _priorityNodes;
        public PriorityDocumentManager(){
            _documenetList = new LinkedList<Document>();
            _priorityNodes = new List<LinkedListNode<Document>>(10);
            for(int i = 0;i<10;i++){
                _priorityNodes.Add(new LinkedListNode<Document>(null));

            }
        }

在类的公共接口中,有一个AddDocument()方法。AddDocument()方法只调用私有方法AddDocumentToPriorityNode()。把实现代码放在另一个方法中的原因是,AddDocumentToPriorityNode()方法可以递归调用,如后面所示。

        public void AddDocument(Document d){
            if(d == null) throw new ArgumentException(nameof(d));
            AddDocumentToPriorityNode(d,d.Priority);

        }

在AddDocumentToPriorityNode()方法的实现代码中,第一个操作是检查优先级是否在允许的优先级范围内。这里允许的范围是0~9。如果传送了错误的值,就会抛出一个ArgumentException类型的异常。

接着检查是否已经有一个优先级节点与所传送的优先级相同。如果在列表集合中没有这样的优先级节点,就递归调用AddDocumentToPriorityNode()方法,递减优先级值,检查是否有低一级的优先级节点。

如果优先级节点的优先级值与所传送的优先值不同,也没有比该优先级值更低的优先级节点,就可以调用AddLast()方法,将文档安全地添加到链表的末尾。另外,链表节点由负责指定文档优先级的优先级节点引用。

如果存在这样的优先级节点,就可以在链表中找到插入文档的位置。这里必须区分是存在指定优先值的优先级节点,还是存在以较低的优先级值引用文档的优先级节点。对于第一种情况,可以把新文档插入由优先级节点引用的位置的后面。因为优先级节点总是引用指定优先级值的最后一个文档,所以必须设置优先级节点的引用。如果引用文档的优先级节点有较低的优先值,情况就会比较复杂。这里新文档必须插入优先级值与优先级节点相同的所有文档的前面。为了找到优先级值相同的第一个文档,要通过一个while循环,使用Previous属性遍历所有的链表节点,直到找到一个优先级值不同的链表节点为止。这样,就找到了必须插入文档的位置,并可以设置优先级节点。

        private void AddDocumentToPriorityNode(Document doc,int priority){
            if(priority > 9 && priority < 0)
            throw new ArgumentException("Priority must be between 0 and 9");
            if(_priorityNodes[priority].Value == null){
                --priority;
                if(priority >= 0){
                    AddDocumentToPriorityNode(doc,priority);

                }
                else{
                    _documenetList.AddLast(doc);
                    _priorityNodes[doc.Priority] = _documenetList.Last;
                }
                return;

            }
            else{
                LinkedListNode<Document> prioNode = _priorityNodes[priority];
                if(priority == doc.Priority){
                    _documenetList.AddAfter(prioNode,doc);
                    _priorityNodes[doc.Priority] = prioNode.Next;

                }
                else{
                    LinkedListNode<Document> firstPrioNode = prioNode;
                    while(firstPrioNode.Previous != null && firstPrioNode.Previous.Value.Priority == prioNode.Value.Priority){
                        firstPrioNode = prioNode.Previous;
                        prioNode = firstPrioNode;

                    }
                    _documenetList.AddBefore(firstPrioNode,doc);
                    _priorityNodes[doc.Priority] = firstPrioNode.Previous;
                }
            }

        }

现在还剩下几个简单的方法没有讨论。DisplayAllNodes()方法只是在一个foreach循环中,把每个文档的优先级和标题显示在控制台上。

GetDocument()方法从链表中返回第一个文档(优先级最高的文档),并从链表中删除它:

        public void DisplayAllNodes(){
            foreach(Document doc in _documenetList){
                System.Console.WriteLine($"priority: {doc.Priority},title: {doc.Title}");

            }
        }
        public Document GetDocument(){
            Document document= _documenetList.First.Value;
            _documenetList.RemoveFirst();
            return document;
        }

在Main()方法中,PriorityDocumentManager类用于说明其功能。在链表中添加8个优先级不同的新文档,再显示整个链表:

        static void Main(string[] args)
        {
            var pdm = new PriorityDocumentManager();
            pdm.AddDocument(new Document("one","Sample",8));
            pdm.AddDocument(new Document("two","Sample",3));
            pdm.AddDocument(new Document("three","Sample",4));
            pdm.AddDocument(new Document("four","Sample",8));
            pdm.AddDocument(new Document("five","Sample",1));
            pdm.AddDocument(new Document("six","Sample",9));
            pdm.AddDocument(new Document("seven","Sample",1));
            pdm.AddDocument(new Document("eight","Sample",1));
            pdm.DisplayAllNodes();
            //Console.WriteLine("Hello World!");
        }

输出如下:

priority: 9,title: six
priority: 8,title: one
priority: 8,title: four
priority: 4,title: three
priority: 3,title: two
priority: 1,title: five
priority: 1,title: seven
priority: 1,title: eight

完整代码:

using System;
using System.Collections.Generic;

namespace 链表
{
    class Program
    {
        static void Main(string[] args)
        {
            var pdm = new PriorityDocumentManager();
            pdm.AddDocument(new Document("one","Sample",8));
            pdm.AddDocument(new Document("two","Sample",3));
            pdm.AddDocument(new Document("three","Sample",4));
            pdm.AddDocument(new Document("four","Sample",8));
            pdm.AddDocument(new Document("five","Sample",1));
            pdm.AddDocument(new Document("six","Sample",9));
            pdm.AddDocument(new Document("seven","Sample",1));
            pdm.AddDocument(new Document("eight","Sample",1));
            pdm.DisplayAllNodes();
            //Console.WriteLine("Hello World!");
        }
    }

    public class Document{
        public string Title{get;}
        public string Content{get;}
        public byte Priority{get;}
        public Document(string title,string content,byte priority){
            Title = title;
            Content = content;
            Priority = priority;

        }
    }

    public class PriorityDocumentManager{
        private readonly LinkedList<Document> _documenetList;
        private readonly List<LinkedListNode<Document>> _priorityNodes;
        public PriorityDocumentManager(){
            _documenetList = new LinkedList<Document>();
            _priorityNodes = new List<LinkedListNode<Document>>(10);
            for(int i = 0;i<10;i++){
                _priorityNodes.Add(new LinkedListNode<Document>(null));

            }
        }
        public void AddDocument(Document d){
            if(d == null) throw new ArgumentException(nameof(d));
            AddDocumentToPriorityNode(d,d.Priority);

        }

        private void AddDocumentToPriorityNode(Document doc,int priority){
            if(priority > 9 && priority < 0)
            throw new ArgumentException("Priority must be between 0 and 9");
            if(_priorityNodes[priority].Value == null){
                --priority;
                if(priority >= 0){
                    AddDocumentToPriorityNode(doc,priority);

                }
                else{
                    _documenetList.AddLast(doc);
                    _priorityNodes[doc.Priority] = _documenetList.Last;
                }
                return;

            }
            else{
                LinkedListNode<Document> prioNode = _priorityNodes[priority];
                if(priority == doc.Priority){
                    _documenetList.AddAfter(prioNode,doc);
                    _priorityNodes[doc.Priority] = prioNode.Next;

                }
                else{
                    LinkedListNode<Document> firstPrioNode = prioNode;
                    while(firstPrioNode.Previous != null && firstPrioNode.Previous.Value.Priority == prioNode.Value.Priority){
                        firstPrioNode = prioNode.Previous;
                        prioNode = firstPrioNode;

                    }
                    _documenetList.AddBefore(firstPrioNode,doc);
                    _priorityNodes[doc.Priority] = firstPrioNode.Previous;
                }
            }

        }
        public void DisplayAllNodes(){
            foreach(Document doc in _documenetList){
                System.Console.WriteLine($"priority: {doc.Priority},title: {doc.Title}");

            }
        }
        public Document GetDocument(){
            Document document= _documenetList.First.Value;
            _documenetList.RemoveFirst();
            return document;
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值