diff --git a/ch2/README.md b/ch2/README.md index 405b726..d73ff8e 100644 --- a/ch2/README.md +++ b/ch2/README.md @@ -63,8 +63,41 @@ Tips: - 缺点:无法逆向检索,有时候不太方便。 -## 6. 双链表 +## 6. [双链表](double-link/README.md) -## 7. 循环链表 +### 6.1. 初始化 -## 8. 静态链表 +头结点 `prior`、`next` 都指向 `NULL`。 + +### 6.2. 插入(后插) + +- 注意新插入结点、前驱结点、后继结点的指针修改。 +- 边界情况:新插入结点在最后一个位置,需特殊处理。 + +### 6.3. 删除(后删) + +- 注意删除结点的前驱结点、后继结点的指针修改。 +- 边界情况:如果被删除结点是最后一个数据结点,需特殊处理。 + +### 6.4. 遍历 + +- 从一个给定结点开始,后向遍历、前向遍历的实现(循环的终止条件)。 +- 双链表不可随机存取,按位查找、按值查找都只能用遍历的方式实现。 + +## 7. [循环链表](circular-link/README.md) + +- 如何判空 +- 如何判断结点 `p` 是否是表尾、表头结点 +- 如何在表头、表中、表尾插入、删除一个结点 + +## 8. [静态链表](static-link/README.md) + +用数组的方式实现的链表。 + +- 优点:增、删操作不需要大量移动元素。 +- 缺点:不能随机存取,只能从头结点开始一次往后查找,容量固定不可变。 + +适用场景: + +- 不支持指针的低级语言。 +- 数据元素数量固定不变的场景(如操作系统的文件分配表 FAT)。 diff --git a/ch2/circular-link/README.md b/ch2/circular-link/README.md new file mode 100644 index 0000000..b0b7be8 --- /dev/null +++ b/ch2/circular-link/README.md @@ -0,0 +1,141 @@ +# 循环链表 + +## 1. 循环单链表 + +### 1.1. 定义 + +- 单链表:表尾结点的 `next` 指针指向 `NULL`。 +- 循环单链表:表尾结点的 `next` 指针指向头结点。 + +```cpp +typedef struct LNode +{ + int data; + struct LNode *next; +} LNode, *LinkList; +``` + +```cpp +// 初始化一个循环单链表,带头结点,头结点不存储数据 +bool InitList(LinkList &L) +{ + // 分配一个头结点 + L = (LNode *)malloc(sizeof(LNode)); + if (L == NULL) + { + return false; + } + L->next = L; + return true; +} +``` + +```cpp +// 判断循环单链表是否为空 +bool Empty(LinkList L) +{ + return L->next == L; +} +``` + +```cpp +// 判断 p 结点是否为表尾结点 +bool isTail(LinkList L, LNode *p) +{ + return p->next == L; +} +``` + +### 1.2. 特性 + +- 单链表:从一个结点出发,只能找到后续的各个结点。 +- 循环单链表:从一个结点出发,可以找到其他任何一个结点。 + +如果经常操作循环单链表的头部或尾部,可以让 `L` 指向表尾结点。 + +- 单链表和循环单链表:从头结点找到尾部,时间复杂度为 $O(n)$。 +- 循环单链表:从尾部找到头部,时间复杂度为 $O(1)$。 + +> 插入、删除时需要修改 L。 + +## 2. 循环双链表 + +### 2.1. 定义 + +- 双链表:表头结点的 `prior` 指向 `NULL`,表尾结点的 `next` 指向 `NULL`。 +- 循环双链表:表头结点的 `prior` 指向表尾结点,表尾结点的 `next` 指向表头结点。 + +```cpp +typedef struct DNode +{ + int data; + struct DNode *prior, *next; +} DNode, *DLinkList; +``` + +```cpp +// 初始化一个循环双链表,带头结点 +bool InitDLinkList(DLinkList &L) +{ + L = (DNode *)malloc(sizeof(DNode)); + if (L == NULL) + { + return false; + } + L->prior = L; + L->next = L; + return true; +} +``` + +```cpp +// 判断双链表是否为空 +bool Empty(DLinkList L) +{ + return L->next == L; +} +``` + +```cpp +// 判断 p 结点是否为表尾结点 +bool isTail(DLinkList L, DNode *p) +{ + return p->next == L; +} +``` + +### 2.2. 后插 + +```cpp +// 后插操作:在 p 结点之后插入 s 结点 +bool InsertNextDNode(DNode *p, DNode *s) +{ + if (p == NULL || s == NULL) + { + return false; + } + s->next = p->next; + p->next->prior = s; + s->prior = p; + p->next = s; + return true; +} +``` + +### 2.3. 后删 + +```cpp +// 删除操作:删除 p 结点的后继结点 q +bool DeleteNextDNode(DLinkList L, DNode *p) +{ + if (p == NULL || isTail(L, p)) + { + return false; + } + DNode *q = p->next; + p->next = q->next; + q->next->prior = p; + free(q); + return true; +} +``` diff --git a/ch2/circular-link/double.cpp b/ch2/circular-link/double.cpp new file mode 100644 index 0000000..cc29ea7 --- /dev/null +++ b/ch2/circular-link/double.cpp @@ -0,0 +1,67 @@ +#include +#include +typedef struct DNode +{ + int data; + struct DNode *prior, *next; +} DNode, *DLinkList; + +// 初始化一个循环双链表,带头结点 +bool InitDLinkList(DLinkList &L) +{ + L = (DNode *)malloc(sizeof(DNode)); + if (L == NULL) + { + return false; + } + L->prior = L; + L->next = L; + return true; +} + +// 判断双链表是否为空 +bool Empty(DLinkList L) +{ + return L->next == L; +} + +// 判断 p 结点是否为表尾结点 +bool isTail(DLinkList L, DNode *p) +{ + return p->next == L; +} + +// 后插操作:在 p 结点之后插入 s 结点 +bool InsertNextDNode(DNode *p, DNode *s) +{ + if (p == NULL || s == NULL) + { + return false; + } + s->next = p->next; + p->next->prior = s; + s->prior = p; + p->next = s; + return true; +} + +// 删除操作:删除 p 结点的后继结点 q +bool DeleteNextDNode(DLinkList L, DNode *p) +{ + if (p == NULL || isTail(L, p)) + { + return false; + } + DNode *q = p->next; + p->next = q->next; + q->next->prior = p; + free(q); + return true; +} + +int main() +{ + DLinkList L; + InitDLinkList(L); + return 0; +} diff --git a/ch2/circular-link/single.cpp b/ch2/circular-link/single.cpp new file mode 100644 index 0000000..cfe30ac --- /dev/null +++ b/ch2/circular-link/single.cpp @@ -0,0 +1,39 @@ +#include +#include +typedef struct LNode +{ + int data; + struct LNode *next; +} LNode, *LinkList; + +// 初始化一个循环单链表,带头结点,头结点不存储数据 +bool InitList(LinkList &L) +{ + // 分配一个头结点 + L = (LNode *)malloc(sizeof(LNode)); + if (L == NULL) + { + return false; + } + L->next = L; + return true; +} + +// 判断循环单链表是否为空 +bool Empty(LinkList L) +{ + return L->next == L; +} + +// 判断 p 结点是否为表尾结点 +bool isTail(LinkList L, LNode *p) +{ + return p->next == L; +} + +int main() +{ + LinkList L; + InitList(L); + return 0; +} diff --git a/ch2/double-link/README.md b/ch2/double-link/README.md new file mode 100644 index 0000000..2e141ce --- /dev/null +++ b/ch2/double-link/README.md @@ -0,0 +1,109 @@ +# 双链表 + +```cpp +typedef struct DNode +{ + ElemType data; + struct DNode *prior, *next; +} DNode, *DLinkList; +``` + +## 1. 初始化 + +```cpp +// 初始化一个双链表,带头结点 +bool InitDLinkList(DLinkList &L) +{ + L = (DNode *)malloc(sizeof(DNode)); + if (L == NULL) + { + return false; + } + L->prior = NULL; // 头结点的 prior 永远指向 NULL + L->next = NULL; + return true; +} +``` + +```cpp +// 判断双链表是否为空 +bool Empty(DLinkList L) +{ + return L->next == NULL; +} +``` + +## 2. 插入 + +```cpp +// 后插操作:在 p 结点之后插入 s 结点 +bool InsertNextDNode(DNode *p, DNode *s) +{ + if (p == NULL || s == NULL) + { + return false; + } + s->next = p->next; + if (p->next != NULL) // 如果 p 结点不是表尾结点 + { + p->next->prior = s; + } + s->prior = p; + p->next = s; +} +``` + +## 3. 删除 + +```cpp +// 删除操作:删除 p 结点的后继结点 q +bool DeleteNextDNode(DNode *p) +{ + if (p == NULL) + { + return false; + } + DNode *q = p->next; + if (q == NULL) // p 结点没有后继结点 + { + return false; + } + p->next = q->next; + if (q->next != NULL) // q 结点不是表尾结点 + { + q->next->prior = p; + } + free(q); + return true; +} +``` + +## 4. 遍历 + +```cpp +while (p != NULL) // 后向遍历 +{ + printf("%d\n", p->data); + p = p->next; +} +``` + +```cpp +while (p != NULL) // 前向遍历 +{ + printf("%d\n", p->data); + p = p->prior; +} +``` + +```cpp +while (p->prior != NULL) // 前向遍历,跳过头结点 +{ + printf("%d\n", p->data); + p = p->prior; +} +``` + +双链表不可随机存取,按位查找、按值查找都智能用遍历的方式实现。 + +- 时间复杂度 $O(n)$ diff --git a/ch2/double-link/with.cpp b/ch2/double-link/with.cpp new file mode 100644 index 0000000..5449dfd --- /dev/null +++ b/ch2/double-link/with.cpp @@ -0,0 +1,128 @@ +#include +#include +typedef struct DNode +{ + int data; + struct DNode *prior, *next; +} DNode, *DLinkList; + +// 初始化一个双链表,带头结点 +bool InitDLinkList(DLinkList &L) +{ + L = (DNode *)malloc(sizeof(DNode)); + if (L == NULL) + { + return false; + } + L->prior = NULL; // 头结点的 prior 永远指向 NULL + L->next = NULL; + return true; +} + +// 判断双链表是否为空 +bool Empty(DLinkList L) +{ + return L->next == NULL; +} + +// 后插操作:在 p 结点之后插入 s 结点 +bool InsertNextDNode(DNode *p, DNode *s) +{ + if (p == NULL || s == NULL) + { + return false; + } + s->next = p->next; + if (p->next != NULL) // 如果 p 结点不是表尾结点 + { + p->next->prior = s; + } + s->prior = p; + p->next = s; + return true; +} + +// 删除操作:删除 p 结点的后继结点 q +bool DeleteNextDNode(DNode *p) +{ + if (p == NULL) + { + return false; + } + DNode *q = p->next; + if (q == NULL) // p 结点没有后继结点 + { + return false; + } + p->next = q->next; + if (q->next != NULL) // q 结点不是表尾结点 + { + q->next->prior = p; + } + free(q); + return true; +} + +// 销毁双链表 +void DestroyList(DLinkList &L) +{ + while (L->next != NULL) + { + DeleteNextDNode(L); + } + free(L); // 释放头结点 + L = NULL; // 头指针指向 NULL +} + +// 创建一个双链表的结点 +DNode *CreateDNode(int e) +{ + DNode *s = (DNode *)malloc(sizeof(DNode)); + s->data = e; + return s; +} + +// 获取一个指向双链表的表尾结点的指针 +DNode *GetLastDNode(DLinkList L) +{ + DNode *p = L->next; + while (p->next != NULL) + { + p = p->next; + } + return p; +} + +int main() +{ + DLinkList L; + InitDLinkList(L); + // 测试插入操作 + InsertNextDNode(L, CreateDNode(1)); + InsertNextDNode(L, CreateDNode(2)); + InsertNextDNode(L, CreateDNode(3)); + InsertNextDNode(L, CreateDNode(4)); + InsertNextDNode(L, CreateDNode(5)); + // 打印操作 + DNode *p = L->next; + while (p != NULL) // 后向遍历 + { + printf("%d\n", p->data); + p = p->next; + } + // 打印操作 + p = GetLastDNode(L); + while (p != NULL) // 前向遍历 + { + printf("%d\n", p->data); + p = p->prior; + } + // 打印操作 + p = GetLastDNode(L); + while (p->prior != NULL) // 前向遍历,跳过头结点 + { + printf("%d\n", p->data); + p = p->prior; + } + return 0; +} diff --git a/ch2/static-link/README.md b/ch2/static-link/README.md new file mode 100644 index 0000000..cb60e99 --- /dev/null +++ b/ch2/static-link/README.md @@ -0,0 +1,101 @@ +# 静态链表 + +## 1. 定义 + +- 单链表:各个结点在内存中星罗棋布、散落天涯。 +- 静态链表:分配一整片连续的内存空间,各个结点集中安置。 + +| 内存地址 | 数据域 | 游标域 | +| -------- | ------ | ------ | +| ... | ... | ... | +| 0 | 头 | 2 | +| 1 | $e_2$ | 6 | +| 2 | $e_1$ | 1 | +| 3 | $e_4$ | -1 | +| 4 | ... | ... | +| 5 | ... | ... | +| 6 | $e_3$ | 3 | +| 7 | ... | ... | +| 8 | ... | ... | +| 9 | ... | ... | +| ... | ... | ... | + +- 游标充当 “指针”。 +- 游标为 `-1` 表示已经到达表尾。 + +每个数据元素 $4B$,每个游标 $4B$(每个结点共 $8B$),设其实地址为 $addr$,则 $e_1$ 的存放地址为: + +$$ +addr + 8*2 +$$ + +```cpp +#define MaxSize 10 +typedef struct +{ + ElemType data; + int next; +} SNode; +``` + +高级写法: + +```cpp +typedef struct +{ + int data; + int next; +} SLinkList[MaxSize]; +``` + +```cpp +SLinkList a; +``` + +```cpp +struct Node +{ + int data; + int next; +}; +typedef struct Node SLinkList[MaxSize]; +``` + +```cpp +struct Node a[MaxSize]; +``` + +## 2. 初始化 + +```cpp +SLinkList a; +a[0]->next = -1; +for (int i = 1; i < a.length; i++) +{ + a[i].next = -2; +} +``` + +## 3. 查找 + +查找某个位序的结点,需要从头结点出发,挨个往后遍历结点。 + +- 时间复杂度 $O(n)$ + +区别: + +- 位序:各个结点在逻辑上的顺序。 +- 数组下标:各个结点在物理上的位置。 + +## 4. 插入 + +1. 找到一个空的结点,存入数据元素。 +2. 从头结点出发,找到位序为 $i-1$ 的结点。 +3. 修改新节点的 `next`。 +4. 修改 $i-1$ 结点的 `next`。 + +## 5. 删除 + +1. 从头结点出发找到前驱结点。 +2. 修改前驱结点的 `next`。 +3. 被删除结点的 `next` 设置为 `-2`。 diff --git a/ch2/static-link/with.cpp b/ch2/static-link/with.cpp new file mode 100644 index 0000000..0dacfcd --- /dev/null +++ b/ch2/static-link/with.cpp @@ -0,0 +1,24 @@ +#include +#include +#define MaxSize 10 +struct Node +{ + int data; + int next; +}; +typedef struct +{ + int data; + int next; +} SLinkList[MaxSize]; + +int main() +{ + struct Node x; + printf("sizeX=%d\n", sizeof(x)); + struct Node a[MaxSize]; + printf("sizeA=%d\n", sizeof(a)); + SLinkList b; + printf("sizeB=%d\n", sizeof(b)); + return 0; +}