2.2 线性表的顺序表示
2.2.1 顺序表的定义
-
顺序表:用顺序存储的方式实现线性表顺序存储,把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
-
顺序表的特点:
a. 表中元素的逻辑顺序与物理顺序相同
b. 表中元素可以随机存取,通过首地址和元素序号可在时间O(1)内找到指定的元素设线性表L的存储的起始位置为LOC(A),即第一个元素的访问位置为LOC(A) sizeof(Elemtype)是每个数据元素所占用存储空间大小,则第二个元素的位置为LOC(A)+sizeof(ElemType) 第i个元素的位置为LOC(A)+(i-1)*sizeof(ElemType)
c. 存储密度高,每个结点只存储数据元素
d. 拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高) -
优点:随机存取,存储密度高
-
缺点:要求大片连续空间,不易改变容量
-
顺序表存储类型描述:
a. 静态分配:数组大小和空间事先固定,一旦空间占满,再加入新的数据就会产生溢出,从而使程序崩溃```c #define MaxSize 100 //定义最大长度 typedef struct{ ElemType data[MaxSize]; // 存放数据元素的数组 int length; //当前元素个数 }SqList; // 基本操作--初始化一个顺序表 void InitList(&L){ for(int i=0;i<MaxSIze;i++){ // 将所有数据元素初始化为0 L.data[i]=0; } // 表示L还是一个空表 L.length=0; } int main(void){ SqList L; InitList(L); // 遍历数组时,限制条件应该使用length而不是MaxSize // MaxSize是最大存储空间,length才是当前顺序表的长度,即已存储的元素个数 for(int i=0;i<L.length;i++){ //...后续操作 } return 0; } ```
b. 动态分配:存储数组的空间是在程序执行过程中通过动态分配语句分配到,一旦数据空间占满,就另外开辟一块更大的空间来替换原来的存储空间,达到扩充存储数组空间的目的
#inlcude<stdlib.h> //malloc,free需要 #define InitSize 100 typedef struct{ ElemType *data; int MaxSize,length; //数组最大容量和当前元素个数 }SeqList; // C初始动态分配语句 L.data=(ElemType* )malloc(sizeof(ElemType)*InitSize); // C++ 分配空间 // L.data=new ElemType[InitSIze];
// 基本操作--增加动态数组长度 void IncreaseSize(SeqList &L, int len){ int *p=L.data; L.data=(ElemType*) malloc(sizeof(ElemType)*(L.MaxSize+len)); for(int i=0;i<L.length;i++){ L.data[i]=p[i]; } L.MaxSize=L.MaxSize+len; free(p); //记得释放1掉原来的空间 } int main(void){ SeqList L; InitList(L); IncreaseSize(L, 5); return 0; }
动态分配依然是顺序存储结构,只是分配空间可以在运行时动态决定
顺序输出所有元素时,顺序表和线性表时间复杂度相同
2.2.2 顺序表上基本操作的实现
-
插入操作
/* * 1. 在顺序表L的第i个位置插入元素e * 2. 判断i是否合法,i>=1 && i<=length+1, length<=MaxSize * 3. 将第i个位置及其后面的所有元素依次向后移动一个位置 * 4. 将e插入到第i个位置,顺序表长度+1 * 5. 插入成功,返回true */ bool ListInsert(SqList &L, int i, ElemType e){ if(i<1 || i>L.length+1 || L.length>=<Maxsize){ return false; } for(int i=L.length; i>=i;i--){ L.data[i]=L.data[i-1]; } L.data[i-1]=e; L.length++; return true; }
a. 最好情况:在表尾插入(i=n+1),元素移动语句不执行,时间复杂度为O(1)
b. 最坏情况:在表头插入(i=1),元素后移语句执行n次,时间复杂度为O(n)
c. 平均情况:在每个位置插入元素的概率相等,即 p i = 1 n + 1 p_i=\frac{1}{n+1} pi=n+11,所需移动的平均次数为∑ i = 1 n + 1 p i ∗ ( n − i + 1 ) = ∑ i = 1 n + 1 1 n + 1 ∗ ( n − i + 1 ) = 1 n + 1 ∑ i = 1 n + 1 ( n − i + 1 ) = 1 n + 1 ∗ n ∗ ( n + 1 ) 2 = n 2 \sum_{i=1}^{n+1}p_i*(n-i+1)= \sum_{i=1}^{n+1} \frac{1}{n+1}*(n-i+1)\\=\frac{1}{n+1} \sum_{i=1}^{n+1}(n-i+1)\\=\frac{1}{n+1}*\frac{n*(n+1)}{2}=\frac{n}{2} i=1∑n+1pi∗(n−i+1)=i=1∑n+1n+11∗(n−i+1)=n+11i=1∑n+1(n−i+1)=n+11∗2n∗(n+1)=2n
-
删除操作
/* * 1. 删除顺序表L中的第i个元素,并用引用变量e返回删除的值 * 2. 判断i是否合法: 1<=i<=L.length * 3. 将被删除元素赋给引用变量e * 4. 将第i个元素后面的元素依次向前移动一个位置 * 5. 删除成功返回true */ bool ListDelete(SqList &L, int i, ElemType &e){ if(i<1 && i>L.length){ return false; } e=L.data[i-1]; for(int j=i; j<L.length;j++){ L.data[j-1]=L.data[j]; } L.length-; return true; }
a. 最好情况:删除表尾1元素,元素移动语句执行次数为0,O(1)
b. 最坏情况:删除表头元素,元素移动语句执行n-1次,O(n)
c. 平均情况:每个位置上的元素被删除的元素相等: p i = 1 n p_i=\frac{1}{n} pi=n1,平均时间复杂度为:∑ i = 1 n p i ∗ ( n − 1 ) = ∑ i = 1 n 1 n ∗ ( n − i ) = 1 n ∑ i = 1 n n − i = 1 n ∗ n ∗ ( n − 1 ) 2 = n − 1 2 \sum_{i=1}^{n}p_i*(n-1)=\sum_{i=1}^{n}\frac{1}{n}*(n-i)\\=\frac{1}{n}\sum_{i=1}^{n}n-i\\=\frac{1}{n}*\frac{n*(n-1)}{2}\\=\frac{n-1}{2} i=1∑npi∗(n−1)=i=1∑nn1∗(n−i)=n1i=1∑nn−i=n1∗2n∗(n−1)=2n−1
-
查找操作
/* * 1. 获取顺序表L的元素值等于给定值的元素的位置 * 2. 判断e是否合法 * 3. 匹配 * 4. 返回找到的值的下标+1 * 5. 失败则返回0 */ int LocateElem(SqList L, ElemType e){ for(int i=0;i<L.length;i++){ f(L.data[i]==e){ retrn i+1; } } return 0; }
a. 最好情况:查找元素就是表头元素,比较函数执行1次,O(1)
b. 最差情况:查找元素就是表尾元素,比较函数执行次数为n,O(n)
c. 平均情况:被查找的元素在每个位置上的概率相等: p i = 1 n p_i=\frac{1}{n} pi=n1,平均时间复杂度为:∑ i = 1 n p i ∗ i = ∑ i = 1 n 1 n ∗ i = 1 n ∑ i = 1 n i = 1 n ∗ n ∗ ( n + 1 ) 2 = n + 1 2 \sum_{i=1}^{n}p_i*i=\sum_{i=1}^{n}\frac{1}{n}*i=\frac{1}{n}\sum_{i=1}^{n}i=\frac{1}{n}*\frac{n*(n+1)}{2}=\frac{n+1}{2} i=1∑npi∗i=i=1∑nn1∗i=n1i=1∑ni=n1∗2n∗(n+1)=2n+1