mirror of
https://github.com/happyflyer/wangdao-data-structure.git
synced 2026-04-30 13:42:14 +08:00
完成双链表、循环链表、静态链表
This commit is contained in:
@@ -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
141
ch2/circular-link/README.md
Normal 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;
|
||||
}
|
||||
```
|
||||
67
ch2/circular-link/double.cpp
Normal file
67
ch2/circular-link/double.cpp
Normal 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;
|
||||
}
|
||||
39
ch2/circular-link/single.cpp
Normal file
39
ch2/circular-link/single.cpp
Normal 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
109
ch2/double-link/README.md
Normal 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
128
ch2/double-link/with.cpp
Normal 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
101
ch2/static-link/README.md
Normal 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
24
ch2/static-link/with.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user