完成双链表、循环链表、静态链表

This commit is contained in:
lifei
2020-11-30 20:53:00 +08:00
parent fc8d9fea74
commit 9511bbb4b2
8 changed files with 645 additions and 3 deletions

View File

@@ -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

141
ch2/circular-link/README.md Normal file
View File

@@ -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;
}
```

View File

@@ -0,0 +1,67 @@
#include <stdio.h>
#include <stdlib.h>
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;
}

View File

@@ -0,0 +1,39 @@
#include <stdio.h>
#include <stdlib.h>
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;
}

109
ch2/double-link/README.md Normal file
View File

@@ -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)$

128
ch2/double-link/with.cpp Normal file
View File

@@ -0,0 +1,128 @@
#include <stdio.h>
#include <stdlib.h>
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;
}

101
ch2/static-link/README.md Normal file
View File

@@ -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`

24
ch2/static-link/with.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include <stdio.h>
#include <stdlib.h>
#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;
}