# 线性表

## 1. 定义

线性表（Linear List）是具有相同数据类型的 $n$ $(n \geq 0)$ 个数据元素的有序序列。

- **相同数据类型**
- **有序**
- **序列**

其中 $n$ 为**表长**。当 $n=0$ 时线性表是一个**空表**。

$$
L=(\alpha_1,\alpha_2,...,\alpha_i,\alpha_{i+1},...,\alpha_n)
$$

- $\alpha_i$ 是线性表中的 “第 $i$ 个” 元素线性表中的**位序**。
- $\alpha_1$ 是**表头元素**，$\alpha_n$ 是**表尾元素**。
- 出第一个元素外，每个元素有且仅有一个**直接前驱**；除最后一个元素外，每个元素有且仅有一个**直接后继**。

> 注意：位序是从 $1$ 开始的，而数组下标是从 $0$ 开始的。

## 2. 基本操作

- `InitList(&L)`：初始化表。构造一个空的线性表 $L$，分配内存空间。
- `DestroyList(&L)`：销毁操作。销毁线性表，并释放线性表 $L$ 所占用的内存。
- `ListInsert(&L,i,e)`：插入操作。在表 $L$ 中的第 $i$ 个位置插入指定元素 $e$。
- `ListDelete(&L,i,&e)`：插入操作。删除表 $L$ 中的第 $i$ 个位置的元素，并用 $e$ 返回删除元素的值。
- `GetElem(L,i)`：按位查找操作。获取表 $L$ 中的第 $i$ 个位置的元素的值。
- `LocateElem(L,e)`：按值查找操作。在表 $L$ 中查找查找具有给行关键字值的元素。
- `Length(L)`：求表长。返回线性表 $L$ 的长度，即 $L$ 中所有元素的个数。
- `PrintList(L)`：输出操作。按前后顺粗输出线性表 $L$ 的所有元素值。
- `Empty(L)`：判空操作。若 $L$ 为空表，则返回 `true`，否则返回 `false`。

Tips：

- 对数据的操作（记忆思路）：创销、增删改查。
- C 语言函数的定义：<返回值类型> 函数名(<参数 1 类型> 参数 1, <参数 2 类型> 参数 2, ...)。
- 实际开发中，可根据实际需求定义其他的基本操作。
- 函数名和参数的形式、命名都可改变（参考：严蔚敏《数据结构》）。（可读性）
- 什么时候要传入引用 `&`：对参数的修改结果需要 “带回来”。（C++ 语法）

为什么要实现对数据结构的基本操作：

- 团队合作编程，你定义的数据结构要让别人能够很方便的使用（封装）。
- 将常用的操作、运算封装成函数，避免重复工作，降低出错风险。

## 3. 按照存储/物理结构分类

- 顺序表（顺序存储）
- 链表（链式存储）
  - 单链表
  - 双链表
  - 循环链表
  - 静态链表

## 4. [顺序表](sequence/README.md)

- 优点：可随机存取，存储密度高。
- 缺点：要求大片连续空间，改变容量不方便。

## 5. [单链表](single-link/README.md)

- 缺点：无法逆向检索，有时候不太方便。

## 6. [双链表](double-link/README.md)

### 6.1. 初始化

头结点 `prior`、`next` 都指向 `NULL`。

### 6.2. 插入（后插）

- 注意新插入结点、前驱结点、后继结点的指针修改。
- 边界情况：新插入结点在最后一个位置，需特殊处理。

### 6.3. 删除（后删）

- 注意删除结点的前驱结点、后继结点的指针修改。
- 边界情况：如果被删除结点是最后一个数据结点，需特殊处理。

### 6.4. 遍历

- 从一个给定结点开始，后向遍历、前向遍历的实现（循环的终止条件）。
- 双链表不可随机存取，按位查找、按值查找都只能用遍历的方式实现。

## 7. [循环链表](circular-link/README.md)

- 如何判空
- 如何判断结点 `p` 是否是表尾、表头结点
- 如何在表头、表中、表尾插入、删除一个结点

## 8. [静态链表](static-link/README.md)

用数组的方式实现的链表。

- 优点：增、删操作不需要大量移动元素。
- 缺点：不能随机存取，只能从头结点开始一次往后查找，容量固定不可变。

适用场景：

- 不支持指针的低级语言。
- 数据元素数量固定不变的场景（如操作系统的文件分配表 FAT）。

## 9. 顺序表 VS 链表

三要素：

1. 逻辑结构
2. 物理结构/存储结构
3. 数据的运算/基本操作

|          | 顺序表   | 链表         |
| -------- | -------- | ------------ |
| 随机存取 | **支持** | 不支持       |
| 存储密度 | **高**   | 不高         |
| 存储空间 | 大片连续 | **小且离散** |
| 改变容量 | 麻烦     | **方便**     |

基本操作

- **创**
- 销
- **增**
- **删**
- 改
- **查**

### 9.1. 创建

- 顺序表：需要预分配大片联系空间。若分配空间过小，则之后不方便扩展容量；若分配空间过大，则浪费内存资源。
  - 静态分配，静态数据，容量不可改变。
  - 动态分配，动态数据（`malloc`，`free`），容量可以改变，但需要移动大量元素，时间代价高。
- **链表**：只需要分配一个头结点（也可以不要头结点，只声明一个头指针），之后方便扩展。

### 9.2. 销毁

- 顺序表：修改 `Length=0`。
  - 静态分配，静态数组，系统自动回收空间。
  - 动态分配，动态数组，需要手动 `free`。（属于内存模型的堆区）
- 链表：依次删除各个结点（`free`）。

成对出现：

```cpp
L.data = (ElemType *)malloc(sizeof(ElemType * InitSize));
```

```cpp
free(L.data);
```

### 9.3. 增、删

- 顺序表：插入/删除元素要将后续元素都后移/前移。时间复杂度为 $O(n)$，时间开销主要来自移动元素。
- **链表**：插入/删除元素只需要修改指针即可。时间复杂度为 $O(n)$，时间开销主要来自查找目标元素。

> 若数据元素很大，则移动的时间代价很高；查找元素的时间代价更低。

### 9.4. 查找

- **顺序表**
  - 按位查找：时间复杂度为 $O(1)$。
  - 按值查找：时间复杂度为 $O(n)$，若表内元素有序，则时间复杂度为 $O(log_2n)$。
- 链表：
  - 按位查找：时间复杂度为 $O(n)$。
  - 按值查找：时间复杂度为 $O(n)$。

### 9.5. 总结

|                | 顺序表 | 链表 |
| -------------- | ------ | ---- |
| 弹性（可扩容） | ×      | √    |
| 增、删         | ×      | √    |
| 查             | √      | ×    |

根据场景选择顺序表或链表：

- 表长难于预估、经常要增加/删除元素：链表
- 表长可预估、查询（搜索）操作较多：顺序表

**问题**：

请描述顺序表和链表的 bla bla bla

实现线性表时，用顺序表还是链表好？

**答题思路**：

顺序表和链表的逻辑结构都是线性结构，都属于线性表。

但是二者的存储结构不同，顺序表采用顺序存储...（特点，带来的优点缺点）：链表采用链式存储...（特点、导致的优缺点）。

由于采用不同的存储方式实现，因此基本操作的实现效率也不同。当初始化时...当插入一个数据元素时...；当删除一个数据元素时...；当查找一合数据元素时...
