Henry手记的内容介绍

Henry手记的内容介绍

Henry手记—.NET数据结构对象补遗之单链表(一)

韩睿 ( 06/15/2003)

.NET Framework提供了众多常用的数据结构对象,放在System.Collections命名空间中。现有ArraylistQueueStackSortListHashTable等。但是奇怪的是,微软没有在Framework中加入链表、二叉搜索树等相当重要的数据结构对象。从本篇开始,我们将陆续将它们补充进我们自己的.NET类库,让它们更有效地成为工作的利器。同时,大家也会通过这些基本数据结构的实现,达到对Framework类与接口编程的深入理解,所以,不要错过。同时,我们也一起复习一下数据结构的内容吧。

Arraylist是近来被大家不断提及与关注的列表类,它是由数组构成的。虽然microsoft对数组作了大量的改进,提高了增减元素操作的效率、实现了动态扩充的性能。但是数组始终是数组:在对元素的排序、删除、添加操作时,效率仍很低;更重要的是,数组的大小是固定的,即使是动态数组,只不过是加入了重新分配空间的能力;同时,为了避免频繁地重新分配,不得不将数组初始化大小设得较大,这样就会大量地浪费内存。而事实上,FrameworkQueueStackHashtable等数据集合的实现,也部分或全部地使用了链表的方法,所以,自己动手实践一次,是分外有益的,Let’s go!

1. 单链表(singly linked list)

单链表是最简单的链表表示,因此我们先来对它进行分析与实现。用它来表示线性表时,每一个数据元素占用一个结点(node)。一个结点一般由两个域组成,一个域存放数据元素data;另一个域存放一个指向链表中下一个结点的指针link,它指出下一个结点的开始存储地址。

单链表的类实现,在每一本《数据结构》课本上都有详细的介绍:需要定义结点类和链表类,在链表类中实现对结点的操作。

在本文所要分析的链表类,我们采用嵌套定义的方式,将结点类定义在链表类的内部。所能进行的操作包括:添加(向链表尾插入结点)、插入(向链表中指定的索引位置插入结点)、按索引位置删除结点、按数据元素值删除结点、搜索数据元素值的索引、用For Each遍历链表中的值等。在我们的程序中,还会向大家展示如何在类中设计抛出异常、嵌套枚举器类等不常被提及的处理。

为了避免发生在链表头部或尾部进行插入或删除操作这样特殊的情况,给链表加一个数据为空的头节点。并加入尾结点指针,以方便添加功能的实现。

2. ArrayList类分析

我们的单链表实现方法是模仿Framework现有的数据集合类的做法,而不是仅仅按照《数据结构》课本的C++定义方法来实现。因此需要先来学习一下Arraylist类的定义:

Public Class ArrayList

Implements IList, ICollection, IEnumerable, ICloneable

也就是说ArrayList是实现了四个接口,我们当然要分别看一下这些接口的作用才好进行仿制工作。

1)IEnumerableIEnumerable是公开枚举数,该枚举数支持在集合上进行简单迭代。必须对它进行实现才能支持 Microsoft Visual Basic ForEach 语义。

2) ICollection:派生自IEnumerable接口,定义所有集合的大孝枚举数和同步方法。ICollection 接口是 System.Collections 命名空间中类的基接口。

3) IListIlist派生自ICollectionIDictionary IList 是基于 ICollection 接口的更专用的接口。IDictionary 实现是键/值对的集合,如 Hashtable 类。IList 实现是可被排序且可按照索引访问其成员的值的集合,如 ArrayList 类。

另:某些集合(如 Queue 类和 Stack 类)限制对其成员的访问,它们直接实现 ICollection 接口。或者当 IDictionary 接口和 IList 接口都不能满足所需集合的要求时,则从 ICollection 接口派生新集合类以提高灵活性。

以上三个接口的继承关系为:IEnumerable-> ICollection-> IList。也就是说ArrayList其实是通过对IList的实现,同时实现了ICollectionIEnumerable。因此我们只需要实现IList接口即可。

4)ICloneable:这个接口的作用就是实现克隆,即用与现有实例相同的值创建类的新实例。为纯粹我们的目标,这个接口不在本文中进行实现。留待后续文章中进行分析。

通过以上的分析,我们可以这样来定义我们即将要实现的单链表类了:

Public Class SLList

Implements IList

要实现接口,即要实现接口中定义的方法或属性。IList接口的实现需要声明的方法与属性如图1所示,一共有15个。分属于IEnumerableICollectionIList

Henry手记的内容介绍

1 单链表类SLList需要实现的来自于接口的方法与属性

3. 实际演练

由于单链表类要能够在用户扩展时当成基类使用,所以它所有的字段、方法和属性是Public或者是Protected的,并且在必要的地方用Overridable进行标记。

接下来,我们依序从类定义到功能实现一一来详细说明:

3.1 类定义

我们用嵌套类定义的方式来定义结点类与链表类:

Imports System.Collections

Public Class SLList

Implements IList

Protected Class ListNode

Public NextNode As ListNode '指向下一个结点的引用

Public Data As Object '用object类型来声明数据,可以达到template的效果

Public Sub New(ByVal data As Object, _

ByVal nextNode As ListNode)

'初始化结点

Me.Data = data

Me.NextNode = nextNode

End Sub

Public Sub New(ByVal data As Object)

'作为尾结点的结点

Me.New(data, Nothing)

End Sub

End Class

……

3.2 类内公用变量初始化

Protected head As ListNode = New ListNode(Nothing) '初始化头结点

Protected tail As ListNode = head '指向链表尾的引用

Protected nodeCount As Integer = 0 '保存节点数目,通过Count方法返回

Protected version As Integer = 0 '记录链表变化的版本号

这里使用了version这个变量,它的作用在后面我们会详细讨论的。

3.3验证索引与数据元素

在类里定义了验证函数,验证失败,会抛出异常,这样用户在调用时可以用TryEnd Try来处理:

Protected Overridable Overloads Sub Validate(ByVal index As Integer)

'验证index是否在可用范围内

If index < 0 Or index >= nodeCount Then

Throw New ArgumentOutOfRangeException("索引越界.")

End If

End Sub

Protected Overridable Overloads Sub Validate(ByVal value As Object)

'验证输入的数据是否存在

If value Is Nothing Then

Throw New ArgumentNullException()

End If

End Sub

Protected Overridable Overloads Sub Validate(ByVal index As Integer, ByVal value As Object)

'同时验证索引与数据元素

Validate(index)

Validate(value)

End Sub

----

声明:本文版权与解释权归韩睿所有,如需转载,请保留完整的内容及此声明。

QQ: 18349592

E-Mail: henry7685@hotmail.com

请访问本人专栏: