LinkedList<T>是系统定义的一个双向链表。它由LinkedListNode<T>链接而成。其元素LinkedListNode<T>指向其前一元素和后一元素。
链表的优点是:想链表中插入元素时,不用移动该元素后边的所有元素,只需要修改上一个元素的Next和下一个元素的Previous。
链表的缺点是:链表中的元素只能一个个地访问,查找元素时比较耗时。
链表不仅能在列表中存储元素,在存储元素时链表还必须存储每个元素下一个元素和上一个元素的信息。这就是LinkedList<T> 包含LinkedListNode的原因。
使用LinkedListNode<T>类,可以获得列表中的下一个元素和上一个元素。LinkedListNode<T>定义了属性Next、Pervious、List和Value,如下图所示:
List属性返回与该LinkedListNode相关的LinkedList<T>对象。Value属性返回该节点包含的值。Next和Previous分别表示LinkedList<T>中该节点的下一个节点和前一个节点,这两个属性用于遍历链表。
LinkedList<T>类定义的成员可以访问链表中的第一个和最后一个元素(First和Last)、在指定位置插入元素(AddAfter、Addbefore、AddFirst和AddLast)、删除指定位置的元素(Remove、RemoveFirst、RemoveLast方法)、从链表开始或结尾查找元素(Find、FindLast方法)。
示例:用List和LinkerList表示带有优先级的文档
下面是一个示例程序,该程序使用了一个链表(LinkerList)和一个列表(List)。链表包含一个带有优先级的文档。在链表中文档按照优先级来排序,如果多个文档优先级相同,那么就按照文档的插入时间来排序。
为了快速访问不同优先级的元素,我们定义了一个列表,列表中包含的10个元素分别引用每个优先级的最后一个文档。因此,列表List<T>定义成了List<LinkedListNode<Document>>类型。对每个优先级的最后一个文档的引用称为优先级节点。
先来定义Document类:
namespace MyLinkedListSample { public class Document { public string Title { get; private set; } public string Content { get; private set; } public byte Priority { get; private set; } public Document(string title, string content, byte priority) { this.Title = title; this.Content = content; this.Priority = priority; } } }
这个类定义了文件名称、文件内容和优先级,并提供了一个构造函数用来初始化这个文件。
解决方案的核心是PriorityDocumentManager类。通过这个类的公共接口,可以向链表中添加Document类型的元素。可以检索不同优先级的文档。这个类包含了两个集合:List<LinkedListNode<Document>>最多10个元素类型的集合和LinkerList<Document>类型的包含所有元素的集合。他们是添加指定优先级的新文档的入口。
这两个集合都用PriorityDocumentManager的构造函数进行初始化,列表集合List<LinkedListNode<Document>>也用null进行初始化:
public class PriorityDocumentManager { private readonly LinkedList<Document> documentList; private readonly List<LinkedListNode<Document>> priorityNodes; public PriorityDocumentManager() { this.documentList = new LinkedList<Document>(); this.priorityNodes = new List<LinkedListNode<Document>>(10); for (int i = 0; i < 10; i++) { priorityNodes.Add(new LinkedListNode<Document>(null)); } }
}
在类的公共接口中,添加一个AddDocument(Document document)方法,调用私有的AddDocumentToPriorityNode(Document document, int priority)方法。
AddDocumentToPriorityNode(Document document, int priority)使用了递归,所以需要把实现代码单独放置:
public void AddDocument(Document document) { if (document == null) throw new ArgumentNullException("Document is null"); this.AddDocumentToPriorityNode(document, document.Priority); }
在AddDocumentToPriorityNode方法中,首先检查优先级是否在允许的范围内:
if (priority > 9 || priority < 0) { throw new ArgumentException("优?先è级?应|该?在ú1到?9之?间?。£"); }
接着检查是否已有一个优先级节点与所传入的优先级相同。如果列表中没有这样的节点,就递归调用AddDocumentToPriorityNode方法,递减优先级,检查是否有低一级的节点,如果优先级节点的优先级值与所传送的优先级值不同,也没有比该优先级更低的节点,就调用AddLast()方法,将文档安全地添加到链表的末尾。
if (this.priorityNodes[priority].Value == null) { --priority; if (priority >= 0) { AddDocumentToPriorityNode(document, priority); } else { documentList.AddLast(document); priorityNodes[document.Priority] = documentList.Last; } } 如果存在这样的优先级节点,就找到了在链表中插入文档的位置。如果存在指定优先级的节点,就可以把新文档插入到由优先级节点引用的位置后边。因为优先级节点总是引用优先级值的最后一个文档。
if (priority == document.Priority)
{
documentList.AddAfter(prioNode, document);
priorityNodes[document.Priority] = prioNode.Next;
}
如果引用文档的优先级节点有较低的优先级值,新文档必须插入优先级值与优先级节点相同的所有文档的前面。为了找到优先级值相同的第一个文档,使用了While循环,使用Previous属性遍历所有的链表节点,知道找到一个优先级值不同的链表节点为止。这样就找到了插入节点的位置并可以设置优先级节点了。
else { LinkedListNode<Document> firstPrioNode = prioNode; while (firstPrioNode.Previous != null && firstPrioNode.Previous.Value.Priority == prioNode.Value.Priority) { firstPrioNode = prioNode.Previous; prioNode = firstPrioNode; } documentList.AddBefore(firstPrioNode, document); priorityNodes[document.Priority] = firstPrioNode.Previous; }
之后实现一个DisplayAllNode方法,用于在控制台上显示链表中的所有元素:
public void DisplayAllNode() { foreach (var item in documentList) { Console.WriteLine("Priority {0},title {1}", item.Priority, item.Title); } }
PriorityDocumentManager类的完整代码如下:
namespace MyLinkedListSample { public class PriorityDocumentManager { private readonly LinkedList<Document> documentList; private readonly List<LinkedListNode<Document>> priorityNodes; public PriorityDocumentManager() { this.documentList = new LinkedList<Document>(); this.priorityNodes = new List<LinkedListNode<Document>>(10); for (int i = 0; i < 10; i++) { priorityNodes.Add(new LinkedListNode<Document>(null)); } }
public void DisplayAllNode() { foreach (var item in documentList) { Console.WriteLine("Priority {0},title {1}", item.Priority, item.Title); } } public Document GetDocument() { Document doc = documentList.First.Value; documentList.RemoveFirst(); return doc; } public void AddDocument(Document document) { if (document == null) throw new ArgumentNullException("Document is null"); this.AddDocumentToPriorityNode(document, document.Priority); } private void AddDocumentToPriorityNode(Document document, int priority) { if (priority > 9 || priority < 0) { throw new ArgumentException("优?先è级?应|该?在ú1到?9之?间?。£"); } if (this.priorityNodes[priority].Value == null) { --priority; if (priority >= 0) { AddDocumentToPriorityNode(document, priority); } else { documentList.AddLast(document); priorityNodes[document.Priority] = documentList.Last; } } else { LinkedListNode<Document> prioNode = priorityNodes[priority]; if (priority == document.Priority) { documentList.AddAfter(prioNode, document); priorityNodes[document.Priority] = prioNode.Next; } else { LinkedListNode<Document> firstPrioNode = prioNode; while (firstPrioNode.Previous != null && firstPrioNode.Previous.Value.Priority == prioNode.Value.Priority) { firstPrioNode = prioNode.Previous; prioNode = firstPrioNode; } documentList.AddBefore(firstPrioNode, document); priorityNodes[document.Priority] = firstPrioNode.Previous; } } } } }
最后,查看下今晚的成果:
在Main方法中,为链表添加几个优先级不同的文档,之后调用DisplayAllNode方法显示整个链表:
static void Main() { PriorityDocumentManager 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)); System.Console.Read(); }
输出:
注:本文代码抄自C#高级编程。打出来仅为理解链表这东西。本例中比较难以理解的是PriorityDocumentManager中的AddDocumentToPriorityNode方法,该方法是用递归来查找需要插入的位置,分了集中请款进行判断。