单链表的定义以及使用
链表是一种有序的列表,它的内容通常存储与内容分散的位置上。
一般链表的串联方式有两种:
一种是通过数组有序串联链表的列表元素,通常用到两个数组,一个数组存放数据,一个数组存放链接的关系。这种链表的缺点在于,在插于或者删除元素的时候,要频繁的搬动元素,而且数组的大小是固定的,使用缺乏弹性。
另一种则是动态内存配置的链表,它由许多的结点(Node)链接而成,每一个结点包含数据部分以及指向下一个结点的指针(引用)。
我们常说的“链表”一般就是指第二种。
.Net Framework里并没有加入链表的数据结构对象。所以我就自己动手实践了一下。
偶尔看到韩睿的“.NET数据结构对象补遗之单链表”,我没有看他后面的代码部分(他用的是VB.NET),我也来写一个单链表的类。
上面讲了,链表由许多个结点链接而成,那首先,我们要考虑的就是创建一个链表结点的类。
首先,一个结点应该有两个域,一个域用来存放数据,一个域则存放指向下一个结点的引用。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
接下来,要考虑的就是创建一个链表类。先考虑一下链表类需要提供哪些操作。
首先,通过位置(索引)获取到对链表中的数据的访问,这对于列表数据结构而言是基本的实现内容,同样,针对链表元素的数据项的所在位置的索引,也是我们需要提供的实现方法之一。
昨天写了篇数组的东西,里面就说到数组的增、删、排效率很低,因为要频繁移动存储位置。而链表作为一种动态内存分配的列表数据结构,在添加、删除、排序方面没有了数组的先天不足,所以添加、删除、排序这几个方法肯定需要在我的这个链表类中得以实现。
从上面这些我首先想到的就是链表类可以从IList继承而来,IList是所有列表对象的抽象基类,也就是所有的列表对象都实现了System.Collections.IList这个接口。除了排序,上面提到的需要实现的方法在IList中都有定义。
同时在这里,我把刚才前面定义的那个结点类作为Nested Class内嵌在链表内中,提升这个结构的聚合性。
2
3
4
5
6
7
8
9
在开始为链表类的方法作具体实现之前,先定义几个基本的成员;
2
3
4
5
6
7
8
9
10
11
12
13
14
以及一些基本的内部调用,比如验证结点的有效性,根据结点索引或数据内容查找结点等。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
到这里,准备工作做差不多了,开始为这个单链表提供具体实现的代码编写。
基础实现部分
1. 添加结点元素(实现IList.Insert、IList.Add)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2. 删除结点元素(自定义一个RemoveNode、实现IList.RemoveAt、IList.Remove)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
3. 按索引获取结点以及根据结点值返回结点索引(实现IList索引器、IList.IndexOf)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
4. 其他IList成员的实现
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
进阶功能实现
1. 复制(实现ICollection.CopyTo)
public void CopyTo(Array array, int index)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类,定义如下:
public class SingleLinkedListEnumerator : IEnumerator2
{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
#region IEnumerator 成员13

14
public void Reset()15
{16
this._list = -1;17
}18

19
public object Current20
{21
get22
{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
else50
{51
return true;52
}53
}54

55
#endregion56
}实现IEnumerable.GetEnumerator
public IEnumerator GetEnumerator()2
{3
return new SingleLinkedListEnumerator(this);4
}未完待续。。。



浙公网安备 33010602011771号