对于许多应用程序而言,最好把数据存储成列表的形式,而且列表在日常生活中是很常见的:待办事件列表、购物清单、前十名名单等等。本章会研究一种特殊类型的列表、即链表。
11.1 数组存在的问题
在处理列表的时候数组是常用的数据结构。数组可以对所存储的数据项提供快速的存取访问,而且它很易于进行循环遍历操作。当然,数组已经是语言的一部分了,用户不需要使用额外的内存,也不需要花费因使用用户自定义的数据结构所需的处理时间
然而正如所见,数组不是一种最佳的数据结构。在无序数组中查找一个数据项是很慢的,这是因为在找到要查找的元素之前需要尽可能地访问到数组内的每一个元素,有序数组对查找而言会更加高效一点,但是插入和删除操作还是很慢的,因为需要向前或向后移动元素来为插入留出空间,或者为删除移除空间,更别提在有序数组内海需要为插入元素查找到合适的位置了。
链表几乎可以用于每一种使用数组的情况中,除非需要随机存取访问列表内的数据项,这时数组或许会是最好的选择。
11.2 链表的定义
11.3 面向对象链表的设计
链表的设计至少包含两个类。这里会创建一个Node类,而且每次向链表添加节点的时候会实例化一个Node对象。链表内的节点通过索引与其他节点相互连接在一起。而且使用在一个独立的LinkedList类中所创建的方法设置引用。
11.3.1 Node类
public class Node
{
public Object Element;
public Node link;
public Node()
{
Element = null;
link = null;
}
public Node(Object theElement)
{
Element = theElement;
link = null;
}
}
11.3.2 LinkedList类
LinkedList类用来创建链表中节点之间的链接。
public class LinkedList
{
protected Node header;
public LinkedList()
{
header = new Node("header");
}
}
为了在已有节点的后边插入新节点,需要首先找到这个“之后”的节点。
private Node Find(Object item)
{
Node current = header;
//链表只能从头开始查
while (current.Element != item)
current = current.Link;
return current;
}
//插入一对象
public void Insert(Object newItem, Object after)
{
Node current = new Node();
Node newNode = new Node(newItem);
current = Find(after);
//注意赋值的先后顺序
newNode.Link = current.Link;
current.Link = newNode;
}
//移除一对象
public void Remove(Object item)
{
Node p = FindPrevious(item);
if (p.Link != null)
p.Link = p.Link.Link;
}
//输出单向链表中各个节点的值
public void PrintList()
{
Node current = header;
while (current.Link != null)
{
Console.WriteLine(current.Link.Element);
current = current.Link;
}
}
11.4 链表设计的改进方案
11.4.1 双向链表
//双向链表中的节点
public class DNode
{
public Object Element;
//链向后一个节点
public DNode Flink;
//链向前一个节点
public DNode Blink;
public DNode()
{
Flink = null;
Blink = null;
}
public DNode(Object theElement)
{
Element = theElement;
Flink = null;
Blink = null;
}
}
//双向链表
public class DoublyLinkedList
{
protected DNode header;
public DoublyLinkedList()
{
header = new DNode("header");
}
private DNode Find(Object item)
{
DNode current = header;
while (current.Element != item)
current = current.Flink;
return current;
}
public void Insert(Object newItem, Object after)
{
DNode current = new DNode();
DNode newNode = new DNode(newItem);
current = Find(after);
newNode.Flink = current.Flink;
newNode.Blink = current;
current.Flink = newNode;
if (newNode.Flink != null)
{
newNode.Flink.Blink = newNode;
}
}
public void Remove(Object n)
{
DNode p = Find(n);
if (p.Blink == null)
//或者用这句判断
// if(p == header)
throw new Exception("The header can not be removed");
if (p != null)
{
if (p.Flink != null)
{
p.Flink.Blink = p.Blink;
}
//由于p.Blink为null时会抛出异常,所以这里没有必要判断p.Blink不为null才执行下面一条语句
p.Blink.Flink = p.Flink;
p.Flink = null;
p.Blink = null;
}
}
private DNode FindLast()
{
DNode current = header;
while (current.Flink != null)
current = current.Flink;
return current;
}
//输出双向链表中各个节点的值
public void PrintList()
{
DNode current = header;
while (current.Flink != null)
{
Console.WriteLine(current.Flink.Element);
current = current.Flink;
}
}
//反向输出双向链表中各个节点的值
public void PrintListReverse()
{
DNode current = FindLast();
while (current.Blink != null)
{
//注意不需要加Blink,因为不用输出头节点
Console.WriteLine(current.Element);
current = current.Blink;
}
}
}
//双向链表中的节点
public class DNode
{
public Object Element;
//链向后一个节点
public DNode Flink;
//链向前一个节点
public DNode Blink;
public DNode()
{
Flink = null;
Blink = null;
}
public DNode(Object theElement)
{
Element = theElement;
Flink = null;
Blink = null;
}
}
11.4.2 循环链表
//环形链表
public class CircularlyLinkedList
{
protected Node header;
private int count;
public int Count
{
get
{
return count;
}
}
public CircularlyLinkedList()
{
header = new Node("header");
//初始化时头节点指向自身
header.Link = header;
count = 0;
}
public bool IsEmpty()
{
//如果头节点指向自身,则说明环形链表为空
return header.Link == header;
//或者看count是否等于0
//return count == 0;
}
public void Clear()
{
header.Link = header;
count = 0;
}
private Node FindPrevious(Object item)
{
Node current = header;
while (current.Link != header && current.Link.Element != item)
current = current.Link;
return current;
}
public void Remove(Object item)
{
Node p = FindPrevious(item);
//避免移除header节点
if (p.Link != header)
{
p.Link = p.Link.Link;
count--;
}
}
private Node Find(Object item)
{
Node current = header.Link;
while (current.Element != item)
{
current = current.Link;
//如果找遍整个环形链表找不到,则返回null
if (current.Link == header.Link)
return null;
}
return current;
}
public void Insert(Object newItem, Object after)
{
Node newNode = new Node(newItem);
Node current = Find(after);
if (current != null)
{
newNode.Link = current.Link;
current.Link = newNode;
count++;
}
//如果找不到相关节点,则将新节点插入头节点后面
else
InsertFirst(newItem);
}
public void InsertFirst(Object item)
{
Node current = new Node(item);
current.Link = header.Link;
header.Link = current;
count++;
}
public Node Move(int n)
{
Node current = header.Link;
for (int i = 0; i < n; i++)
current = current.Link;
//确保不是header节点
if (current == header)
current = current.Link;
return current;
}
public void PrintList()
{
Node current = header;
while (current.Link != header)
{
Console.WriteLine(current.Link.Element);
current = current.Link;
}
}
}
最后来调用一下:
class Program
{
static void Main(string[] args)
{
//单向链表
LinkedList list = new LinkedList();
list.Insert("SevenXue", "header");
list.Insert("薛江白", "header");
list.Insert("徐丽华", "薛江白");
list.Insert("刘明", "SevenXue");
list.Remove("SevenXue");
Console.WriteLine("输出单向链表中的值:");
list.PrintList();
Console.WriteLine();
//双向链表
DoublyLinkedList dlist = new DoublyLinkedList();
dlist.Insert("SevenXue", "header");
dlist.Insert("1", "header");
dlist.Insert("2", "1");
dlist.Insert("3", "1");
dlist.Insert("9", "2");
dlist.Insert("7", "3");
dlist.Remove("SevenXue");
dlist.Insert("XUE", "9");
dlist.Remove("3");
Console.WriteLine("正向输出双向链表中的值:");
dlist.PrintList();
Console.WriteLine("反向输出双向链表中的值:");
dlist.PrintListReverse();
Console.WriteLine();
//环形链表
CircularlyLinkedList clist = new CircularlyLinkedList();
clist.Insert("2", "header");
clist.InsertFirst("7");
Console.WriteLine(clist.IsEmpty() ? "环形链表为空" : "环形链表不为空");
if (clist.Count > 0)
clist.PrintList();
//header节点是无法被移除的
clist.Remove("header");
clist.Remove("2");
clist.Remove("7");
Console.WriteLine("输出环形链表中的节点个数:");
Console.WriteLine(clist.Count);
clist.InsertFirst("99");
clist.InsertFirst("56");
clist.Insert("78", "56");
Console.WriteLine("输出环形链表中的值:");
if (clist.Count > 0)
clist.PrintList();
clist.Remove("99");
clist.Clear();
Console.WriteLine("输出环形链表中的值:");
if (clist.Count > 0)
clist.PrintList();
else
Console.WriteLine("环形链表为空");
clist.InsertFirst("79");
clist.InsertFirst("42");
clist.Insert("38", "79");
//3这个节点不存在,所以插入到header节点之后
clist.Insert("80", "3");
clist.Insert("66", "80");
clist.Insert("23", "66");
clist.InsertFirst("27");
clist.Remove("38");
Console.WriteLine("输出环形链表中的值:");
if (clist.Count > 0)
clist.PrintList();
Console.WriteLine("输出环形链表中的节点个数:");
Console.WriteLine(clist.Count);
Node node = clist.Move(1);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(2);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(5);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(6);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(7);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(8);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
Console.ReadKey();
}
}
输出结果如下图所示:
注:代码中环形链表使用的节点类型同单向链表的一样;其实在.NET中提供了对链表的支持,节点和链表对应的类分别是LinkedListNode<T>和LinkedList<T>,可以通过.NET Reflector来查看它们的实现代码,不过在看它们的实现之前,还是先把我这篇文章里的代码看懂了,要不然是看不懂.NET中是如何实现的。。
//单向链表
public class LinkedList
{
protected Node header;
public LinkedList()
{
header = new Node("header");
}
private Node Find(Object item)
{
Node current = header;
//链表只能从头开始查
while (current.Element != item)
current = current.Link;
return current;
}
//插入一对象
public void Insert(Object newItem, Object after)
{
Node current = new Node();
Node newNode = new Node(newItem);
current = Find(after);
//注意赋值的先后顺序
newNode.Link = current.Link;
current.Link = newNode;
}
private Node FindPrevious(Object item)
{
Node current = header;
while ((current.Link != null) && (current.Link.Element != item))
current = current.Link;
return current;
}
//移除一对象
public void Remove(Object item)
{
Node p = FindPrevious(item);
if (p.Link != null)
p.Link = p.Link.Link;
}
//输出单向链表中各个节点的值
public void PrintList()
{
Node current = header;
while (current.Link != null)
{
Console.WriteLine(current.Link.Element);
current = current.Link;
}
}
}
//单向链表中的节点
public class Node
{
public Object Element;
public Node Link;
public Node()
{
Element = null;
Link = null;
}
public Node(Object theElement)
{
Element = theElement;
Link = null;
}
}
//双向链表
public class DoublyLinkedList
{
protected DNode header;
public DoublyLinkedList()
{
header = new DNode("header");
}
private DNode Find(Object item)
{
DNode current = header;
while (current.Element != item)
current = current.Flink;
return current;
}
public void Insert(Object newItem, Object after)
{
DNode current = new DNode();
DNode newNode = new DNode(newItem);
current = Find(after);
newNode.Flink = current.Flink;
newNode.Blink = current;
current.Flink = newNode;
if (newNode.Flink != null)
{
newNode.Flink.Blink = newNode;
}
}
public void Remove(Object n)
{
DNode p = Find(n);
if (p.Blink == null)
//或者用这句判断
// if(p == header)
throw new Exception("The header can not be removed");
if (p != null)
{
if (p.Flink != null)
{
p.Flink.Blink = p.Blink;
}
//由于p.Blink为null时会抛出异常,所以这里没有必要判断p.Blink不为null才执行下面一条语句
p.Blink.Flink = p.Flink;
p.Flink = null;
p.Blink = null;
}
}
private DNode FindLast()
{
DNode current = header;
while (current.Flink != null)
current = current.Flink;
return current;
}
//输出双向链表中各个节点的值
public void PrintList()
{
DNode current = header;
while (current.Flink != null)
{
Console.WriteLine(current.Flink.Element);
current = current.Flink;
}
}
//反向输出双向链表中各个节点的值
public void PrintListReverse()
{
DNode current = FindLast();
while (current.Blink != null)
{
//注意不需要加Blink,因为不用输出头节点
Console.WriteLine(current.Element);
current = current.Blink;
}
}
}
//双向链表中的节点
public class DNode
{
public Object Element;
//链向后一个节点
public DNode Flink;
//链向前一个节点
public DNode Blink;
public DNode()
{
Flink = null;
Blink = null;
}
public DNode(Object theElement)
{
Element = theElement;
Flink = null;
Blink = null;
}
}
//环形链表
public class CircularlyLinkedList
{
protected Node header;
private int count;
public int Count
{
get
{
return count;
}
}
public CircularlyLinkedList()
{
header = new Node("header");
//初始化时头节点指向自身
header.Link = header;
count = 0;
}
public bool IsEmpty()
{
//如果头节点指向自身,则说明环形链表为空
return header.Link == header;
//或者看count是否等于0
//return count == 0;
}
public void Clear()
{
header.Link = header;
count = 0;
}
private Node FindPrevious(Object item)
{
Node current = header;
while (current.Link != header && current.Link.Element != item)
current = current.Link;
return current;
}
public void Remove(Object item)
{
Node p = FindPrevious(item);
//避免移除header节点
if (p.Link != header)
{
p.Link = p.Link.Link;
count--;
}
}
private Node Find(Object item)
{
Node current = header.Link;
while (current.Element != item)
{
current = current.Link;
//如果找遍整个环形链表找不到,则返回null
if (current.Link == header.Link)
return null;
}
return current;
}
public void Insert(Object newItem, Object after)
{
Node newNode = new Node(newItem);
Node current = Find(after);
if (current != null)
{
newNode.Link = current.Link;
current.Link = newNode;
count++;
}
//如果找不到相关节点,则将新节点插入头节点后面
else
InsertFirst(newItem);
}
public void InsertFirst(Object item)
{
Node current = new Node(item);
current.Link = header.Link;
header.Link = current;
count++;
}
public Node Move(int n)
{
Node current = header.Link;
for (int i = 0; i < n; i++)
current = current.Link;
//确保不是header节点
if (current == header)
current = current.Link;
return current;
}
public void PrintList()
{
Node current = header;
while (current.Link != header)
{
Console.WriteLine(current.Link.Element);
current = current.Link;
}
}
}
class Program
{
static void Main(string[] args)
{
//单向链表
LinkedList list = new LinkedList();
list.Insert("SevenXue", "header");
list.Insert("薛江白", "header");
list.Insert("徐丽华", "薛江白");
list.Insert("刘明", "SevenXue");
list.Remove("SevenXue");
Console.WriteLine("输出单向链表中的值:");
list.PrintList();
Console.WriteLine();
//双向链表
DoublyLinkedList dlist = new DoublyLinkedList();
dlist.Insert("SevenXue", "header");
dlist.Insert("1", "header");
dlist.Insert("2", "1");
dlist.Insert("3", "1");
dlist.Insert("9", "2");
dlist.Insert("7", "3");
dlist.Remove("SevenXue");
dlist.Insert("XUE", "9");
dlist.Remove("3");
Console.WriteLine("正向输出双向链表中的值:");
dlist.PrintList();
Console.WriteLine("反向输出双向链表中的值:");
dlist.PrintListReverse();
Console.WriteLine();
//环形链表
CircularlyLinkedList clist = new CircularlyLinkedList();
clist.Insert("2", "header");
clist.InsertFirst("7");
Console.WriteLine(clist.IsEmpty() ? "环形链表为空" : "环形链表不为空");
if (clist.Count > 0)
clist.PrintList();
//header节点是无法被移除的
clist.Remove("header");
clist.Remove("2");
clist.Remove("7");
Console.WriteLine("输出环形链表中的节点个数:");
Console.WriteLine(clist.Count);
clist.InsertFirst("99");
clist.InsertFirst("56");
clist.Insert("78", "56");
Console.WriteLine("输出环形链表中的值:");
if (clist.Count > 0)
clist.PrintList();
clist.Remove("99");
clist.Clear();
Console.WriteLine("输出环形链表中的值:");
if (clist.Count > 0)
clist.PrintList();
else
Console.WriteLine("环形链表为空");
clist.InsertFirst("79");
clist.InsertFirst("42");
clist.Insert("38", "79");
//3这个节点不存在,所以插入到header节点之后
clist.Insert("80", "3");
clist.Insert("66", "80");
clist.Insert("23", "66");
clist.InsertFirst("27");
clist.Remove("38");
Console.WriteLine("输出环形链表中的值:");
if (clist.Count > 0)
clist.PrintList();
Console.WriteLine("输出环形链表中的节点个数:");
Console.WriteLine(clist.Count);
Node node = clist.Move(1);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(2);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(5);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(6);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(7);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
node = clist.Move(8);
Console.Write("现在位于节点:");
Console.WriteLine(node.Element);
Console.ReadKey();
}
}
11.5 使用Iterator类
Linked类存在的一个问题就是不能在链表内同时引用两个位置。大家可以引用链表内的任何一个位置(当前节点、前一个节点等等),但是如果想制定两个甚至更多个位置,比如想从链表中移除一段范围内的节点,就需要一些其他方法了。这种方法就是Iterator类。
Iterator类由三个数据字段组成:一个存储链表的字段,一个存储当前节点的字段。构造器方法传递链表对象,而且这个方法会把当前字段设置为链表的头节点传递到方法中。
//迭代类ListIter
public class ListIter
{
private Node current;
private Node previous;
private LinkedList theList;
private int count;
public int Count
{
get
{
return count;
}
}
public ListIter(LinkedList list)
{
theList = list;
current = theList.GetFirst();
previous = null;
count = 0;
}
public void NextLink()
{
previous = current;
current = current.Link;
}
public Node GetCurrent()
{
return current;
}
public void InsertBefore(Object newElement)
{
Node newNode = new Node(newElement);
if (previous.Link != null)
{
newNode.Link = previous.Link;
previous.Link = newNode;
current = newNode;
count++;
}
else
throw new InsertBeforeHeaderException("Can't insert here.");
}
public void InsertAfter(Object newElement)
{
Node newNode = new Node(newElement);
newNode.Link = current.Link;
current.Link = newNode;
NextLink();
count++;
}
public void Remove()
{
if (count != 1)
{
if (current.Link != null)
{
previous.Link = current.Link;
current = previous.Link;
count--;
}
else
{
Console.WriteLine("It's the end of the LinkedList.");
Console.WriteLine("Reset to the first node.");
Reset();
}
}
else
Clear();
}
//清除链表
public void Clear()
{
previous = null;
current = theList.GetFirst();
current.Link = null;
count = 0;
}
//将current定位到链表head元素的后面第一个元素
public void Reset()
{
current = theList.GetFirst();
previous = current;
current = current.Link;
}
public bool AtEnd()
{
return current.Link == null;
}
}
11.5.1新的LinkedList类
现在用Iterator类做了大量的工作,可以把LinkedList类稍微消减一些,当然,这里还是需要一个头节点字段和一个实例化链表的构造器方法。
//链表类LinkedList
public class LinkedList
{
protected Node header;
public LinkedList()
{
header = new Node("header");
}
public Node GetFirst()
{
return header;
}
public bool IsEmpty()
{
return header.Link == null;
}
//输出单向链表中各个节点的值
public void PrintList()
{
Node current = header;
while (current.Link != null)
{
Console.WriteLine(current.Link.Element);
current = current.Link;
}
}
}
//自定义异常类InsertBeforeHeaderException
public class InsertBeforeHeaderException : Exception
{
public InsertBeforeHeaderException(string message) : base(message) { }
}
//节点类Node
public class Node
{
public Object Element;
public Node Link;
public Node()
{
Element = null;
Link = null;
}
public Node(Object theElement)
{
Element = theElement;
Link = null;
}
}
11.5.2 实例化Iterator类
11.6 泛型Linked类和泛型Node类