10.顺序表
10.顺序表
10.1 顺序表的原理精讲
10.1.1 定义
顺序表是简单的一种线性结构,逻辑上相邻的数据在计算机内的存储位置也是相邻的,可以快速定位第几个元素,中间不允许有空值,插入、删除时需要移动大量元素。
顺序表是线性表的顺序存储结构,具备两大核心特征:
- 逻辑与存储的一致性:逻辑上相邻的元素(如第 i 个和第 i+1 个元素),在计算机内存中也占据连续的存储单元,地址相邻;
- 数据存储规则:存储区域中间不允许有空值,需连续存放元素;访问时可快速定位任意第 n 个元素,但插入、删除元素时需移动大量后续元素以维持连续性。
10.1.2 顺序表的三个要素
顺序表的结构由三个关键要素构成,共同实现对连续存储区域的管理:
| 要素名称 | 定义与作用 |
|---|---|
| 基地址(elems) | 本质是指针,用于记录顺序表存储区域的起始内存地址,是访问所有元素的 “入口”(通过基地址 + 元素下标可计算任意元素地址) |
| 总存储空间(size) | 表示为顺序表分配的连续存储空间的总容量(即最多可存储的元素个数),通常在初始化时固定(如文档中MAX_SIZE=100),避免内存溢出 |
| 实际元素个数(length) | 记录顺序表当前实际存储的有效元素数量(0 ≤ length ≤ size),用于控制遍历、插入、删除等操作的范围,避免访问无效数据 |
三者关系示意图:
基地址(elems) → 存储元素 a₁、a₂、…、aₙ(共 length 个) → 剩余空间(size - length 个) → 总容量 size

10.1.3 顺序表的结构体定义
为实现对三大要素的封装管理,文档中给出标准结构体定义(基于 C/C++),核心是通过结构体整合基地址、length、size:
// 1. 先定义顺序表的最大容量(宏定义,固定总存储空间)
#define MAX_SIZE 100
// 2. 顺序表结构体(ElemType为泛型,可替换为int、STAR等具体数据类型)
struct _SqList{
ElemType *elems; // 基地址:指向存储元素的连续内存区域
int length; // 实际元素个数:当前有效元素数量
int size; // 总存储空间:顺序表最大可存储元素个数(通常=MAX_SIZE)
};
- 注:
ElemType是泛型标识,实际使用时需替换为具体数据类型(如文档后续案例中,存储 int 时用int *elems,存储星星信息时用struct STAR *elems)。
10.1.4 顺序表的核心特性(基于原理衍生)
-
高效随机访问:
借助 “基地址 + 元素下标 × 单个元素字节数” 的计算公式,可直接定位任意第 i 个元素的内存地址,访问时间复杂度为O(1)(这是顺序表最核心的优势)。
例:若
elems地址为 0x1000,单个元素占 4 字节,第 3 个元素(下标 2)的地址 = 0x1000 + 2×4 = 0x1008。 -
存储密度高:
仅存储有效元素数据,无额外指针开销(对比链表),内存利用率高,但需提前分配固定容量,可能存在 “容量不足” 或 “容量过剩” 的情况。
-
插入 / 删除效率受限:
因需维持元素连续性,插入(非尾部)或删除元素时,需移动后续所有元素(如在第 i 个位置插入,需移动第 i 至第 length-1 个元素),时间复杂度为O(n)(n=length)。
10.2 顺序表的算法实现
10.2.1 顺序表的初始化
(1)前期代码准备:搭建基础环境
在编写顺序表算法前,需先完成基础代码框架搭建,确保后续功能可正常编译运行,老师在课上明确了以下步骤:
-
引入头文件与命名空间
顺序表实现需用到输入输出(如打印信息)、内存分配等功能,因此首先引入核心头文件,并使用
std命名空间简化代码(避免频繁写std::):#include <iostream> // 用于输入输出(如cout打印信息) using namespace std; // 简化标准库调用 -
定义顺序表最大容量(MAX_SIZE)
顺序表是 “固定大小的连续存储空间”,需先定义最大可存储元素个数。老师强调 “该值非固定,需按实际项目需求调整”,课上暂设为 100(课件中也采用此默认值):
#define MAX_SIZE 100 // 宏定义顺序表最大容量,可根据需求修改(如1000、10000)
(2)顺序表结构体定义:封装核心属性
顺序表的核心是 “存储基地址、实际元素个数、总容量” 三个属性
①基础写法:先定义结构体,再取别名
先定义结构体struct _SqList,包含三个核心成员,再通过typedef给结构体取别名SqList,避免后续定义变量时重复写struct:
// 1. 定义顺序表结构体(存储基地址、实际长度、总容量)
struct _SqList
{
int *elems; // 基地址:指向存储整数元素的连续内存首地址
int length; // 实际长度:当前顺序表中已存储的元素个数(初始为0)
int size; // 总容量:顺序表最大可存储元素个数(对应MAX_SIZE)
};
// 2. 给结构体取别名SqList,简化后续变量定义
typedef struct _SqList SqList;

②简化写法:结构体定义与 typedef 合并
可将两步合并”,直接在定义结构体时通过typedef取别名,无需单独写结构体名_SqList,代码更简洁:
// 合并写法:直接定义结构体并取别名SqList
typedef struct
{
int *elems; // 基地址
int length; // 实际长度
int size; // 总容量
}SqList; // 结构体别名,后续可直接用“SqList 变量名”定义顺序表
老师特别说明:两种写法功能完全一致,仅代码简洁度不同,后续均以 “SqList作为顺序表类型” 进行开发。
(3)初始化函数(initList):顺序表的 “启动第一步”
初始化是顺序表使用的前提 —— 需为其分配内存、设置初始状态(无元素时length=0),老师称其为 “给顺序表‘搭骨架’”,并强调 “必须做防御性检查(避免内存分配失败)”,与课件中initList函数逻辑完全匹配。
①函数设计思路
-
函数原型:
bool initList(SqList &L)- 返回值:
bool类型(true表示初始化成功,false表示失败,如内存分配失败) - 参数:
SqList &L(引用传递,直接修改实参的顺序表变量,避免值传递拷贝开销)
- 返回值:
-
核心任务:
- 为顺序表分配 “可存储
MAX_SIZE个 int 元素” 的连续内存; - 检查内存分配是否成功(防御性检查);
- 初始化
length(0,无元素)和size(MAX_SIZE,总容量); - 返回初始化结果。
- 为顺序表分配 “可存储
②完整代码实现
bool initList(SqList &L)
{
// 1. 为顺序表分配内存:可存储MAX_SIZE个int元素
L.elems = new int[MAX_SIZE]; // 动态分配内存(C++写法,C语言用malloc)
// 2. 防御性检查:判断内存是否分配成功(如内存不足时,new会返回NULL)
if (!L.elems) // 若elems为NULL,说明分配失败
{
cout << "顺序表初始化失败:内存分配不足!" << endl;
return false; // 返回失败
}
// 3. 初始化顺序表状态:无元素,总容量为MAX_SIZE
L.length = 0; // 实际元素个数:0(刚初始化无元素)
L.size = MAX_SIZE; // 总容量:等于预定义的MAX_SIZE
cout << "顺序表初始化成功!" << endl;
return true; // 返回成功
}
③强调的关键细节
- 内存分配的意义:
L.elems是基地址,只有分配内存后,它才指向一块有效空间,后续才能存储元素; - 防御性检查的必要性:“操作系统可能因内存不足拒绝分配”,若不检查直接使用
L.elems,会导致程序崩溃; - 初始化后的状态:
length=0(无元素)、size=100(默认最大容量),基地址L.elems指向有效内存首地址。
(4)打印函数(listPrint):可视化查看顺序表状态
初始化后需验证状态,后续插入 / 删除元素也需查看结果,因此设计listPrint函数,用于输出顺序表的 “总容量(size)、实际长度(length)、已存储元素”
①函数设计思路
-
函数原型:
void listPrint(SqList &L)- 返回值:
void(仅打印信息,无返回值) - 参数:
SqList &L(引用传递,获取顺序表的当前状态)
- 返回值:
-
核心任务:
- 打印顺序表的总容量(
size)和实际元素个数(length); - 遍历顺序表,打印所有已存储的元素(若
length=0,则无元素输出)。
- 打印顺序表的总容量(
②完整代码实现
void listPrint(SqList &L)
{
// 1. 打印顺序表的基础状态:总容量、实际元素个数
cout << "顺序表状态:容量size=" << L.size << ",已保存元素个数length=" << L.length << endl;
// 2. 遍历打印已存储的元素(仅当length>0时执行)
cout << "顺序表中的元素:";
for (int i = 0; i < L.length; i++)// 下标从0到length-1(遍历所有元素)
{
cout << L.elems[i] << " "; // 输出第i个元素,加空格分隔
}
cout << endl << "-------------------------" << endl; // 换行分隔,便于查看
}
③初始化后的测试效果
在课上演示了 “初始化后调用listPrint” 的结果,与代码逻辑完全一致:
顺序表初始化成功!
顺序表状态:容量size=100,已保存元素个数length=0
顺序表中的元素:
-------------------------
- 因
length=0,循环不执行,无元素输出; - 可直观验证 “初始化是否成功”(如
size=100符合预期,length=0符合 “无元素” 的初始状态)。
(5)初始化流程的完整测试:从定义变量到验证状态
“定义顺序表变量→调用初始化函数→调用打印函数验证” 的流程,形成闭环,代码如下:
#include <iostream>
using namespace std;
#define MAX_SIZE 100 //顺序表最大空间
/*
typedef struct _SqList SqList;
struct _SqList
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
};
*/
typedef struct
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
}SqList;
bool initList(SqList& L)//构造一个空的顺序表
{
L.elems = new int[MAX_SIZE];//为顺序表分配一个MAX_SIZE个int元素的空间
if (!L.elems) return false;//存储分配失败
L.length = 0;
L.size = MAX_SIZE;
return true;
}
void listPrint(SqList& L)
{
cout << "顺序表的存储空间size:" << L.size << ",已保存的元素个数 length:" << L.length << endl;
for (int i = 0; i < L.length; i++)
cout << "L.elems[" << i << "] = " << L.elems[i] << ",";
cout << endl;
}
int main()
{
SqList list;
cout << "顺序表的初始化" << endl;
//1.初始化
if (initList(list))
cout << "顺序表初始化成功" << endl;
listPrint(list);
system("pause");
return 0;
}
测试结果说明
- 若内存分配成功:输出 “顺序表初始化成功!”, followed by 顺序表状态(
size=100,length=0,无元素); - 若内存分配失败:输出 “顺序表初始化失败:内存分配不足!”,无后续打印。
总结:顺序表算法实现的核心逻辑(老师强调的重点)
- 结构体是 “容器”:封装基地址(存哪里)、长度(存了多少)、容量(能存多少),是顺序表的基础;
- 初始化是 “启动键”:必须先分配内存、做防御性检查、置初始状态,否则后续操作会崩溃;
- 打印函数是 “调试工具”:可视化查看顺序表状态,验证初始化、插入、删除等操作的正确性;
- 灵活性:
MAX_SIZE可按实际需求修改(如小芳需要 10000 个元素,只需改#define MAX_SIZE 10000),体现顺序表 “按需定义容量” 的特点。
10.2.2 顺序表添加元素
(1)增加元素的前提与核心概念
在实现 “增加元素” 前,需明确两个基础前提与核心逻辑,这是避免代码错误的关键:
①前提条件
顺序表增加元素的首要前提是 “顺序表已完成初始化”—— 即已通过initList函数分配连续内存(elems指向内存首地址)、length初始化为 0(无元素)、size设为最大容量(如MAX_SIZE=100)。若未初始化直接增加元素,会因elems为空指针导致内存访问错误。
②核心逻辑
顺序表的元素存储具有 “连续且有序” 的特点,增加元素(追加)是将新元素放到当前所有元素的末尾,具体逻辑如下:
- 末尾位置的索引 = 当前
length(因为length是已存元素个数,元素索引从 0 开始,最后一个元素索引为length-1,所以新元素应存在length位置); - 存入新元素后,需将
length加 1(更新已存元素个数); - 关键约束:若
length == size(已存元素个数达到最大容量),则无法继续增加元素(避免数组越界)。

(2)增加元素的代码实现(完整版)
“增加元素” 的核心函数为listAppend,以下是完整代码实现(含依赖的结构体定义、初始化函数及打印函数,仅为支撑增加元素功能,不展开其他函数细节)。
①基础依赖代码(结构体 + 初始化 + 打印)
先补充必要的基础代码,确保listAppend函数可正常运行:
#include <iostream>
using namespace std;
// 1. 定义顺序表最大容量
#define MAX_SIZE 100 //顺序表最大空间
/*
typedef struct _SqList SqList;
struct _SqList
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
};
*/
// 2. 定义顺序表结构体(整数类型,typedef简化类型名)
typedef struct
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
}SqList;
// 3. 顺序表初始化函数(增加元素的前提,简要实现)
bool initList(SqList& L)//构造一个空的顺序表
{
L.elems = new int[MAX_SIZE];//为顺序表分配一个MAX_SIZE个int元素的空间
if (!L.elems) return false;//存储分配失败 防御性检查:内存分配失败(如系统内存不足)
L.length = 0;// 初始无元素,length=0
L.size = MAX_SIZE; // 容量设为最大尺寸
return true;
}
// 4. 打印函数(验证增加元素的效果)
void listPrint(SqList& L)
{
cout << "顺序表的存储空间size:" << L.size << ",已保存的元素个数 length:" << L.length << endl;
for (int i = 0; i < L.length - 1; i++)
cout << "L.elems[" << i << "] = " << L.elems[i] << ",";
cout << endl;
}
②增加元素核心函数(listAppend)
函数定义与参数说明
bool listAppend(SqList &L, int e)
{
// 步骤1:防御性检查——判断顺序表是否已满(容量上限)
if (L.length == L.size)
{
cout << "追加失败:顺序表容量已满(当前容量" << L.size << ")\n";
return false;
}
// 步骤2:将新元素存入顺序表末尾(末尾索引 = L.length)
L.elems[L.length] = e;
// 步骤3:更新已存元素个数(length+1)
L.length++;
// 步骤4:追加成功,返回true
return true;
}
③主函数测试(验证增加元素效果)
通过主函数调用初始化、增加元素、打印函数,直观查看效果:
#include <iostream>
using namespace std;
// 1. 定义顺序表最大容量
#define MAX_SIZE 100 //顺序表最大空间
/*
typedef struct _SqList SqList;
struct _SqList
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
};
*/
// 2. 定义顺序表结构体(整数类型,typedef简化类型名)
typedef struct
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
}SqList;
// 3. 顺序表初始化函数(增加元素的前提,简要实现)
bool initList(SqList& L)//构造一个空的顺序表
{
L.elems = new int[MAX_SIZE];//为顺序表分配一个MAX_SIZE个int元素的空间
if (!L.elems) return false;//存储分配失败 防御性检查:内存分配失败(如系统内存不足)
L.length = 0;// 初始无元素,length=0
L.size = MAX_SIZE; // 容量设为最大尺寸
return true;
}
// 4. 打印函数(验证增加元素的效果)
void listPrint(SqList& L)
{
cout << "顺序表的存储空间size:" << L.size << ",已保存的元素个数 length:" << L.length << endl;
for (int i = 0; i < L.length; i++)
cout << "L.elems[" << i << "] = " << L.elems[i] << ",";
cout << endl;
}
bool listAppend(SqList& L, int e)
{
// 步骤1:防御性检查——判断顺序表是否已满(容量上限)
if (L.length == L.size)
{
cout << "追加失败:顺序表容量已满(当前容量" << L.size << ")\n";
return false;
}
// 步骤2:将新元素存入顺序表末尾(末尾索引 = L.length)
L.elems[L.length] = e;
// 步骤3:更新已存元素个数(length+1)
L.length++;
// 步骤4:追加成功,返回true
return true;
}
int main()
{
SqList list;
cout << "顺序表的初始化" << endl;
//1.初始化
if (initList(list))
cout << "顺序表初始化成功" << endl;
//2.添加元素
int count = 0;
cout << "请输入要添加的元素个数:" << endl;
cin >> count;
int e;
for (int i = 0; i < count; i++)
{
cout << "\n请输入要添加的元素e:";
cin >> e;
if (listAppend(list, e)) cout << "添加成功!" << endl;
else cout << "添加失败!" << endl;
}
listPrint(list);
system("pause");
return 0;
}
10.2.3 顺序表插入元素
(1)插入元素的核心需求与原理
①核心需求
在顺序表已存储的元素序列中,指定一个合法下标位置i,将新元素e插入到该位置,同时保证插入后原位置及后续元素的顺序不被破坏(仅向后偏移 1 位),且顺序表的长度同步更新。
②插入原理(图示解析)

假设现有一个顺序表,已存储 3 个元素(length=3,下标范围0~2),元素依次为[a0, a1, a2],需求是 “在下标i=1的位置插入新元素a3”,具体过程如下:
- 初始状态:顺序表的基地址指向
a0,a0在elems[0]、a1在elems[1]、a2在elems[2],length=3; - 元素后移:要插入
a3到elems[1],需先将a1及后续的a2向后移动 1 位 ——必须从最后一个元素(a2,下标2)开始后移,否则a1会先覆盖a2导致数据丢失。移动后a2到elems[3]、a1到elems[2],elems[1]空出; - 插入新元素:将
a3赋值到空出的elems[1]; - 更新长度:顺序表长度从
3变为4,最终序列为[a0, a3, a1, a2]。
核心规律:被插入位置i及之后的所有元素,必须从后往前依次后移 1 位,再插入新元素。
(2)插入算法的实现步骤
顺序表插入元素的函数命名为listInsert,需接收 3 个参数:SqList &L(待操作的顺序表,用引用传递以修改原表)、int i(插入位置的下标)、int e(待插入的元素值)。算法实现分 4 步,且每一步需做好 “防御性检查”,避免非法操作。
①步骤 1:合法性检查(防御性措施)
插入操作前需先判断两种非法情况,若触发则直接返回false(插入失败):
-
情况 1:插入位置
i非法插入位置
i必须满足 “0 ≤ i < L.length”:-
i < 0:下标为负,无意义; -
i ≥ L.length:顺序表的有效元素仅到L.length-1,i超出有效范围(若i = L.length,本质是 “尾部添加”,应调用listAppend而非listInsert)。代码:
if (i < 0 || i >= L.length) return false; // i值不合法
-
-
情况 2:顺序表空间已满
若顺序表现有元素个数
L.length已等于最大容量MAX_SIZE,插入后会导致数组越界,因此无法插入。代码:
if (L.length == MAX_SIZE) return false; // 存储空间已满
②步骤 2:元素后移(从后往前)
通过for循环实现 “从最后一个元素到插入位置i” 的后移:
- 循环变量
j的初始值:L.length - 1(最后一个元素的下标); - 循环条件:
j ≥ i(直到处理到插入位置i的元素); - 循环操作:
L.elems[j+1] = L.elems[j](将当前元素后移 1 位到j+1下标),然后j--(向前推进 1 位)。
代码:
for (int j = L.length - 1; j >= i; j--)
{
L.elems[j+1] = L.elems[j]; // 从最后一个元素开始后移,直到第i个元素
}
③步骤 3:插入新元素
此时elems[i]已空出(被后移操作腾位),直接将新元素e赋值到elems[i]。
代码:L.elems[i] = e; // 将新元素e放入第i个位置
④步骤 4:更新顺序表长度
插入成功后,顺序表的有效元素个数增加 1,需将L.length加 1(若忘记更新,后续操作会忽略新插入的元素)。
代码:L.length++; // 表长增1
完整插入函数代码
结合上述步骤,完整的listInsert函数实现如下(与课件代码一致,补充详细注释):
// 功能:在顺序表L的第i个下标位置插入元素e
// 参数:L-待操作的顺序表(引用传递),i-插入位置下标,e-待插入元素
// 返回值:true-插入成功,false-插入失败
bool listInsert(SqList& L, int i, int e)
{
// 1. 合法性检查:位置非法或空间已满,直接返回失败
if (i < 0 || i >= L.length) return false;//i的值不合法
if (L.length == L.size) return false;//存储空间已满
// 2. 元素后移:从最后一个元素到位置i,依次后移1位
for (int j = L.length - 1; j >= i; j--)
{
L.elems[j+1] = L.elems[j];
}
// 3. 插入新元素到空出的位置i
L.elems[i] = e;
// 4. 更新顺序表长度
L.length++;//表长加1
return true;
}
(3)插入操作的测试验证
为确保listInsert函数正确,需在main函数中编写测试逻辑 —— 模拟 “添加初始元素→输入插入位置和元素→执行插入→打印结果” 的流程,同时测试非法场景(验证防御性检查是否生效)。
①测试流程代码
#include <iostream>
using namespace std;
// 1. 定义顺序表最大容量
#define MAX_SIZE 100 //顺序表最大空间
/*
typedef struct _SqList SqList;
struct _SqList
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
};
*/
// 2. 定义顺序表结构体(整数类型,typedef简化类型名)
typedef struct
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
}SqList;
// 3. 顺序表初始化函数(增加元素的前提,简要实现)
bool initList(SqList& L)//构造一个空的顺序表
{
L.elems = new int[MAX_SIZE];//为顺序表分配一个MAX_SIZE个int元素的空间
if (!L.elems) return false;//存储分配失败 防御性检查:内存分配失败(如系统内存不足)
L.length = 0;// 初始无元素,length=0
L.size = MAX_SIZE; // 容量设为最大尺寸
return true;
}
// 4. 打印函数(验证增加元素的效果)
void listPrint(SqList& L)
{
cout << "顺序表的存储空间size:" << L.size << ",已保存的元素个数 length:" << L.length << endl;
for (int i = 0; i < L.length; i++)
cout << "L.elems[" << i << "] = " << L.elems[i] << ",";
cout << endl;
}
bool listAppend(SqList& L, int e)
{
// 步骤1:防御性检查——判断顺序表是否已满(容量上限)
if (L.length == L.size)
{
cout << "追加失败:顺序表容量已满(当前容量" << L.size << ")\n";
return false;
}
// 步骤2:将新元素存入顺序表末尾(末尾索引 = L.length)
L.elems[L.length] = e;
// 步骤3:更新已存元素个数(length+1)
L.length++;
// 步骤4:追加成功,返回true
return true;
}
bool listInsert(SqList& L, int i, int e)
{
// 1. 合法性检查:位置非法或空间已满,直接返回失败
if (i < 0 || i >= L.length) return false;//i的值不合法
if (L.length == L.size) return false;//存储空间已满
// 2. 元素后移:从最后一个元素到位置i,依次后移1位
for (int j = L.length - 1; j >= i; j--)
{
L.elems[j+1] = L.elems[j];
}
// 3. 插入新元素到空出的位置i
L.elems[i] = e;
// 4. 更新顺序表长度
L.length++;//表长加1
return true;
}
int main()
{
SqList list;
cout << "顺序表的初始化" << endl;
//1.初始化
if (initList(list))
cout << "顺序表初始化成功" << endl;
//2.添加元素
int count = 0;
cout << "请输入要添加的元素个数:" << endl;
cin >> count;
int e;
for (int i = 0; i < count; i++)
{
cout << "\n请输入要添加的元素e:";
cin >> e;
if (listAppend(list, e)) cout << "添加成功!" << endl;
else cout << "添加失败!" << endl;
}
listPrint(list);
//3.插入元素
cout << "请输入需要插入的元素的位置和数据元素:";
int i;
cin >> i >> e;
if (listInsert(list, i, e))
{
cout << "插入成功!" << endl;
listPrint(list);
}
else
{
cout << "插入失败!" << endl;
}
system("pause");
return 0;
}
②测试用例与结果分析
用例 1:合法插入(正常场景)
- 初始元素:
[0, 1, 2](length=3); - 输入:插入位置
i=2,元素e=4; - 预期结果:插入后元素为
[0, 1, 4, 2],length=4; - 实际结果:打印 “插入成功!”,顺序表长度从 3 变为 4,元素顺序符合预期 —— 验证
listInsert正常工作。
用例 2:非法插入(位置超出范围)
- 初始元素:
[0, 1, 2](length=3); - 输入:插入位置
i=3,元素e=4; - 预期结果:插入失败(
i=3 ≥ length=3); - 实际结果:打印 “插入失败!”—— 验证位置合法性检查生效。
用例 3:非法插入(空间已满)
- 初始操作:添加 100 个元素(
MAX_SIZE=100,length=100); - 输入:插入位置
i=50,元素e=999; - 预期结果:插入失败(
length=MAX_SIZE); - 实际结果:打印 “插入失败!”—— 验证空间合法性检查生效。
(4)关键注意事项
-
后移方向必须 “从后往前”
若从插入位置
i开始 “从前往后” 移动(如j从i开始),会导致elems[i]先覆盖elems[i+1],后续元素数据丢失(例如a1覆盖a2,a2永久丢失),这是插入算法的核心易错点。 -
防御性检查不可省略
若跳过 “位置合法性” 或 “空间满” 的检查,会导致数组越界(程序崩溃)或插入到无效位置(数据混乱),必须优先处理非法场景。
-
插入后必须更新
lengthL.length记录顺序表的有效元素个数,若插入后不递增,后续的listPrint、listDelete等操作会 “忽略” 新插入的元素,导致逻辑错误。
(5)小结
顺序表插入元素的核心是 “先防御检查→再后移元素→最后插入更新”,其本质是利用顺序表 “存储连续” 的特性,通过元素后移腾位实现指定位置插入。虽然插入操作的时间复杂度为O(n)(最坏情况需移动所有元素),但它是顺序表的基础接口,也是理解 “线性表操作逻辑” 的关键。后续在实际项目中(如课件提到的 “高并发 WEB 服务器连接管理”),若需在固定位置插入数据,可基于此算法扩展实现。
10.2.4 顺序表删除元素
(1)顺序表删除元素的核心原理

顺序表的本质是 “逻辑相邻则物理相邻” 的线性结构,删除元素后需保证剩余元素依然连续(无空洞)。根据删除位置的不同,可分为两种核心场景:
①场景 1:删除 “最后一个元素”(边界场景)
若要删除的元素是顺序表的最后一个(即下标 i = length - 1,length 为当前有效元素个数),由于其后续无其他元素,无需移动任何数据 —— 只需将 length 直接减 1,即可表示该元素不再属于 “有效元素范围”。
示例:现有顺序表 length = 5(元素下标 0~4),删除下标 4 的最后一个元素:
- 直接执行
length = 4,此时有效元素范围变为 0~3,原下标 4 的元素不再被视为有效,无需移动其他元素。
②场景 2:删除 “非最后一个元素”(普通场景)
若删除的是中间或开头的元素(i < length - 1),由于后续元素会形成 “空洞”,需将删除位置之后的所有元素依次向前移动 1 位,覆盖空洞后再更新 length。
示例:现有顺序表 length = 5(元素下标 0~4),删除下标 1 的元素:
- 从下标 1 开始,将下标 2 的元素移到下标 1、下标 3 的元素移到下标 2、下标 4 的元素移到下标 3;
- 移动完成后,执行
length = 4,有效元素范围变为 0~3,原下标 4 的元素被 “舍弃”(不再有效)。
图示逻辑:
| 删除前(length=5) | 下标 0 | 下标 1(待删) | 下标 2 | 下标 3 | 下标 4 |
|---|---|---|---|---|---|
| 移动过程 | 不变 | 被下标 2 覆盖 | 被下标 3 覆盖 | 被下标 4 覆盖 | 无后续元素 |
| 删除后(length=4) | 下标 0 | 下标 1(原下标 2) | 下标 2(原下标 3) | 下标 3(原下标 4) | 无效 |
(2)顺序表删除元素的算法实现(完整代码)
顺序表删除元素的函数实现需遵循 “先检查、再处理、后更新” 的逻辑。
①前提:顺序表结构体定义
首先明确顺序表的结构体,后续操作基于此结构:
#define MAX_SIZE 100 // 顺序表最大容量
typedef struct
{
int *elems; // 存储元素的基地址(连续空间的起始位置)
int length; // 当前有效元素个数
int size; // 顺序表总容量(最大可存储元素数)
} SqList;
②核心函数:listDelete(删除指定下标元素)
函数功能:删除顺序表 L 中下标为 i 的元素,返回 true 表示删除成功,false 表示删除失败(如下标不合法)。
步骤 1:合法性检查(关键!避免越界)
删除操作的第一步必须判断 “待删除下标 i 是否合法”,不合法的情况包括:
i < 0:下标为负数,超出顺序表的下标范围(顺序表下标从 0 开始);i >= L.length:下标超出当前有效元素的范围(如length=3时,i=3及以上均无效);- 特殊情况:若
L.length=0(顺序表为空),则任何i都不合法(无元素可删)。
步骤 2:分场景处理删除逻辑
- 场景 1:删除最后一个元素(
i == L.length - 1):直接将L.length减 1; - 场景 2:删除非最后一个元素(
i < L.length - 1):循环移动后续元素,再减length。
完整代码实现
bool listDelete(SqList &L, int i)
{
// 步骤1:合法性检查(不合法则返回false)
if (i < 0 || i >= L.length)
{
printf("删除失败:下标不合法(超出有效元素范围)\n");
return false;
}
// 步骤2:分场景处理删除
// 场景1:删除最后一个元素,直接减length
if (i == L.length - 1)
{
L.length--;
return true;
}
// 场景2:删除非最后一个元素,后续元素前移
for (int j = i; j < L.length - 1; j++)
{
// 从待删下标i开始,将j+1的元素移到j的位置
L.elems[j] = L.elems[j + 1];
}
// 移动完成后,有效元素个数减1
L.length--;
return true;
}
示例
#include <iostream>
using namespace std;
// 1. 定义顺序表最大容量
#define MAX_SIZE 100 //顺序表最大空间
/*
typedef struct _SqList SqList;
struct _SqList
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
};
*/
// 2. 定义顺序表结构体(整数类型,typedef简化类型名)
typedef struct
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
}SqList;
// 3. 顺序表初始化函数(增加元素的前提,简要实现)
bool initList(SqList& L)//构造一个空的顺序表
{
L.elems = new int[MAX_SIZE];//为顺序表分配一个MAX_SIZE个int元素的空间
if (!L.elems) return false;//存储分配失败 防御性检查:内存分配失败(如系统内存不足)
L.length = 0;// 初始无元素,length=0
L.size = MAX_SIZE; // 容量设为最大尺寸
return true;
}
// 4. 打印函数(验证增加元素的效果)
void listPrint(SqList& L)
{
cout << "顺序表的存储空间size:" << L.size << ",已保存的元素个数 length:" << L.length << endl;
for (int i = 0; i < L.length; i++)
cout << "L.elems[" << i << "] = " << L.elems[i] << ",";
cout << endl;
}
bool listAppend(SqList& L, int e)
{
// 步骤1:防御性检查——判断顺序表是否已满(容量上限)
if (L.length == L.size)
{
cout << "追加失败:顺序表容量已满(当前容量" << L.size << ")\n";
return false;
}
// 步骤2:将新元素存入顺序表末尾(末尾索引 = L.length)
L.elems[L.length] = e;
// 步骤3:更新已存元素个数(length+1)
L.length++;
// 步骤4:追加成功,返回true
return true;
}
bool listInsert(SqList& L, int i, int e)
{
// 1. 合法性检查:位置非法或空间已满,直接返回失败
if (i < 0 || i >= L.length) return false;//i的值不合法
if (L.length == L.size) return false;//存储空间已满
// 2. 元素后移:从最后一个元素到位置i,依次后移1位
for (int j = L.length - 1; j >= i; j--)
{
L.elems[j+1] = L.elems[j];
}
// 3. 插入新元素到空出的位置i
L.elems[i] = e;
// 4. 更新顺序表长度
L.length++;//表长加1
return true;
}
bool listDelete(SqList& L, int i)
{
// 步骤1:合法性检查(不合法则返回false)
if (i < 0 || i >= L.length)
{
printf("删除失败:下标不合法(超出有效元素范围)\n");
return false;
}
// 步骤2:分场景处理删除
// 场景1:删除最后一个元素,直接减length
if (i == L.length - 1)
{
L.length--;
return true;
}
// 场景2:删除非最后一个元素,后续元素前移
for (int j = i; j < L.length - 1; j++)
{
// 从待删下标i开始,将j+1的元素移到j的位置
L.elems[j] = L.elems[j + 1];
}
// 移动完成后,有效元素个数减1
L.length--;
return true;
}
int main()
{
SqList list;
cout << "顺序表的初始化" << endl;
//1.初始化
if (initList(list))
cout << "顺序表初始化成功" << endl;
//2.添加元素
int count = 0;
cout << "请输入要添加的元素个数:" << endl;
cin >> count;
int e;
for (int i = 0; i < count; i++)
{
cout << "\n请输入要添加的元素e:";
cin >> e;
if (listAppend(list, e)) cout << "添加成功!" << endl;
else cout << "添加失败!" << endl;
}
listPrint(list);
//3.插入元素
cout << "请输入需要插入的元素的位置和数据元素:";
int i;
cin >> i >> e;
if (listInsert(list, i, e))
{
cout << "插入成功!" << endl;
listPrint(list);
}
else
{
cout << "插入失败!" << endl;
}
//4.删除元素
cout << "请输入需要删除的元素的位置:";
cin >> i;
if (listDelete(list, i))
{
cout << "删除成功" << endl;
listPrint(list);
}
else
{
cout << "删除失败" << endl;
}
system("pause");
return 0;
}
(3)测试用例设计(验证算法正确性)
“测试需覆盖边界场景与普通场景”,确保代码的所有分支都能被执行,避免潜在 bug。以下是 4 个核心测试用例:
测试用例 1:顺序表为空时删除(边界场景)
- 初始状态:
L.length=0,L.elems为空; - 操作:调用
listDelete(L, 0)(尝试删除下标 0 的元素); - 预期结果:删除失败,提示 “下标不合法”,
L.length仍为 0。
测试用例 2:删除最后一个元素(边界场景)
- 初始状态:
L.length=3,元素为[10, 20, 30](下标 0~2); - 操作:调用
listDelete(L, 2)(删除最后一个元素,下标 2); - 预期结果:删除成功,
L.length=2,有效元素变为[10, 20],无需移动元素。
测试用例 3:删除中间元素(普通场景)
- 初始状态:
L.length=5,元素为[1, 2, 3, 4, 5](下标 0~4); - 操作:调用
listDelete(L, 1)(删除下标 1 的元素 “2”); - 预期结果:删除成功,后续元素前移后,有效元素变为
[1, 3, 4, 5],L.length=4。
测试用例 4:删除下标超出范围(非法场景)
- 初始状态:
L.length=4,元素为[5, 6, 7, 8](下标 0~3); - 操作:调用
listDelete(L, 4)(下标 4 超出有效范围); - 预期结果:删除失败,提示 “下标不合法”,
L.length仍为 4。
(4)常见问题与注意事项
- 忘记合法性检查导致越界:若跳过
i >= L.length的检查,当i超出有效范围时,循环移动元素会访问L.elems[j+1](如j=L.length时,j+1超出数组容量),导致内存越界错误。 - 循环范围错误:移动元素时,循环条件需为
j < L.length - 1(而非j <= L.length - 1)。若写成j <= L.length - 1,则j=L.length-1时,j+1=L.length会访问无效内存。 - 删除后未更新 length:若只移动元素但忘记
L.length--,会导致 “有效元素个数未减少”,后续操作(如遍历)会误将已 “删除” 的元素视为有效。 - 顺序表为空的特殊处理:当
L.length=0时,i=0也属于i >= L.length(0>=0),会被合法性检查拦截,无需额外写判断逻辑,代码更简洁。
(5)总结
顺序表删除元素的核心是 “连续存储无空洞”,需根据删除位置分场景处理:
- 删最后一个元素:直接减
length(高效,时间复杂度 O (1)); - 删非最后一个元素:移动后续元素(时间复杂度 O (n),n 为有效元素个数)。
其中,合法性检查是避免内存越界的关键,而 “边界场景测试”(空表、删最后一个元素)是验证代码正确性的必要步骤。掌握这一操作,能更深入理解顺序表 “连续存储” 的特性与局限性。
10.2.5 顺序表销毁
(1)销毁顺序表的核心意义
在 C++ 中,顺序表的存储空间是通过new动态分配的(如初始化函数initList中L.elems = new int[MAX_SIZE];),而 C++ 没有自动垃圾回收机制。若顺序表使用完毕后不主动释放内存,会导致内存泄漏—— 即动态分配的内存始终被占用,无法被系统回收,长期运行(如服务器程序)会逐渐耗尽内存,最终导致程序崩溃。
因此,“销毁顺序表” 的核心目的是:释放顺序表动态分配的存储空间,重置表的属性,标志顺序表生命周期的结束,避免内存泄漏和后续误操作。
(2)销毁顺序表的算法逻辑与核心步骤
销毁顺序表需遵循 “先释放内存,再重置属性” 的逻辑,确保资源完全回收且表状态清晰,具体步骤如下:
- 判断内存是否有效:检查顺序表的基地址
elems是否指向有效内存(非NULL),避免对空指针重复释放。 - 释放动态内存:若
elems非空,使用delete[]释放其指向的数组内存(因elems由new[]分配,需匹配delete[])。 - 重置表属性:将顺序表的有效长度
length和总空间大小size重置为 0,标记表已销毁,避免后续误操作。
(3)代码实现与细节拆解
①顺序表结构体定义(依赖基础)
在理解销毁逻辑前,需明确顺序表的结构体定义,销毁操作需操作结构体的三个核心成员:
#define MAX_SIZE 100
typedef struct
{
int *elems; // 顺序表的基地址(指向动态分配的数组)
int length; // 顺序表的有效元素个数
int size; // 顺序表的总存储空间大小
}SqList;
②销毁顺序表的完整代码(来自课件)
void destroyList(SqList &L)
{
if (L.elems) delete []L.elems; // 释放存储空间
L.length = 0; // 重置有效元素个数
L.size = 0; // 重置总空间大小
}
③代码逐行拆解
A. 函数定义:void destroyList(SqList &L)
- 参数类型:
SqList &L(顺序表的引用)必须使用引用传递,原因是:销毁操作需要修改顺序表L本身的成员(elems指向的内存、length、size),引用传递可直接操作原对象,避免值传递的 “拷贝开销” 和 “无法修改原对象” 的问题。若用值传递(SqList L),函数内修改的是拷贝后的临时对象,原顺序表的内存仍未释放,会导致内存泄漏。
B. 内存释放判断:if (L.elems) delete []L.elems;
-
判断
L.elems非空:顺序表初始化时(
initList),elems会通过new分配内存,但存在两种特殊情况:①new分配失败时,elems会返回NULL;② 顺序表已被销毁过,elems已变成野指针。虽然 C++ 标准中delete NULL是安全的,但增加此判断能避免 “重复释放野指针” 的风险(重复释放会导致程序崩溃),是严谨的编程习惯。 -
delete []L.elems的必要性:因
elems指向的是动态数组(初始化时用new int[MAX_SIZE]创建),必须用delete[]释放;若误用delete(用于释放单个对象),会导致数组中除第一个元素外的其他元素内存无法回收,引发内存泄漏。
C. 属性重置:L.length = 0; L.size = 0;
-
重置
length = 0:length表示顺序表的有效元素个数,内存释放后,有效元素已不存在,重置为 0 可明确表内无有效数据。 -
重置
size = 0:size表示顺序表的总存储空间大小,内存释放后,总空间已回收,重置为 0 可明确表无分配的存储空间。 -
核心作用:标记顺序表 “已销毁”,避免后续代码误操作(如销毁后仍试图访问
L.elems、调用listInsert插入元素等,此时这些操作已无意义,重置为 0 能减少错误)。
(4)“销毁顺序表” 与 “删除表中元素” 的关键区别
课件中同时实现了 “删除元素”(listDelete)和 “销毁顺序表”(destroyList),二者极易混淆,需明确区分:
| 对比维度 | 销毁顺序表(destroyList) |
删除表中元素(listDelete) |
|---|---|---|
| 操作对象 | 整个顺序表的存储空间 | 顺序表中某个下标i的单个元素 |
| 操作目的 | 回收表的动态内存,结束表的生命周期 | 调整表的内容,删除无效元素,表仍可继续使用 |
| 内存操作 | 释放elems指向的全部动态内存 |
不释放总内存,仅通过 “元素前移” 覆盖待删元素 |
| 后续可用性 | 表不可用(需重新initList才能复用) |
表仍可用(可继续插入、删除其他元素) |
| 典型调用时机 | 顺序表不再需要时(如程序退出、模块结束) | 表需调整内容时(如删除无效数据) |
(5)销毁操作的注意事项
-
避免重复销毁:
顺序表销毁后,
elems会变成野指针(内存已释放,但指针地址未置空),若再次调用destroyList,if (L.elems)会判定为非空,执行delete[]会导致程序崩溃。建议销毁后可额外将elems置空(如L.elems = NULL;),增强代码安全性:void destroyList(SqList &L) { if (L.elems) { delete []L.elems; L.elems = NULL; // 避免野指针,防止重复释放 } L.length = 0; L.size = 0; } -
销毁前确保表已初始化:
若对未初始化的
SqList(elems未分配内存,可能是随机值)调用destroyList,if (L.elems)可能误判为非空,执行delete[]会释放非法内存,导致程序崩溃。因此需确保destroyList仅在initList成功后调用。 -
配合顺序表生命周期使用:
顺序表的生命周期应遵循 “初始化(
initList)→ 增删操作(listAppend/listInsert/listDelete)→ 销毁(destroyList)” 的流程,缺一不可,尤其不能省略 “销毁” 步骤,否则必然导致内存泄漏。
例子:
#include <iostream>
using namespace std;
// 1. 定义顺序表最大容量
#define MAX_SIZE 100 //顺序表最大空间
/*
typedef struct _SqList SqList;
struct _SqList
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
};
*/
// 2. 定义顺序表结构体(整数类型,typedef简化类型名)
typedef struct
{
int* elems;//顺序表的基地址
int length;//顺序表的长度
int size;//顺序表的总空间
}SqList;
// 3. 顺序表初始化函数(增加元素的前提,简要实现)
bool initList(SqList& L)//构造一个空的顺序表
{
L.elems = new int[MAX_SIZE];//为顺序表分配一个MAX_SIZE个int元素的空间
if (!L.elems) return false;//存储分配失败 防御性检查:内存分配失败(如系统内存不足)
L.length = 0;// 初始无元素,length=0
L.size = MAX_SIZE; // 容量设为最大尺寸
return true;
}
// 4. 打印函数(验证增加元素的效果)
void listPrint(SqList& L)
{
cout << "顺序表的存储空间size:" << L.size << ",已保存的元素个数 length:" << L.length << endl;
for (int i = 0; i < L.length; i++)
cout << "L.elems[" << i << "] = " << L.elems[i] << ",";
cout << endl;
}
bool listAppend(SqList& L, int e)
{
// 步骤1:防御性检查——判断顺序表是否已满(容量上限)
if (L.length == L.size)
{
cout << "追加失败:顺序表容量已满(当前容量" << L.size << ")\n";
return false;
}
// 步骤2:将新元素存入顺序表末尾(末尾索引 = L.length)
L.elems[L.length] = e;
// 步骤3:更新已存元素个数(length+1)
L.length++;
// 步骤4:追加成功,返回true
return true;
}
bool listInsert(SqList& L, int i, int e)
{
// 1. 合法性检查:位置非法或空间已满,直接返回失败
if (i < 0 || i >= L.length) return false;//i的值不合法
if (L.length == L.size) return false;//存储空间已满
// 2. 元素后移:从最后一个元素到位置i,依次后移1位
for (int j = L.length - 1; j >= i; j--)
{
L.elems[j+1] = L.elems[j];
}
// 3. 插入新元素到空出的位置i
L.elems[i] = e;
// 4. 更新顺序表长度
L.length++;//表长加1
return true;
}
bool listDelete(SqList& L, int i)
{
// 步骤1:合法性检查(不合法则返回false)
if (i < 0 || i >= L.length)
{
printf("删除失败:下标不合法(超出有效元素范围)\n");
return false;
}
// 步骤2:分场景处理删除
// 场景1:删除最后一个元素,直接减length
if (i == L.length - 1)
{
L.length--;
return true;
}
// 场景2:删除非最后一个元素,后续元素前移
for (int j = i; j < L.length - 1; j++)
{
// 从待删下标i开始,将j+1的元素移到j的位置
L.elems[j] = L.elems[j + 1];
}
// 移动完成后,有效元素个数减1
L.length--;
return true;
}
void destroyList(SqList& L)
{
if (L.elems)
{
delete[]L.elems;
L.elems = NULL; // 避免野指针,防止重复释放
}
L.length = 0;
L.size = 0;
}
int main()
{
SqList list;
cout << "顺序表的初始化" << endl;
//1.初始化
if (initList(list))
cout << "顺序表初始化成功" << endl;
//2.添加元素
int count = 0;
cout << "请输入要添加的元素个数:" << endl;
cin >> count;
int e;
for (int i = 0; i < count; i++)
{
cout << "\n请输入要添加的元素e:";
cin >> e;
if (listAppend(list, e)) cout << "添加成功!" << endl;
else cout << "添加失败!" << endl;
}
listPrint(list);
//3.插入元素
cout << "请输入需要插入的元素的位置和数据元素:";
int i;
cin >> i >> e;
if (listInsert(list, i, e))
{
cout << "插入成功!" << endl;
listPrint(list);
}
else
{
cout << "插入失败!" << endl;
}
//4.删除元素
cout << "请输入需要删除的元素的位置:";
cin >> i;
if (listDelete(list, i))
{
cout << "删除成功" << endl;
listPrint(list);
}
else
{
cout << "删除失败" << endl;
}
//5. 销毁
cout << "顺序表销毁..." << endl;
destroyList(list);
system("pause");
return 0;
}
(6)总结
“销毁顺序表” 是顺序表操作中 “收尾” 的关键步骤,核心是通过delete[]释放动态内存,再重置表属性,本质是解决 C++ 手动内存管理的 “内存泄漏” 问题。其与 “删除元素” 的核心区别在于 “操作对象是整个表的内存,还是表内单个元素”,需在实际开发中严格区分,避免混淆导致程序错误。
大话数据结构课程代码
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
int j, k, l;
k = MAXSIZE - 1; /* 注意k首先是最后一个元素的下标 */
if (i < 1 || i > ListLength(L) + 1)
return ERROR;
j = Malloc_SSL(L); /* 获得空闲分量的下标 */
if (j)
{
L[j].data = e; /* 将数据赋值给此分量的data */
for(l = 1; l <= i - 1; l++) /* 找到第i个元素之前的位置 */
k = L[k].cur;
L[j].cur = L[k].cur; /* 把第i个元素之前的cur赋值给新元素的cur */
L[k].cur = j; /* 把新元素的下标赋值给第i个元素之前元素的ur */
return OK;
}
return ERROR;
}
/*将所有的在线性表Lb中但不在La中的数据元素插入到La中*/
void unionL(SqList *La,SqList Lb)
{
int La_len,Lb_len,i;
ElemType e; /*声明与La和Lb相同的数据元素e*/
La_len=ListLength(*La); /*求线性表的长度 */
Lb_len=ListLength(Lb);
for (i=1;i<=Lb_len;i++)
{
GetElem(Lb,i,&e); /*取Lb中第i个数据元素赋给e*/
if (!LocateElem(*La,e)) /*La中不存在和e相同数据元素*/
ListInsert(La,++La_len,e); /*插入*/
}
}
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int ElemType; /* ElemType类型根据实际情况而定,这里为int */
typedef struct
{
ElemType data[MAXSIZE]; /* 数组,存储数据元素 */
int length; /* 线性表当前长度 */
}SqList;
#define OK 1
#define ERROR 0
/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Status;
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值,注意i是指位置,第1个位置的数组是从0开始 */
Status GetElem(SqList L,int i,ElemType *e)
{
if(L.length==0 || i<1 || i>L.length)
return ERROR;
*e=L.data[i-1];
return OK;
}
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(SqList *L,int i,ElemType e)
{
int k;
if (L->length==MAXSIZE) /* 顺序线性表已经满 */
return ERROR;
if (i<1 || i>L->length+1) /* 当i比第一位置小或者比最后一位置后一位置还要大时 */
return ERROR;
if (i<=L->length) /* 若插入数据位置不在表尾 */
{
for(k=L->length-1;k>=i-1;k--) /* 将要插入位置后的元素向后移一位 */
L->data[k+1]=L->data[k];
}
L->data[i-1]=e; /* 将新元素插入 */
L->length++;
return OK;
}
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(SqList *L,int i,ElemType *e)
{
int k;
if (L->length==0) /* 线性表为空 */
return ERROR;
if (i<1 || i>L->length) /* 删除位置不正确 */
return ERROR;
*e=L->data[i-1];
if (i<L->length) /* 如果删除不是最后位置 */
{
for(k=i;k<L->length;k++) /* 将删除位置后继元素前移 */
L->data[k-1]=L->data[k];
}
L->length--;
return OK;
}
/* 线性表的单链表存储结构 */
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList; /* 定义LinkList */
/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p; /* 声明一结点p */
p = L->next; /* 让p指向链表L的第一个结点 */
j = 1; /* j为计数器 */
while (p && j<i) /* p不为空或者计数器j还没有等于i时,循环继续 */
{
p = p->next; /* 让p指向下一个结点 */
++j;
}
if ( !p || j>i )
return ERROR; /* 第i个元素不存在 */
*e = p->data; /* 取第i个元素的数据 */
return OK;
}
s->next = p->next; /* 将p的后继结点赋值给s的后继 */
p->next = s; /* 将s赋值给p的后继 */
/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinkList p,s;
p = *L;
j = 1;
while (p && j < i) /* 寻找第i个结点 */
{
p = p->next;
++j;
}
if (!p || j > i)
return ERROR; /* 第i个元素不存在 */
s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */
s->data = e;
s->next = p->next; /* 将p的后继结点赋值给s的后继 */
p->next = s; /* 将s赋值给p的后继 */
return OK;
}
q = p->next;
p->next = q->next; /* 将q的后继赋值给p的后继 */
/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(LinkList *L,int i,ElemType *e)
{
int j;
LinkList p,q;
p = *L;
j = 1;
while (p->next && j < i) /* 遍历寻找第i个元素 */
{
p = p->next;
++j;
}
if (!(p->next) || j > i)
return ERROR; /* 第i个元素不存在 */
q = p->next;
p->next = q->next; /* 将q的后继赋值给p的后继 */
*e = q->data; /* 将q结点中的数据给e */
free(q); /* 让系统回收此结点,释放内存 */
return OK;
}
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand(time(0)); /* 初始化随机数种子 */
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; /* 先建立一个带头结点的单链表 */
for (i=0; i<n; i++)
{
p = (LinkList)malloc(sizeof(Node)); /* 生成新结点 */
p->data = rand()%100+1; /* 随机生成100以内的数字 */
p->next = (*L)->next;
(*L)->next = p; /* 插入到表头 */
}
}
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */
void CreateListTail(LinkList *L, int n)
{
LinkList p,r;
int i;
srand(time(0)); /* 初始化随机数种子 */
*L = (LinkList)malloc(sizeof(Node)); /* L为整个线性表 */
r=*L; /* r为指向尾部的结点 */
for (i=0; i<n; i++)
{
p = (Node *)malloc(sizeof(Node)); /* 生成新结点 */
p->data = rand()%100+1; /* 随机生成100以内的数字 */
r->next=p; /* 将表尾终端结点的指针指向新结点 */
r = p; /* 将当前的新结点定义为表尾终端结点 */
}
r->next = NULL; /* 表示当前链表结束 */
}
/* 初始条件:链式线性表L已存在。操作结果:将L重置为空表 */
Status ClearList(LinkList *L)
{
LinkList p,q;
p=(*L)->next; /* p指向第一个结点 */
while(p) /* 没到表尾 */
{
q=p->next;
free(p);
p=q;
}
(*L)->next=NULL; /* 头结点指针域为空 */
return OK;
}
#define MAXSIZE 1000 /* 存储空间初始分配量 */
/* 线性表的静态链表存储结构 */
typedef struct
{
ElemType data;
int cur; /* 游标(Cursor) ,为0时表示无指向 */
} Component,StaticLinkList[MAXSIZE];
/* 将一维数组space中各分量链成一个备用链表,space[0].cur为头指针,"0"表示空指针 */
Status InitList(StaticLinkList space)
{
int i;
for (i=0; i<MAXSIZE-1; i++)
space[i].cur = i+1;
space[MAXSIZE-1].cur = 0; /* 目前静态链表为空,最后一个元素的cur为0 */
return OK;
}
/* 若备用空间链表非空,则返回分配的结点下标,否则返回0 */
int Malloc_SSL(StaticLinkList space)
{
int i = space[0].cur; /* 当前数组第一个元素的cur存的值 */
/* 就是要返回的第一个备用空闲的下标 */
if (space[0]. cur)
space[0]. cur = space[i].cur; /* 由于要拿出一个分量来使用了, */
/* 所以我们就得把它的下一个 */
/* 分量用来做备用 */
return i;
}
/* 删除在L中第i个数据元素 */
Status ListDelete(StaticLinkList L, int i)
{
int j, k;
if (i < 1 || i > ListLength(L))
return ERROR;
k = MAXSIZE - 1;
for (j = 1; j <= i - 1; j++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SSL(L, j);
return OK;
}
/* 将下标为k的空闲结点回收到备用链表 */
void Free_SSL(StaticLinkList space, int k)
{
space[k].cur = space[0].cur; /* 把第一个元素的cur值赋给要删除的分量cur */
space[0].cur = k; /* 把要删除的分量下标赋值给第一个元素的cur */
}
/* 初始条件:静态链表L已存在。操作结果:返回L中数据元素个数 */
int ListLength(StaticLinkList L)
{
int j=0;
int i=L[MAXSIZE-1].cur;
while(i)
{
i=L[i].cur;
j++;
}
return j;
}
p=rearA->next; /* 保存A表的头结点,即① */
rearA->next=rearB->next->next; /* 将本是指向B表的第一个结点(不是头结点)*/
/* 赋值给reaA->next,即② */
q=rearB->next;
rearB->next=p; /* 将原A表的头结点赋值给rearB->next,即③ */
free(q); /* 释放q */
/*线性表的双向链表存储结构*/
typedef struct DulNode
{
ElemType data;
struct DuLNode *prior; /*直接前驱指针*/
struct DuLNode *next; /*直接后继指针*/
} DulNode, *DuLinkList;
p->next->prior = p = p->prior->next
s - >prior = p; /*把p赋值给s的前驱,如图中①*/
s -> next = p -> next; /*把p->next赋值给s的后继,如图中②*/
p -> next -> prior = s; /*把s赋值给p->next的前驱,如图中③*/
p -> next = s; /*把s赋值给p的后继,如图中④*/
p->prior->next=p->next; /*把p->next赋值给p->prior的后继,如图中①*/
p->next->prior=p->prior; /*把p->prior赋值给p->next的前驱,如图中②*/
free(p); /*释放结点*/
参考资料来源:
奇牛学院

浙公网安备 33010602011771号