第三章 线性表之顺序表
- 第三章 线性表之顺序表
- 一、什么是线性表?
- 1> 概念
- 2> 线性表的基本操作
- 二、线性表的顺序存储
-
- 存储结构
- 顺序存储图示
-
- 核心操作
- 1> 初始化
- 顺序表初始化图示
- C 语言实现
- 2> 清空
- 顺序表清空图示
- C 语言实现
- 3> 销毁
- 顺序表销毁图示
- C 语言实现
- 4> 插入
- 顺序表插入图示
- C 语言实现
- 5> 删除
- 顺序表删除图示
- C 语言实现
- 6> 随机访问
- C 语言实现
- 总结
-
- 一、什么是线性表?
一、什么是线性表?
1> 概念
线性表: n 个元素的有序序列。n 可为 0,表示空表;n > 0 时,除了头元素,每个元素都有后继元素,除了尾元素,每个元素都有前驱元素。换句俗话,就是说线性表中有 n 个元素,它们按顺序串成一串儿。
线性表的特点:
- 同一性:线性表的元素都属于同一类型;
- 有穷性:线性表由有限个数据元素组成,表长度就是表中数据元素的个数;
- 有序性:线性表中元素有序。
2> 线性表的基本操作
线性表的抽象数据型规定了线性表的基本操作,它们相当于线性表的接口。基本操作只考虑功能,不考虑实现;不同的存储方式下,实现相同的功能算法也不同;后续我们将采用不同的存储方式实现这些基本操作。
- InitList(&L)
- 结果:构造一个空的线性表 L
- DestroyList(&L)
- 前提:线性表 L 已存在
- 结果:将 L 销毁
- ClearList(&L)
- 前提:线性表 L 已存在
- 结果:将 L 置为空表
- EmptyList(&L)
- 前提:线性表 L 已存在
- 结果:如果 L 为空表,则返回 TRUE,否则,返回 FALSE
- ListLength(L)
- 前提:线性表 L 已存在
- 结果:返回表中的元素个数
- GetElem(L, i, &e)
- 前提:表 L 存在,且 0 <= i <= ListLength(L) - 1
- 结果:用 e 返回 L 中第 i 个数据元素
- LocateElem(L, e, compare())
- 前提:表 L 存在,compare 是数据元素的判定函数
- 结果:返回 L 中第 1 个与 e 满足关系 compare() 的数据元素的位序,如果 e 不存在,在返回 0
- PriorElem(L, cur_e, &pre_e)
- 前提:线性表已存在
- 结果:若 cur_e 是 L 的数据元素,且不是第一个,则用 pre_e 返回它的前驱,否则操作失败,pre_e 无定义
- NextElem(L, cur_e, &next_e)
- 前提:线性表已存在
- 结果:若 cur_e 是 L 的数据元素,且不是最后一个,则用 next_e 返回它的前驱,否则操作失败,next_e 无定义
- ListInsert(&L, i, e)
- 前提:表 L 已存在,e 为合法元素值且 0 <= i <= ListLength(L)
- 结果:在 L 中第 i 个位置之前插入新的数据 e,L 的长度加 1
- ListDelete(&L, i, &e)
- 前提:表 L 存在且非空,0 <= i <= ListLength(L) - 1
- 结果:删除 L 的第 i 个数据元素,并用 e 返回其值,L 的长度减 1
- ListTranverse(L, visit())
- 前提:表 L 存在
- 结果:依次对 L 的每个数据元素调用 visit(),一旦 visit() 失败则操作失败
二、线性表的顺序存储
线性表的顺序存储 是指用一组地址连续的存储单元存储数据,使得逻辑上相邻的元素在物理上也是相邻的。
1. 存储结构
我们可以使用一维数组来存储一个线性表的元素,具体如下:
// 缓冲区初始大小
#define LIST_INIT_SIZE 100
// 缓冲区增量
#define LIST_INCREMENT 10
// 顺序表结构
typedef struct
{
char *elem; // 线性表存储空间基址,为简便起见,我们让线性表存储单个字符
int length; // 线性表中元素个数
int listsize; // 当前分配的存储容量
} SeqList;
顺序存储图示
2. 核心操作
我们详细说明顺序表的核心操作,其他操作见项目代码。
1> 初始化
申请顺序表空间,初始化变量。
顺序表初始化图示
C 语言实现
// 前提:L 为未初始化的线性表
// 结果:将 L 初始化为空表
Status InitList(SeqList& L)
{ // 开辟顺序表的初始存储空间,即:缓冲区空间
L.elem = (char*) malloc(LIST_INIT_SIZE * sizeof(char));
if(!L.elem)
{ // 如果申请失败,返回错误代码
printf("存储分配失败\n");
return OVERFLOW;
}
L.length = 0; // 设置元素个数为 0
L.listsize = LIST_INIT_SIZE; // 设置初始缓冲区大小
return OK; // 初始化成功
}
2> 清空
将当前顺序表中数据清除,保持原有存储结构;此时缓冲区大小 L.listsize
不做改变,也就是说,缓冲区大小是可以大于初始大小 LIST_INIT_SIZE
的。
顺序表清空图示
C 语言实现
// 前提:线性表 L 已存在
// 结果:将 L 置为空表
void ClearList(SeqList &L)
{
// 将顺序表缓冲区中的数据清零
if(L.elem)
memset(L.elem, 0, sizeof(char) * L.listsize);
// 将顺序表长度置 0
L.length = 0;
}
3> 销毁
将顺序表销毁,释放所有占用的内存。
顺序表销毁图示
C 语言实现
// 前提:线性表 L 已存在
// 结果:将 L 销毁
void DestroyList(SeqList &L)
{
if(L.elem)
{
free(L.elem);
L.elem = NULL;
}
L.length = 0;
L.listsize = 0;
}
4> 插入
在顺序表中的指定位置 i (0 <= i <= L.length) 插入元素 e。
顺序表插入图示
C 语言实现
// 前提:表 L 已存在,e 为合法元素值且 0 <= i <= ListLength(L)
// 结果:在 L 中第 i 个位置之前插入新的数据 e,L 的长度加 1,
// 插入成功返回 TRUE,否则,返回 FALSE
int ListInsert(SeqList &L, int i, char e)
{ // 判断插入位置是否合法
if(i < 0 || i > L.length)
return ERROR; // 地址错误
// 判断缓冲区是否充足
if(L.length >= L.listsize)
{ // 缓存区长度不够,扩大容量,让缓冲区增加 LIST_INCREMENT
char *buf = (char*) realloc(L.elem, (L.listsize + LIST_INCREMENT) * sizeof(char));
if(!buf) return OVERFLOW;
// 重置缓冲区指针
L.elem = buf;
// 重置缓冲区大小
L.listsize += LIST_INCREMENT;
}
// pos 指向下标为 i 的元素
char *pos = &(L.elem[i]);
// p 从最后一个数据元素 L.elem[L.length - 1] 开始,依次递减,直到下标为 i 的元素
for(char *p = &(L.elem[L.length - 1]); p >= pos; p--)
*(p + 1) = *p; // 将当前元素向后移动一个位置
*pos = e; // 将新增元素放在 pos 所指位置
L.length++; // 元素个数加 1
return OK;
}
5> 删除
将顺序表中指定位置的元素删除。
顺序表删除图示
C 语言实现
// 删除表中元素
// 前提:表 L 存在且非空,0 <= i <= ListLength(L) - 1
// 结果:删除 L 的第 i 个数据元素,并用 e 返回其值,L 的长度减 1
int ListDelete(SeqList &L, int i, char &e)
{
// 判断删除位置是否合法
if(i < 0 || i >= L.length) return ERROR;
// p 指向下标为 i 的元素
char *p = &L.elem[i];
// 将下标为 i 的元素放入 e 以便返回
e = *p;
// 从 p + 1 开始,到 L.elem[L.length - 1],将每个元素向前移动一个位置
for(p++; p < L.elem + L.length; p++)
*(p - 1) = *p;
L.length--; // 元素个数减 1
return OK;
}
6> 随机访问
获取线性表中指定位置 i (0 <= i <= L.length - 1) 的元素值。
C 语言实现
// 前提:表 L 存在,且 0 <= i <= ListLength(L) - 1
// 结果:用 e 返回 L 中第 i 个数据元素,
// 如果失败,返回 ERROR;否则,返回 OK
int GetElem(SeqList &L, int i, char *e)
{ // 判断位置是否合法
if(i < 0 || i >= L.length)
return ERROR;
// 将下标为 i 的元素放入 e 以便返回
*e = L.elem[i];
return OK;
}
3. 总结
- 顺序表随机访问时,效率比较高,直接就可以访问到,时间为:O(1)
- 顺序表插入和删除时,为了保持顺序结构,需要移动大量的元素,因此效率很差,时间复杂度为:O(n)