10.顺序表

10.顺序表

10.1 顺序表的原理精讲

10.1.1 定义

顺序表是简单的一种线性结构,逻辑上相邻的数据在计算机内的存储位置也是相邻的,可以快速定位第几个元素,中间不允许有空值,插入、删除时需要移动大量元素。

顺序表是线性表的顺序存储结构,具备两大核心特征:

  1. 逻辑与存储的一致性:逻辑上相邻的元素(如第 i 个和第 i+1 个元素),在计算机内存中也占据连续的存储单元,地址相邻;
  2. 数据存储规则:存储区域中间不允许有空值,需连续存放元素;访问时可快速定位任意第 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 顺序表的核心特性(基于原理衍生)

  1. 高效随机访问

    借助 “基地址 + 元素下标 × 单个元素字节数” 的计算公式,可直接定位任意第 i 个元素的内存地址,访问时间复杂度为O(1)(这是顺序表最核心的优势)。

    例:若elems地址为 0x1000,单个元素占 4 字节,第 3 个元素(下标 2)的地址 = 0x1000 + 2×4 = 0x1008。

  2. 存储密度高

    仅存储有效元素数据,无额外指针开销(对比链表),内存利用率高,但需提前分配固定容量,可能存在 “容量不足” 或 “容量过剩” 的情况。

  3. 插入 / 删除效率受限

    因需维持元素连续性,插入(非尾部)或删除元素时,需移动后续所有元素(如在第 i 个位置插入,需移动第 i 至第 length-1 个元素),时间复杂度为O(n)(n=length)。

10.2 顺序表的算法实现

10.2.1 顺序表的初始化

(1)前期代码准备:搭建基础环境

在编写顺序表算法前,需先完成基础代码框架搭建,确保后续功能可正常编译运行,老师在课上明确了以下步骤:

  1. 引入头文件与命名空间

    顺序表实现需用到输入输出(如打印信息)、内存分配等功能,因此首先引入核心头文件,并使用std命名空间简化代码(避免频繁写std::):

    #include <iostream>  // 用于输入输出(如cout打印信息)
    using namespace std;  // 简化标准库调用
    
  2. 定义顺序表最大容量(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(引用传递,直接修改实参的顺序表变量,避免值传递拷贝开销)
  • 核心任务:

    1. 为顺序表分配 “可存储MAX_SIZE个 int 元素” 的连续内存;
    2. 检查内存分配是否成功(防御性检查);
    3. 初始化length(0,无元素)和sizeMAX_SIZE,总容量);
    4. 返回初始化结果。
②完整代码实现
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(引用传递,获取顺序表的当前状态)
  • 核心任务:

    1. 打印顺序表的总容量(size)和实际元素个数(length);
    2. 遍历顺序表,打印所有已存储的元素(若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=100length=0,无元素);
  • 若内存分配失败:输出 “顺序表初始化失败:内存分配不足!”,无后续打印。

总结:顺序表算法实现的核心逻辑(老师强调的重点)

  1. 结构体是 “容器”:封装基地址(存哪里)、长度(存了多少)、容量(能存多少),是顺序表的基础;
  2. 初始化是 “启动键”:必须先分配内存、做防御性检查、置初始状态,否则后续操作会崩溃;
  3. 打印函数是 “调试工具”:可视化查看顺序表状态,验证初始化、插入、删除等操作的正确性;
  4. 灵活性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”,具体过程如下:

  1. 初始状态:顺序表的基地址指向a0a0elems[0]a1elems[1]a2elems[2]length=3
  2. 元素后移:要插入a3elems[1],需先将a1及后续的a2向后移动 1 位 ——必须从最后一个元素(a2,下标2)开始后移,否则a1会先覆盖a2导致数据丢失。移动后a2elems[3]a1elems[2]elems[1]空出;
  3. 插入新元素:将a3赋值到空出的elems[1]
  4. 更新长度:顺序表长度从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-1i超出有效范围(若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=100length=100);
  • 输入:插入位置i=50,元素e=999
  • 预期结果:插入失败(length=MAX_SIZE);
  • 实际结果:打印 “插入失败!”—— 验证空间合法性检查生效。

(4)关键注意事项

  1. 后移方向必须 “从后往前”

    若从插入位置i开始 “从前往后” 移动(如j从i开始),会导致elems[i]先覆盖elems[i+1],后续元素数据丢失(例如a1覆盖a2a2永久丢失),这是插入算法的核心易错点。

  2. 防御性检查不可省略

    若跳过 “位置合法性” 或 “空间满” 的检查,会导致数组越界(程序崩溃)或插入到无效位置(数据混乱),必须优先处理非法场景。

  3. 插入后必须更新length

    L.length记录顺序表的有效元素个数,若插入后不递增,后续的listPrintlistDelete等操作会 “忽略” 新插入的元素,导致逻辑错误。

(5)小结

顺序表插入元素的核心是 “先防御检查→再后移元素→最后插入更新”,其本质是利用顺序表 “存储连续” 的特性,通过元素后移腾位实现指定位置插入。虽然插入操作的时间复杂度为O(n)(最坏情况需移动所有元素),但它是顺序表的基础接口,也是理解 “线性表操作逻辑” 的关键。后续在实际项目中(如课件提到的 “高并发 WEB 服务器连接管理”),若需在固定位置插入数据,可基于此算法扩展实现。

10.2.4 顺序表删除元素

(1)顺序表删除元素的核心原理

顺序表的本质是 “逻辑相邻则物理相邻” 的线性结构,删除元素后需保证剩余元素依然连续(无空洞)。根据删除位置的不同,可分为两种核心场景:

①场景 1:删除 “最后一个元素”(边界场景)

若要删除的元素是顺序表的最后一个(即下标 i = length - 1length 为当前有效元素个数),由于其后续无其他元素,无需移动任何数据 —— 只需将 length 直接减 1,即可表示该元素不再属于 “有效元素范围”。

示例:现有顺序表 length = 5(元素下标 0~4),删除下标 4 的最后一个元素:

  • 直接执行 length = 4,此时有效元素范围变为 0~3,原下标 4 的元素不再被视为有效,无需移动其他元素。
②场景 2:删除 “非最后一个元素”(普通场景)

若删除的是中间或开头的元素(i < length - 1),由于后续元素会形成 “空洞”,需将删除位置之后的所有元素依次向前移动 1 位,覆盖空洞后再更新 length

示例:现有顺序表 length = 5(元素下标 0~4),删除下标 1 的元素:

  1. 从下标 1 开始,将下标 2 的元素移到下标 1、下标 3 的元素移到下标 2、下标 4 的元素移到下标 3;
  2. 移动完成后,执行 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=0L.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)常见问题与注意事项

  1. 忘记合法性检查导致越界:若跳过 i >= L.length 的检查,当 i 超出有效范围时,循环移动元素会访问 L.elems[j+1](如 j=L.length 时,j+1 超出数组容量),导致内存越界错误。
  2. 循环范围错误:移动元素时,循环条件需为 j < L.length - 1(而非 j <= L.length - 1)。若写成 j <= L.length - 1,则 j=L.length-1 时,j+1=L.length 会访问无效内存。
  3. 删除后未更新 length:若只移动元素但忘记 L.length--,会导致 “有效元素个数未减少”,后续操作(如遍历)会误将已 “删除” 的元素视为有效。
  4. 顺序表为空的特殊处理:当 L.length=0 时,i=0 也属于 i >= L.length(0>=0),会被合法性检查拦截,无需额外写判断逻辑,代码更简洁。

(5)总结

顺序表删除元素的核心是 “连续存储无空洞”,需根据删除位置分场景处理:

  • 删最后一个元素:直接减 length(高效,时间复杂度 O (1));
  • 删非最后一个元素:移动后续元素(时间复杂度 O (n),n 为有效元素个数)。

其中,合法性检查是避免内存越界的关键,而 “边界场景测试”(空表、删最后一个元素)是验证代码正确性的必要步骤。掌握这一操作,能更深入理解顺序表 “连续存储” 的特性与局限性。

10.2.5 顺序表销毁

(1)销毁顺序表的核心意义

在 C++ 中,顺序表的存储空间是通过new动态分配的(如初始化函数initListL.elems = new int[MAX_SIZE];),而 C++ 没有自动垃圾回收机制。若顺序表使用完毕后不主动释放内存,会导致内存泄漏—— 即动态分配的内存始终被占用,无法被系统回收,长期运行(如服务器程序)会逐渐耗尽内存,最终导致程序崩溃。

因此,“销毁顺序表” 的核心目的是:释放顺序表动态分配的存储空间,重置表的属性,标志顺序表生命周期的结束,避免内存泄漏和后续误操作

(2)销毁顺序表的算法逻辑与核心步骤

销毁顺序表需遵循 “先释放内存,再重置属性” 的逻辑,确保资源完全回收且表状态清晰,具体步骤如下:

  1. 判断内存是否有效:检查顺序表的基地址elems是否指向有效内存(非NULL),避免对空指针重复释放。
  2. 释放动态内存:若elems非空,使用delete[]释放其指向的数组内存(因elemsnew[]分配,需匹配delete[])。
  3. 重置表属性:将顺序表的有效长度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指向的内存、lengthsize),引用传递可直接操作原对象,避免值传递的 “拷贝开销” 和 “无法修改原对象” 的问题。若用值传递(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)销毁操作的注意事项

  1. 避免重复销毁

    顺序表销毁后,elems会变成野指针(内存已释放,但指针地址未置空),若再次调用destroyListif (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;
    }
    
  2. 销毁前确保表已初始化

    若对未初始化的SqListelems未分配内存,可能是随机值)调用destroyListif (L.elems)可能误判为非空,执行delete[]会释放非法内存,导致程序崩溃。因此需确保destroyList仅在initList成功后调用。

  3. 配合顺序表生命周期使用

    顺序表的生命周期应遵循 “初始化(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);					/*释放结点*/

参考资料来源:

奇牛学院

posted @ 2023-06-13 13:13  CodeMagicianT  阅读(50)  评论(0)    收藏  举报