数据结构
1 线性表
1.1 线性表的定义
-
通俗一点来说,线性表就是指各个数据元素它们之间的逻辑关系、逻辑结构是这种一条线的结构,就是被穿到一起,数据元素之间有这样的前后关系。
-
严谨的定义:线性表是具有相同数据类型 的n(n>=0)个数据元素的有限序列,其中n为表长,当n等于0时,线性表是一个空表。若用L命名线性表,则其一般表示为
L=(a1,a2,...,ai,ai+1,...,an)
-
ai是线性表中的"第i个"元素线性表中的位序,位序从1开始,数组下标从0开始
-
a1是表头元素,an是表尾元素
-
除第一个元素外,每个元素有且仅有一个直接前驱,除最后一个元素外,每个元素有且仅有一个直接后继。
1.2 线性表的基本操作
InitList(&L):初始化表。构造一个空的线性表L,分配内存空间
DestoryList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间
ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e.
ListDelete(&L,i,&e):删除操作,删除表L中第i个位置的元素,并用e返回删除元素的值
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值
其他常用操作:
Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
Empty(L):判空操作。若L为空表,则返回true,否则返回false。
注意:
- 对数据的操作(记忆思路)------创销、增删改查。
- C语言函数的定义------<返回值类型>函数名(<参数1类型>参数1,<参数2类型>参数2,...)
- 什么时候要传入引用"&"------对参数的修改结果需要"带回来"。
总结:

1.3 顺序表的定义
- 顺序表------用顺序存储的方式来实现的线性表

1.4 顺序表的实现
- 顺序存储------把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中。元素之间的关系由存储单元的邻接关系来体现。

1.4.1 顺序表的实现------静态分配

1.4.2 顺序表的实现------动态分配
为了让顺序表的大小可变,可以利用动态分配的实现方式
- malloc和free
-
动态申请空间用malloc,释放内存空间用free
-
L.data=(ElemType*) malloc(sizeof(ElemType) *InitSize);
int*arr=(int *)malloc(sizeof(int) *10)
-
malloc(size) → 向系统申请 size 字节的连续内存,返回这块内存的起始地址( void* 类型),需要强转为指定类型
-
用完必须用free,否则内存泄漏
-
malloc申请的总空间大小=单个元素的大小*元素的最大数量
-
malloc、free函数的头文件:#include<stdlib.h>

练习:

C++
#include <stdio.h> // 加了printf需要的头文件
#include <stdlib.h> // malloc/free的头文件
#define InitSize 10 // 默认最大长度,初始化顺序表
// 动态顺序表结构体
typedef struct {
int *data; // 动态数组指针
int MaxSize; // 最大容量
int length; // 当前长度
} SeqList;
// ✅ 修正:C语言用指针传参,加了malloc空判断
void InitList(SeqList *L) {
// 申请初始内存
L->data = (int *)malloc(InitSize * sizeof(int));
if (L->data == NULL) { // 必须判断!防止申请失败崩溃
printf("初始化失败:内存不足!\n");
exit(1);//程序异常退出
}
L->length = 0;
L->MaxSize = InitSize;
printf("顺序表初始化完成,初始容量:%d\n", L->MaxSize);
}
// ✅ 修正:指针传参+空判断+安全扩容
void IncreaseSize(SeqList *L, int len) {
if (len <= 0) { // 防止无效扩容
printf("扩容长度必须大于0!\n");
return;
}
int *p = L->data; // 保存旧数组地址
// 申请更大的内存
int newSize = L->MaxSize + len;
L->data = (int *)malloc(newSize * sizeof(int));
if (L->data == NULL) { // 申请失败,恢复原指针,不崩溃
printf("扩容失败:内存不足!\n");
L->data = p; // 把原指针还回去,不丢数据
return;
}
// 复制旧数据到新数组
for (int i = 0; i < L->length; i++) {
L->data[i] = p[i];
}
free(p); // 释放旧内存
L->MaxSize = newSize; // 更新最大容量
printf("顺序表扩容完成,新容量:%d\n", L->MaxSize);
}
// ✅ 新增:插入元素函数
bool ListInsert(SeqList *L, int pos, int val) {
// 位置合法性判断(1~length+1)
if (pos < 1 || pos > L->length + 1) {
printf("插入位置不合法!\n");
return false;
}
// 容量满了就扩容(这里自动扩5,也可以手动调)
if (L->length >= L->MaxSize) {
IncreaseSize(L, 5);
}
// 从后往前移动元素,给新元素腾位置
for (int i = L->length; i >= pos; i--) {
L->data[i] = L->data[i - 1];
}
L->data[pos - 1] = val; // 插入新元素
L->length++;
return true;
}
// ✅ 新增:打印顺序表函数(看运行结果)
void PrintList(SeqList *L) {
printf("当前顺序表元素:");
for (int i = 0; i < L->length; i++) {
printf("%d ", L->data[i]);
}
printf("\n当前长度:%d,最大容量:%d\n", L->length, L->MaxSize);
}
int main() {
SeqList L; // 声明顺序表
InitList(&L); // 初始化(传指针)
// 插入元素(补全原代码的注释)
ListInsert(&L, 1, 10);
ListInsert(&L, 2, 20);
ListInsert(&L, 3, 30);
PrintList(&L);
// 手动扩容5
IncreaseSize(&L, 5);
PrintList(&L);
// 继续插入,触发自动扩容
for (int i = 4; i <= 15; i++) {
ListInsert(&L, i, i * 10);
}
PrintList(&L);
// ✅ 规范:手动释放内存
free(L.data);
L.data = NULL;
return 0;
}
效果图:

1.5 顺序表的特点
- 随机访问,即可以在O(1)时间内找到第i个元素,顺序表当中的各个元素的存放位置是连续存放的,只要知道第一个元素的存放地址,那么后面这些元素的存放地址就可以马上算出来。比如数组,只要给一个数组的下标就可以直接找到后面的元素
- 存储密度高,每个节点只存储数据元素本身,如果是链式存储,除了本身还要耗费一定的空间存储指针
- 拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高,数据要复制到新区域)
- 插入、删除操作不方便,需要移动大量元素

总结:

1.6 顺序表的插入和删除
1.6.1 插入操作
ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e.

注意点:
- 代码中的int j=L.length。类似于int i等于零,一个从0往后加,一个从length往前减,逐步把前面的数移到后一个位置
1.6.2 插入操作的时间复杂度

1.6.3 删除操作
- 我们在进行删除的操作时,把元素依次往前移动一位,先移动前面的元素,再移动后面的元素,而当我们进行插入操作时,需要把元素依次往后移动一位,先移动后面的再去移动前面的
- int e=-1是为了给e一个无效初始值,方便判断删除是否成功,这是一个不可能出现在正常顺序表元素里的「标记值」(假设顺序表存的都是正整数),如果删除失败,函数不会修改 e , e 就还是 -1 ,你一眼就能看出来,如果删除成功,函数会把被删元素的值赋给e,就变成了被删的那个数(比如 3 )
- 元素前移逻辑:从第i个位置后的元素依次向前覆盖,填补被删元素的空位

1.6.4 删除操作的时间复杂度

总结:

1.7 顺序表的查找
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素
1.7.1 静态分配数组的按位查找

注意:
-
第i个元素所对应的数组下标为i-1,位序从1开始,而数组下标从0开始
-
如果想让代码健壮性更强,可以加一个判断i的值是否合法
if (i < 1 || i > L->length + 1) {
printf("插入位置不合法!\n");
return false;
}
1.7.2 动态分配数组的按位查找

注意:
- data这个变量是个指针,指向顺序表中的第一个数据元素,也就是malloc函数申请的一整片的连续内存空间的起始地址
1.7.3 按位查找的时间复杂度

-
时间复杂度O(1)的意思是:不管数据量n有多大,执行这个操作需要的时间都是固定的,不会随着n变大而变长。
-
按位查找的时候:
-
不需要从头遍历数组(那是O(n)的做法)
-
只需要做一次简单的地址计算,然后直接访问对应内存位置
-
不管顺序表里有10个元素还是10000个元素,这个步骤都只做1次,时间完全一样
-
1.7.4 动态分配数组的按值查找

- 如果是结构类型如何查找,比较是否相等?

1.7.5 按值查找的时间复杂度

总结:
