一、线性表之顺序表
1.1顺序表的定义
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。又称顺序表。
1.2顺序表的特点
1)逻辑上相邻的元素 ai, ai+1, 其存储位置也是相邻的;
2)对数据元素ai的存取为随机存取或按地址存取。
3)存储密度高。 存储密度D=(数据结构中元素所占存储空间)/( 整个数据结构所占空间) 。
2.1顺序表的存储类型
2.1.1 静态分配
#define MAXSIZE 100
typedef int ElemType;
//静态分配的顺序表
typedef struct {
ElemType data[MAXSIZE]; //存放元素
int len; //顺序表的长度
}SqList; //声明顺序表的类型
2.1.2 动态分配
#define LIST_INIT_SIZE 100 //存储空间的初始化分配量
#define LISTINCREMENT 10 //分配增量
typedef int ElemType;
//动态分配的顺序表
typedef struct
{
ElemType* elem; //存储区域的基址
int len; //当前表的长度
int size; //当前以分配的存储容量
}SqList; //顺序表类型
3.1 对顺序表的基本操作
3.1.1初始化顺序表
void InitList(SqList& L);
{
L.elem = (ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if (!L.elem)
exit(-1); //内存分配失败
L.len = 0;
L.size = LIST_INIT_SIZE;
return 1;
}
3.1.2 查找顺序表中第i个位置的元素的值
ElemType GetElem(SqList L, int i)
{
ElemType e;
e = L.elem[i];
return e;
}
3.1.3查找顺序表中第一个值等于e的元素,并返回其下标
int LocateElem(SqList L, ElemType e)
{
if (L.len == 0)
return 0;
int i;
for (i = 0; i < L.len; i++)
{
if (L.elem[i] == e)
return i + 1;
}
}
3.1.4在顺序表L的第i个位置(1<=i<=L.len+1)前插入新元素e
//在顺序表L的第i个位置(1<=i<=L.len+1)前插入新元素e
Status SqListInsert(SqList& L, int i, ElemType e)
{
if (i<1 || i>L.len)
return -1;
SqListCapacity(L); //判断内存是否足够,如果不足就进行扩容
int j;
for (j = L.len; j > i; j--)
{
L.elem[j] = L.elem[j - 1]; //从i元素开始整体右移一个元素
}
L.elem[i - 1] = e;
L.len++; //长度加一
return 1;
}
3.1.5删除第i个位置的元素,并返回第i个位置的值
//删除顺序表L中的第i个位置(1<=i<=L.len)的元素e,并返回e的值
Status DelSqList(SqList& L, int i, ElemType& e)
{
if (i<1 || i>L.len) //判断是否越界
return -1;
e = L.elem[i - 1]; //存放第i个元素的值
int j;
for (j = i; j < L.len; j++)
{
L.elem[j - 1] = L.elem[j]; //第i个元素之后的元素整体前移一个元素
}
L.len--; //长度减一
return 1;
}
4.1顺序表的图形解释
二、对顺序表习题的练习
顺序表A和顺序表B这两个线性表元素个数分别为m、n,若表中数据都是递增有序的,且这m+n个元素中没有重复,对下面问题进行求解。
1、设计一个算法将这两个顺序表归并到另顺序表C中,且C中元素仍是递增有序的
解题思路:
采用有序表的二路归并思路,用 i 和 j 分别遍历A和B,比较它们的当前元素,将较小的复制到顺序表C中,当A或B中有一个遍历结束后,再将另一个顺序表余下的元素全部复制到顺序表C中,算法如下:
void test1(SqList A, SqList B, SqList& C)
{
int i = 0, j = 0, k = 0;
while (i < A.len && j < B.len) //有序顺序表A和B都没有遍历完时
{
if (A.elem[i] < B.elem[j]) //归并较小的元素A.elem[i]
{
C.elem[k] = A.elem[i];
i++;
k++;
}
else //归并较小的元素B.elem[j]
{
C.elem[k] = B.elem[j];
j++;
k++;
}
}
while (i < A.len) //有序顺序表A没有遍历完时
{
C.elem[k] = A.elem[i];
i++;
k++;
}
while (j < B.len) //有序顺序表B没有遍历完时
{
C.elem[k] = B.elem[j];
j++;
k++;
}
C.len = k; //设置C的长度
}
本题时间复杂度为O(m+n),空间复杂度O(1)
2、如果顺序表B的大小为m+n个单元,是否可以不利用顺序表C而将结果存放与顺序表B中实现合并?
解题思路:
可以采用有序表的二路归并思路,用 i 从后向前遍历顺序表A,用 j 从后向前遍历顺序表B,将较大元素插入B的尾部,算法如下:
void test2(SqList A, SqList& B)
{
int i = A.len - 1, j = B.len - 1, k = A.len + B.len - 1;
while (i >= 0 && j >= 0)
{
if (A.elem[i] > B.elem[j]) //将较大的元素A.elem[i]放到B的尾部
{
B.elem[k] = A.elem[i];
i--;
k--;
}
else //将较大的元素B.elem[i]放到B的尾部
{
B.elem[k] = B.elem[j];
j--;
k--;
}
}
while (i >= 0) //A没有遍历结束
{
B.elem[k] = A.elem[i];
i--; k--;
}
while (j>= 0) //B没有遍历结束
{
B.elem[k] = B.elem[j];
j--; k--;
}
B.len += A.len; //修改B的长度
}
本题时间复杂度为O(m+n),空间复杂度O(1)
3、设顺序表A中前m个元素递增有序,后n个元素也递增有序,设计一个算法,使得整个顺序表依然有序,要求空间复杂度为O(1)
解题思路:
本题因为要求空间复杂度为O(1),因此算法中不能新建顺序表,算法思路是将顺序表A的后半部分插入前半部分中,使得整个表有序,其长度不变,算法如下:
void test3(SqList& A, int m, int n)
{
int i = 0, j = m, k; //j遍历后半部分的有序表,同时记录前半部分有序表的长度
ElemType temp;
while (j < A.len && i < j)
{
if (A.elem[j] > A.elem[j - 1]) //整个表一递增有序,推出循环
break;
else if(A.elem[j]<A.elem[i]) //将A.elem[j]插入前半部分中
{
temp = A.elem[j];
for (k = j - 1; k >= i; k--) //将A.elem[i]及之后的元素后移
{
A.elem[k + 1] = A.elem[k];
}
A.elem[i] = temp; //将A.elem[j]插入A.elem[i]处
i++;
j++;
}
else
{
i++;
}
}
}
本题时间复杂度为O(m*n),空间复杂度O(1)
4、对于第3题算法,在对空间复杂度没有限制的条件下,能否设计具有同样功能的算法时间复杂度为O(m+n)呢?
解题思路:
在算法中建立一个临时顺序表B,先将A的后半部分元素复制到B中,这样A看成只有m个元素的有序表,采用第二题的算法思路将A,B两个顺序表归并为顺序表A ,算法如下:
void test4(SqList& A, int m, int n)
{
SqList B; //建立临时顺序表B
int i, j, k;
for (i = m, j = 0; j < m + n; i++, j++) //将A的后半部分复制到B中
{
B.elem[j] = A.elem[i];
}
i = m - 1;
j = n - 1;
k = m + n - 1;
while (i >= 0 && j >= 0) //将A,B归并到A中
{
if (A.elem[i] > B.elem[j]) //归并较大元素 B.elem[j];
{
A.elem[k] = A.elem[i];
i--;
k--;
}
else //归并较大元素 B.elem[j];
{
A.elem[k] = B.elem[j];
j--;
k--;
}
}
while (i >= 0) //A没有遍历时
{
A.elem[k] = A.elem[i];
i--;
k--;
}
while (j >= 0) //B没有遍历时
{
A.elem[k] = B.elem[j];
j--;
k--;
}
}
本题时间复杂度为O(m+n),空间复杂度O(n)(算法中临时顺序表B的空间大小为n)
三、总结
优点:
1) 可以方便地随机存取表中任一结点。
2)无须增加额外的存储空间表示结点间的逻辑关系。
缺点
1)插入和删除运算不方便,通常须移动大量结点,效率较低。
2)难以进行连续的存储空间的预分配,尤其是当表变化较大时。
3)当你使用静态分配空间时,不知道提前开辟多大的空间合适,#define SIZE X 过大过小都不合适。
.elem[k] = B.elem[j];
j–;
k–;
}
}
本题时间复杂度为O(m+n),空间复杂度O(n)(算法中临时顺序表B的空间大小为n)
# 三、总结
### 优点:
1) 可以方便地随机存取表中任一结点。
2)无须增加额外的存储空间表示结点间的逻辑关系。
### 缺点
1)插入和删除运算不方便,通常须移动大量结点,效率较低。
2)难以进行连续的存储空间的预分配,尤其是当表变化较大时。
3)当你使用静态分配空间时,不知道提前开辟多大的空间合适,#define SIZE X 过大过小都不合适。