定义
通常,定义线性表为 n n n( n ⩾ 0 n \geqslant 0 n⩾0)个数据元素的一个有限的序列。记为
L = ( a 1 , a 2 , . . . , a i , a i + 1 , . . . , a n ) L = (a_1, a_2, ..., a_i, a_{i+1}, ..., a_n) L=(a1,a2,...,ai,ai+1,...,an)
其中 L L L是表名, a i a_i ai是表中的数据元素,是不可再分的原子数据,亦称为结点或表项。
线性表的第一个表项称为表头(head),最后一个表项称为表尾(tail)。
线性表的类定义
程序2.2 线性表的抽象基类 P44
enum bool{false, true} ;
template <class T>
class LinearList{
public:
LinearList();
~LinearList();
virtual int Size()const = 0; //求最大体积
virtual int Length();const = 0; //求表长度
virtual int Search(T& x)const = 0; //在表中搜索给定值x
...
}
线性表的存储方式有基于数组的存储表示、基于链表的存储表示、散列的存储表示等多种方式。
定义
把线性表中的所有表项按照其逻辑顺序依次存储到从计算机存储中指定存储位置开始的一块连续的存储空间中。
线性表的顺序存储结构是一种随机存取的存储结构。
特点
假设顺序表A的起始存储位置(即数组中第0个元素位置)是 L o c ( 1 ) Loc(1) Loc(1),第 i i i个表项的存储位置为 L o c ( i ) Loc(i) Loc(i),则有:
L o c ( i ) = L o c ( 1 ) + ( i − 1 ) × s i z e o f ( T ) Loc(i) = Loc(1) + (i - 1)\times sizeof(T) Loc(i)=Loc(1)+(i−1)×sizeof(T)
存储方式
描述顺序表的存储表示有两种方式:静态方式和动态方式
静态存储表示
#define maxSize 100
typedef int T;
typedef struct {
T data[maxSize];
int n;
}SeqList;
动态存储表示
typedef int T;
typedef struct {
T *data;
int maxSize, n;
}SeqList;
详细实现参照书本P46页
搜索算法
主要思想
从表的起始位置起,根据给定值x,逐个与表中的各表项的值进行比较。若给定值与某个表项的值相等,则算法报告搜索成功的信息并返回该表项的位置;若查遍表中的所有表项,没有任何一个表项满足要求,则算法报告搜索不成功的信息并返回0(必要的话可以改一下算法,返回新表项应插入的位置)。
搜索算法的时间代价用数据比较次数来衡量。
搜索的平均数据比较次数ACN(average comparing number)为
A C N = ∑ i = 1 n p i × c i ACN=\sum_{i=1}^n p_i \times c_i ACN=i=1∑npi×ci
若仅考虑相等概率的情况,搜索各表项的可能性相同,有 p 1 = p 2 = p 3 = p 4 = . . . = p n = 1 n p_1=p_2=p_3=p_4=...=p_n=\frac{1}{n} p1=p2=p3=p4=...=pn=n1,且搜索第1个表项的数据比较次数为1,第2个表项的数据比较次数为2,…,搜索第i个数据比较次数为i,则
A C N = 1 n ∑ i = 1 n i = 1 n × ( 1 + 2 + 3 + . . . + n ) = 1 + n 2 ACN=\frac{1}{n}\sum_{i=1}^n i=\frac{1}{n} \times(1+2+3+...+n)=\frac{1+n}{2} ACN=n1i=1∑ni=n1×(1+2+3+...+n)=21+n
在搜索不成功的情形下,需要把整个表全部检测一遍,数据比较次数达到n次,需要注意的是,在算法中担负检测的循环执行了 n + 1 n+1 n+1次,最后一次仅检测了指针i已经超出表的长度,没有执行数据比较。
插入算法
分析顺序表的插入和删除的时间代价主要看循环内的数据移动次数。
删除算法
复合类
class List;
class LinkNode{
friend class List;
private:
int data;
LinkNode *link;
};
class List{
public:
//链表公共类操作
private:
LinkNode *first;
};
嵌套类
基类和派生类
循环链表(circular list)是另一种形式的表示线性表的链表,它的结点结构与单链表相同,与单链表不同的是链表中表尾结点的指针域中不是NULL,而是存放了一个指向链表开始结点的指针。这样,只要知道表中任何一个结点的地址,就能遍历表中其他任一结点。
双向链表又称双链表。使用双链表的目的是为了解决在链表中访问直接前驱和直接后继的问题。
静态链表借助数组来描述线性表的链式存储结构,结点也有数据域和指针域,与前面所讲链表不同,这里的指针是结点的相对地址(数组下标),又称游标。和顺序表一样,静态链表也要预先分配一块连续的内存空间。
静态链表的指针域指向下一个结点的相对地址(数组下标)。
存取方式
顺序表可以顺序存取,也可以随机存取
逻辑结构和物理结构
采用顺序存储时,逻辑上相邻的元素,其对应的物理存储位置也相邻。
而采用链式存储时,逻辑上相邻的元素,其物理存储位置则不一定相邻,其对应的逻辑关系是通过指针链接来表示的。
请读者注意区别存取方式和存储方式。
查找、插入和删除操作
对于按值查找,顺序表无序时,两者的时间复杂度均为 O ( n ) O(n) O(n);顺序表有序时,可采用折半查找,此时的时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n)
对于按序号查找,顺序表支持随机访问,时间复杂度仅为 O ( 1 ) O(1) O(1),而链表的平均时间复杂度为 O ( n ) O(n) O(n)。
顺序表的插入、删除操作,平均需要移动半个表长的元素。
而链表的插入、删除操作,只需修改相关节点的指针域即可。由于链表的每个节点都带有指针域,因而在存储空间上要比顺序存储付出的代价大,而存储密度不够大。
空间分配
顺序存储在静态分配的情况下,一旦存储空间装满就不能扩充,若再加入新元素,则会出现内存溢出,因此需要预先分配足够大的存储空间。预先分配过大,可能会导致顺序表后部大量闲置;预先分配过小,又会造成溢出。动态存储分配虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且若内存内没有更大块的连续存储空间,则会导致分配失败。
链式存储的结点空间只在需要时申请分配,只要内存有空间就可以分配,操作灵活、高效。
在实际中应该怎样选取存储结构呢?