链表是一种有序的列表,它的内容通常存储与内容分散的位置上。
一般链表的串联方式有两种:
一种是通过数组有序串联链表的列表元素,通常用到两个数组,一个数组存放数据,一个数组存放链接的关系。这种链表的缺点在于,在插于或者删除元素的时候,要频繁的搬动元素,而且数组的大小是固定的,使用缺乏弹性。
另一种则是动态内存配置的链表,它由许多的结点(Node)链接而成,每一个结点包含数据部分以及指向下一个结点的指针(引用)。
我们常说的“链表”一般就是指第二种。
.Net Framework里并没有加入链表的数据结构对象。所以我就自己动手实践了一下。
偶尔看到韩睿的“.NET数据结构对象补遗之单链表”,我没有看他后面的代码部分(他用的是VB.NET),我也来写一个单链表的类。
上面讲了,链表由许多个结点链接而成,那首先,我们要考虑的就是创建一个链表结点的类。
首先,一个结点应该有两个域,一个域用来存放数据,一个域则存放指向下一个结点的引用。
2/// 结点对象的类。
3/// </summary>
4 public class ListNode
5 {
6 Field#region Field
7 private ListNode _nextNode;
8 private object _data;
9 #endregion
10
11 Property#region Property
12 /**//// <summary>
13 /// 指向下一个结点的引用。
14 /// </summary>
15 public ListNode NextNode
16 {
17 get{return this._nextNode;}
18 set{this._nextNode = value;}
19 }
20
21 /**//// <summary>
22 /// 结点的数据项
23 /// </summary>
24 public object Data
25 {
26 get{return this._data;}
27 set{this._data = value;}
28 }
29 #endregion
30
31 Constructor#region Constructor
32 /**//// <summary>
33 /// 初始化一个结点。
34 /// </summary>
35 /// <param name="data">结点数据项。</param>
36 /// <param name="nextNode">指向下一个结点的引用。</param>
37 public ListNode(object data, ListNode nextNode)
38 {
39 this._data = data;
40 this._nextNode = nextNode;
41 }
42
43 /**//// <summary>
44 /// 初始化一个作为尾结点的结点。
45 /// </summary>
46 /// <param name="data">结点数据项。</param>
47 public ListNode(object data) : this(data, null)
48 {
49 }
50 #endregion
51}
接下来,要考虑的就是创建一个链表类。先考虑一下链表类需要提供哪些操作。
首先,通过位置(索引)获取到对链表中的数据的访问,这对于列表数据结构而言是基本的实现内容,同样,针对链表元素的数据项的所在位置的索引,也是我们需要提供的实现方法之一。
昨天写了篇数组的东西,里面就说到数组的增、删、排效率很低,因为要频繁移动存储位置。而链表作为一种动态内存分配的列表数据结构,在添加、删除、排序方面没有了数组的先天不足,所以添加、删除、排序这几个方法肯定需要在我的这个链表类中得以实现。
从上面这些我首先想到的就是链表类可以从IList继承而来,IList是所有列表对象的抽象基类,也就是所有的列表对象都实现了System.Collections.IList这个接口。除了排序,上面提到的需要实现的方法在IList中都有定义。
同时在这里,我把刚才前面定义的那个结点类作为Nested Class内嵌在链表内中,提升这个结构的聚合性。
2
3 public class SingleLinkedList : IList
4 {
5 public class ListNode
6 {
7 // .
8 }
9}
在开始为链表类的方法作具体实现之前,先定义几个基本的成员;
2/// 链表的头结点。
3/// </summary>
4 protected ListNode _head;
5
6 /**/ /// <summary>
7/// 链表的尾结点。
8/// </summary>
9 protected ListNode _tail ;
10
11 /**/ /// <summary>
12/// 链表结点的数量。
13/// </summary>
14 protected int _nodeCount = 0 ;
以及一些基本的内部调用,比如验证结点的有效性,根据结点索引或数据内容查找结点等。
2/// 判断指定索引是否超出链表结点索引上下限。
3/// </summary>
4/// <param name="index">结点的索引值。</param>
5 protected virtual void Validate( int index)
6 {
7 if (index < 0 || index >= this._nodeCount)
8 {
9 throw new ArgumentOutOfRangeException("索引越界");
10 }
11}
12
13 /**/ /// <summary>
14/// 判断结点值是否有效。
15/// </summary>
16/// <param name="value">结点的值。</param>
17 protected virtual void Validate( object value)
18 {
19 if (value == null)
20 {
21 throw new ArgumentNullException();
22 }
23}
24
25 /**/ /// <summary>
26/// 判断结点的索引以及值是否有效。
27/// </summary>
28/// <param name="index">结点的索引值。</param>
29/// <param name="value">结点的值。</param>
30 protected virtual void Validate( int index, object value)
31 {
32 this.Validate(index);
33 this.Validate(value);
34}
35
36 /**/ /// <summary>
37/// 通过结点索引查找结点对象。
38/// </summary>
39/// <param name="index">结点的索引值。</param>
40/// <returns>如果找到指定索引的结点,则返回该结点对象,否则返回null。</returns>
41 public virtual ListNode FindByIndex( int index)
42 {
43 int tmpIndex = 0;
44
45 // 从首结点的下一个结点开始查找
46 ListNode current = this._head.NextNode;
47
48 ListNode returnValue = null;
49
50 do
51 {
52 if (tmpIndex == index)
53 {
54 returnValue = current;
55 }
56 else
57 {
58 tmpIndex ++;
59 current = current.NextNode;
60 }
61 }
62 while (current != null && returnValue == null);
63
64 return returnValue;
65
66}
67
68 /**/ /// <summary>
69/// 根据结点的值查找结点的索引。
70/// </summary>
71/// <param name="value">要查找的结点的值。</param>
72/// <returns>如果找到指定的结点,则返回该结点的索引,否则返回-1。</returns>
73 public virtual int FindByValue( object value)
74 {
75 int tmpIndex = 0;
76
77 // 从首结点的下一个结点开始查找
78 ListNode current = this._head.NextNode;
79
80 int returnValue = -1;
81
82 do
83 {
84 if (value.Equals(current.Data))
85 {
86 returnValue = tmpIndex;
87 }
88 else
89 {
90 tmpIndex ++;
91 current = current.NextNode;
92 }
93 }
94 while (current != null && returnValue == -1);
95
96 return returnValue;
97}
到这里,准备工作做差不多了,开始为这个单链表提供具体实现的代码编写。
基础实现部分
1. 添加结点元素(实现IList.Insert、IList.Add)
2 {
3 // 首先验证结点值的有效性。
4 this.Validate(index, value);
5
6
7 if (index == 0)
8 {
9 // 插入首结点前
10 this._head = new ListNode(value, this._head);
11 }
12 else
13 {
14 ListNode lastNode = this.FindByIndex(index - 1);
15
16 lastNode.NextNode = new ListNode(value, this.FindByIndex(index));
17 }
18
19 this._nodeCount += 1;
20
21 //this._version += 1;
22}
23
24 public int Add( object value)
25 {
26 // 首先验证结点值的有效性。
27 this.Validate(value);
28
29 this._tail.NextNode = new ListNode(value);
30 this._tail = this._tail.NextNode;
31
32 this._nodeCount += 1;
33
34 return this._nodeCount - 1;
35
36 //this._version += 1;
37}
2. 删除结点元素(自定义一个RemoveNode、实现IList.RemoveAt、IList.Remove)
2 {
3 if (index == 0)
4 {
5 // 如果是首结点
6 this._head = node.NextNode;
7 }
8 else
9 {
10 ListNode lastNode = this.FindByIndex(index - 1);
11
12 lastNode.NextNode = node.NextNode;
13
14 if (node == this._tail)
15 {
16 // 如果是尾结点
17 this._tail = lastNode;
18 }
19 }
20
21 this._nodeCount -= 1;
22
23 //this._version += 1;
24}
25
26 public void RemoveAt( int index)
27 {
28 // 首先验证结点值的有效性。
29 this.Validate(index);
30
31 this.RemoveNode(index, this.FindByIndex(index));
32}
33
34 public void Remove( object value)
35 {
36 // 首先验证结点值的有效性。
37 this.Validate(value);
38
39 this.RemoveAt(this.FindByValue(value));
40}
3. 按索引获取结点以及根据结点值返回结点索引(实现IList索引器、IList.IndexOf)
2 {
3 get
4 {
5 // 首先验证结点值的有效性。
6 this.Validate(index);
7
8 return this.FindByIndex(index).Data;
9 }
10 set
11 {
12 this.Insert(index, value);
13 }
14}
15
16 public int IndexOf( object value)
17 {
18 // 首先验证结点值的有效性。
19 this.Validate(value);
20 // 根据结点值查找结点。
21 return this.FindByValue(value);
22}
4. 其他IList成员的实现
2 {
3 // 首先验证结点值的有效性。
4 if (this.IndexOf(value) > -1)
5 {
6 return true;
7 }
8 else
9 {
10 return false;
11 }
12}
13
14 public void Clear()
15 {
16 this._head.NextNode = null;
17
18 this._tail = _head;
19
20 this._nodeCount = 0;
21
22 //this._version = 0;
23}
进阶功能实现
1. 复制(实现ICollection.CopyTo)
2 {
3 if (array == null)
4 {
5 throw new NullReferenceException();
6 }
7 else if (index < 0)
8 {
9 throw new ArgumentOutOfRangeException();
10 }
11 else if (array.Rank != 1 || array.Length - index != this._nodeCount || array.Length <= index)
12 {
13 throw new ArgumentException();
14 }
15
16 ListNode node = this._head;
17
18 while (node.NextNode != null)
19 {
20 array[index] = node.NextNode.Data;
21 node = node.NextNode;
22 index++;
23 }
24
25}
2. 对foreach的支持
VB中的For Each是个很强大的遍历方法,要实现这个功能,链表类必须实现IEnumerable接口,IList接口本身也是由该接口继承而来,所以这里不必要显示继承。C#语言从VB中吸取了这个非常实用的语句(写法为foreach)。对所有支持IEnumerable接口的类的实例,foreach语句使用统一的接口遍历其子项,使得以前冗长的for循环中繁琐的薄记工作完全由编译器自动完成。支持IEnumerable接口的类通常用一个内嵌类实现IEnumerator接口,并通过IEnumerable.GetEnumerator函数,允许类的使用者如foreach语句完成遍历工作。
为了对接口IEnumerable提供支持,我这里需要新建一个实现该接口的SingleLinkedListEnumerator类,定义如下:
2 {
3 protected int _index;
4 protected SingleLinkedList _list;
5
6 public SingleLinkedListEnumerator(SingleLinkedList list)
7 {
8 this._list = list;
9 this._index = -1;
10 }
11
12 IEnumerator 成员#region IEnumerator 成员
13
14 public void Reset()
15 {
16 this._list = -1;
17 }
18
19 public object Current
20 {
21 get
22 {
23 // 验证索引的有效性。
24 if (this._index < -1 || this._index > this._list.Count - 1)
25 {
26 throw new ArgumentException("参数越界");
27 }
28 else if (this._index == -1)
29 {
30 throw new InvalidOperationException("在没有调用MoveNext前访问Current是无效的");
31 }
32 else if (this._index >= this._list.Count)
33 {
34 throw new InvalidOperationException("已到集合尾部,访问无效");
35 }
36
37 return this._list[this._index];
38 }
39 }
40
41 public bool MoveNext()
42 {
43 // 验证索引的有效性。
44 this._index ++;
45 if (this._index > this._list.Count - 1)
46 {
47 return false;
48 }
49 else
50 {
51 return true;
52 }
53 }
54
55 #endregion
56}
实现IEnumerable.GetEnumerator
2 {
3 return new SingleLinkedListEnumerator(this);
4}
未完待续。。。