Linked Lists
A collection class that has no similar version with
a nongeneric collection is LinkedList<T>. LinkedList<T> is a doubly linked list, where
one element references the next and the previous one, as shown in
Figure 10-3.
The advantage of a linked list is that if items are
inserted in the middle of a list, the linked list is very fast.
When an item is inserted, only the Next
reference of the previous item and the Previous reference of the next item must be changed
to reference the inserted item. With the List<T> and ArrayList classes, when an element is inserted all
following elements must be moved.
Of course, there’s also a disadvantage with linked
lists. Items of linked lists can only be accessed one after the
other. It takes a long time to find a item that’s somewhere in the
middle or at the end of the list.
A linked list can not just store the items inside
the list; together with every item, the linked list must have
information about the next and previous items. That’s why the
LinkedList<T> contains items of
type LinkedListNode<T>. With the
class LinkedListNode<T>, you can
get to the next and previous items in the list. The following table
describes the properties of LinkedListNode<T>.
The class LinkedList<T> implements the interfaces
ICollection<T>, IEnumerable<T>, ICollection, IEnumerable,
ISerializable, and IDeserializationCallback. Members of this class are
explained in the following table.
The sample application uses a linked list,
LinkedList<T>, together with a
list, List<T>. The linked list
contains documents as in the previous example, but the documents
have an additional priority associated with them. The documents
will be sorted inside the linked list depending on the priority. If
multiple documents have the same priority, the elements are sorted
according to the time the document was inserted.
Figure
10-4 describes the collections of the sample application.
LinkedList<Document> is the linked
list containing all the Document
objects. The figure shows the title and the priority of the
documents. The title
indicates when the document was added to the list: The first
document added has the title One, the second document Two, and so
on. You can see that the documents One and Four have the same
priority, 8, but because One was added before Four, it is earlier
in the list.
When new documents are added to the linked list,
they should be added after the last document that has the same
priority. A LinkedList<Document>
collection contains elements of type LinkedListNode<Document>. The class
LinkedListNode<T> adds
Next and Previous properties to walk from one node to the
next. For referencing such elements, the List<T> is defined as List<LinkedListNode<Document>>. For fast
access to the last document of every priority, the collection
List<LinkedListNode> contains up
to 10 elements, each referencing the last document of every
priority. In the upcoming discussion, the reference to the last
documents of every priority is called the priority node.
From the previous example, the Document class is extended to contain the priority.
The priority is set with the constructor of the class:
The heart of the solution is the PriorityDocumentManager class. This class is very
easy to use. With the public interface of this class new
Document elements can be added to the
linked list, the first document can be retrieved, and for testing
purposes it also has a method to display all elements of the
collection as they are linked in the list.
The class PriorityDocumentManager contains two collections.The
collection of type LinkedList<Document> contains all documents.
The collection of type List<LinkedListNode<Document>> contains
references of up to 10 elements that are entry points to add new
documents with a specific priority. Both collection variables are
initialized with the constructor of the class PriorityDocumentManager. The list collection is also
initialized with null:
Part of the public interface of the class is the
method AddDocument(). AddDocument() does nothing more than call the
private method AddDocumentToPriorityNode(). The reason for having
the implementation inside a different method is that AddDocumentToPriorityNode() may be called
recursively, as you will see soon.
The first action that is done in the implementation
of AddDocumentToPriorityNode() is a
check to see if the priority fits in the allowed priority range.
Here, the allowed range is between 0 and 9. If a wrong value is
passed, an exception of type ArgumentException is thrown.
Next, you check if there’s already a priority node
with the same priority as the priority that was passed. If there’s
no such priority node in the list collection, AddDocumentToPriorityNode() is invoked recursively
with the priority value decremented to check for a priority node
with the next lower priority.
If there’s no priority node with the same priority
or any priority with a lower value, the document can be safely
added to the end of the linked list by calling the method
AddLast(). Also, the linked list node is
referenced by the priority node that’s responsible for the priority
of the document.
If there’s an existing priority node, you can get
the position inside the linked list where the document should be
inserted. Here, you must differentiate if a priority node already
exists with the correct priority, or if there’s just a priority
node that references a document with a lower priority. In the first
case, you can just insert the new document after the position
that’s referenced by the priority node. Because the priority node
always must reference the last document with a specific priority,
the reference of the priority node must be set. It gets more
complex if just a priority node referencing a document with a lower
priority exists. Here, the document must be inserted before all
documents with the same priority as the priority node. To get the
first document of the same priority, a while loop iterates through all linked list nodes,
using the Previous property, until a
linked list node is reached that has a different priority. This
way, you know the position where the document must be inserted, and
the priority node can be set.
Now only simple methods are left for discussion.
DisplayAllNodes() just does a
foreach loop to display the priority and
the title of every document to the console.
The method GetDocument()
returns the first document (the document with the highest priority)
from the linked list and removes it from the list:
In the Main() method the
PriorityDocumentManager is used to
demonstrate its functionality. Eight new documents with different
priorities are added to the linked list, and then the complete list
is displayed:
With the processed result you can see that the
documents are sorted first by the priority and second by when the
document was added: