( 1)除第一个位置的数据 元素外,其它数据元素位置的前面都只有一个数据元素;( 2)除最后一个位置的 数据元素外,其它数据元素位置的后面都只有一个元素。也就是说,数据元素是 一个接一个的排列。因此,可以把线性表想象为一种数据元素序列的数据结构。 2.1.1 线性表的定义 线性表(List) 线性表的接口如下所示。 public interface IListDS<T> { int GetLength(); //求长度 void Clear(); //清空操作 bool IsEmpty(); //判断线性表是否为空 void Append(T item); //附加操作 void Insert(T item, int i); //插入操作 T Delete(int i); //删除操作 T GetElem(int i); //取表元 int Locate(T value); //按值查找 } 为了和.NET 框架中的接口 IList 相区分,在 IList 后面加了“DS”,“DS” 表示数据结构。 2.2 顺序表 2.2.1 顺序表的定义 在计算机内,保存线性表最简单、最自然的方式,就是把表中的元素一个接 一个地放进顺序的存储单元,这就是线性表的顺序存储(Sequence Storage)。线性 表的顺序存储是指在内存中用一块地址连续的空间依次存放线性表的数据元素, 用这种方式存储的线性表叫顺序表(Sequence List),如图 2.1 所示。顺序表的特点 是表中相邻的数据元素在内存中存储位置也相邻。 0 1 … i-1 i … n-1 … maxsize-1 a1 a2 … ai-1 ai ai+1 … an … C#语言中的数组在内存中占用的存储空间就是一组连续的存储区域,因此, 数组具有随机存取的特点。所以,数组天生具有表示顺序表的数据存储区域的特性。 由于任何线性表都可以进行倒置操作,所以可以把该操作作为 IListDS 接口 的一个成员方法进行声明,然后在各线性表类中实现。IListDS 接口中倒置方法 Reverse 的声明如下: public interface IListDS<T> { …… void Reverse(); } 【例 2-2】有数据类型为整型的顺序表 La 和 Lb,其数据元素均按从小到大的升 序排列,编写一个算法将它们合并成一个表 Lc,要求 Lc 中数据元素也按升序排 数据结构( C#语言版)2.2 顺序表 37 列。 算法思路:依次扫描 La 和 Lb 的数据元素,比较 La 和 Lb 当前数据元素的 值,将较小值的数据元素赋给 Lc,如此直到一个顺序表被扫描完,然后将未完 的那个顺序表中余下的数据元素赋给 Lc 即可。 Lc 的容量要能够容纳 La 和 Lb 两个表相加的长度。 按升序合并两个表的算法 C#实现如下: public SeqList<int> Merge(Seqlist<int> La, SeqList<int> Lb) { SeqList<int> Lc = new SeqList<int>(La.Maxsize + Lb.Maxsize); int i = 0; int j = 0; int k = 0; //两个表中都有数据元素 while ((i <= (La.GetLength()-1))&& (j <= (Lb.GetLength()-1))) { if (La[i] <Lb[j]) { Lc.Append(La[i++]); } else { Lc.Append(Lb[j++]); } } //a表中还有数据元素 while (i <= (La.GetLength() - 1)) { Lc.Append(La[i++]); } //b表中还有数据元素 while (j <= (Lb.GetLength() - 1)) { Lc.Append(Lb[j++]); } return Lc; } 算法的时间复杂度是 O(m+n), m 是 La 的表长, n 是 Lb 的表长。 2.3 单链表 顺序表是用地址连续的存储单元顺序存储线性表中的各个数据元素,逻辑上 相邻的数据元素在物理位置上也相邻。因此,在顺序表中查找任何一个位置上的 数据元素非常方便,这是顺序存储的优点。但是,在对顺序表进行插入和删除时, 需要通过移动数据元素来实现,影响了运行效率。本节介绍线性表的另外一种存 储结构——链式存储(Linked Storage),这样的线性表叫链表(Linked List)。链表不 要求逻辑上相邻的数据元素在物理存储位置上也相邻,因此,在对链表进行插入 和删除时不需要移动数据元素,但同时也失去了顺序表可随机存储的优点。 2.4 其他链表 2.4.1 双向链表 前面介绍的单链表允许从一个结点直接访问它的后继结点,所以, 找直接后 继结点的时间复杂度是 O(1)。但是,要找某个结点的直接前驱结点,只能从表的 头引用开始遍历各结点。如果某个结点的 Next 等于该结点,那么,这个结点就 是该结点的直接前驱结点。也就是说,找直接前驱结点的时间复杂度是 O(n), n 是单链表的长度。当然,我们也可以在结点的引用域中保存直接前驱结点的地址 而不是直接后继结点的地址。这样,找直接前驱结点的时间复杂度只有 O(1), 但找直接后继结点的时间复杂度是 O(n)。如果希望找直接前驱结点和直接后继 结点的时间复杂度都是 O(1),那么,需要在结点中设两个引用域,一个保存直 接前驱结点的地址,叫 prev,一个直接后继结点的地址,叫 next,这样的链表就 是双向链表(Doubly Linked List)。双向链表的结点结构示意图如图 2.15 所示。 prev data next 图 2.15 双向链表的结点结构示意图
//a tong 的单链表问题ListNode* root = &ListNode(0); ListNode* curNode = root; for (int i = 1; i < 100; i++) { curNode->next = New ListNode(i); curNode = curNode->next; } struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} };
时间: 2024-12-17 17:57:35