Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[pull] main from itcharge:main #211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
pull merged 2 commits into AlgorithmAndLeetCode:main from itcharge:main
Aug 21, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
修复和优化语句表述
  • Loading branch information
itcharge committed Aug 21, 2025
commit 77c511a6a0db830e8a712d0404de57e14191dda0
338 changes: 122 additions & 216 deletions docs/01_array/01_15_array_two_pointers.md
View file Open in desktop

Large diffs are not rendered by default.

190 changes: 97 additions & 93 deletions docs/01_array/01_16_array_sliding_window.md
View file Open in desktop

Large diffs are not rendered by default.

391 changes: 173 additions & 218 deletions docs/02_linked_list/02_01_linked_list_basic.md
View file Open in desktop

Large diffs are not rendered by default.

56 changes: 31 additions & 25 deletions docs/02_linked_list/02_02_linked_list_sort.md
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -1,45 +1,51 @@
## 1. 链表排序简介

在数组排序中,常见的排序算法有:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序等
链表排序相比数组排序具有独特的挑战性。由于链表 **不支持随机访问**,只能通过 $next$ 指针顺序遍历,这使得某些排序算法在链表上的实现更加复杂

而对于链表排序而言,因为链表不支持随机访问,访问链表后面的节点只能依靠 `next` 指针从头部顺序遍历,所以相对于数组排序问题来说,链表排序问题会更加复杂一点。
### 1.1 算法适用性分析

下面先来总结一下适合链表排序与不适合链表排序的算法:
| 适用性 | 排序算法 | 说明 |
|--------|----------|------|
| **完全适合** | 冒泡排序、选择排序、插入排序、归并排序、快速排序 | 天然适合链表结构 |
| **需要额外空间** | 计数排序、桶排序、基数排序 | 需要辅助数组,但算法逻辑适合 |
| **不适合** | 希尔排序 | 依赖随机访问,链表无法高效实现 |
| **不推荐** | 堆排序 | 完全二叉树结构用链表存储效率低 |

- 适合链表的排序算法:**冒泡排序**、**选择排序**、**插入排序**、**归并排序**、**快速排序**、**计数排序**、**桶排序**、**基数排序**。
- 不适合链表的排序算法:**希尔排序**。
- 可以用于链表排序但不建议使用的排序算法:**堆排序**。
### 1.2 关键限制因素

> 希尔排序为什么不适合链表排序?
**希尔排序的限制**:希尔排序需要访问序列中第 $i + gap$ 个元素,链表无法直接跳转,必须从头遍历,时间复杂度从 $O(1)$ 退化为 $O(n)$。

**希尔排序**:希尔排序中经常涉及到对序列中第 $i + gap$ 的元素进行操作,其中 $gap$ 是希尔排序中当前的步长。而链表不支持随机访问的特性,导致这种操作不适合链表,因而希尔排序算法不适合进行链表排序
**堆排序的限制**:堆排序基于完全二叉树结构,数组可以通过下标 $O(1)$ 访问父子节点,而链表需要遍历查找,效率极低

> 为什么不建议使用堆排序?

**堆排序**:堆排序所使用的最大堆 / 最小堆结构本质上是一棵完全二叉树。而完全二叉树适合采用顺序存储结构(数组)。因为数组存储的完全二叉树可以很方便的通过下标序号来确定父亲节点和孩子节点,并且可以极大限度的节省存储空间。
## 2. 常见链表排序算法

而链表用在存储完全二叉树的时候,因为不支持随机访问的特性,导致其寻找子节点和父亲节点会比较耗时,如果增加指向父亲节点的变量,又会浪费大量存储空间。所以堆排序算法不适合进行链表排序
链表排序算法可分为 **比较排序** 和 **非比较排序** 两大类,每种算法都有其适用场景和性能特点

如果一定要对链表进行堆排序,则可以使用额外的数组空间表示堆结构。然后将链表中各个节点的值依次添加入堆结构中,对数组进行堆排序。排序后,再按照堆中元素顺序,依次建立链表节点,构建新的链表并返回新链表头节点。
### 2.1 比较排序算法

> 需要用到额外的辅助空间进行排序的算法
| 算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 特点 |
|------|----------|------------|------------|------|
| **冒泡排序** | 相邻节点比较交换 | $O(n^2)$ | $O(1)$ | 实现简单,适合小规模数据 |
| **选择排序** | 选择最小节点交换 | $O(n^2)$ | $O(1)$ | 交换次数少,适合交换成本高的场景 |
| **插入排序** | 逐个插入到有序序列 | $O(n^2)$ | $O(1)$ | 链表上表现优异,适合部分有序数据 |
| **归并排序** | 分治合并 | $O(n \log n)$ | $O(1)$ | 链表上的最优选择,稳定高效 |
| **快速排序** | 基准分割递归 | $O(n \log n)$ | $O(1)$ | 平均性能好,但最坏情况 $O(n^2)$ |

刚才我们说到如果一定要对链表进行堆排序,则需要使用额外的数组空间。除此之外,计数排序、桶排序、基数排序都需要用到额外的数组空间。
### 2.2 非比较排序算法

接下来,我们将对适合链表排序的 8 种算法进行一一讲解。当然,这些排序算法不用完全掌握,重点是掌握 **「链表插入排序」**、**「链表归并排序」** 这两种排序算法。
| 算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 适用条件 |
|------|----------|------------|------------|----------|
| **计数排序** | 统计频率重建 | $O(n + k)$ | $O(k)$ | 值域范围小,$k$ 为值域大小 |
| **桶排序** | 分桶排序合并 | $O(n)$ | $O(n + m)$ | 数据分布均匀,$m$ 为桶数 |
| **基数排序** | 按位分桶排序 | $O(n \times d)$ | $O(n + k)$ | 数字位数 $d$ 较小 |

## 2. 常见链表排序算法
### 2.3 算法选择建议

链表排序常用的方法有冒泡排序、选择排序、插入排序、归并排序、快速排序、计数排序、桶排序、基数排序。
- **小规模数据**:选择冒泡排序或插入排序
- **大规模数据**:优先考虑归并排序
- **特定场景**:根据数据特征选择相应的非比较排序

- **链表冒泡排序**:每次比较相邻两个节点的值,大的往后移。重复多次,直到链表有序。时间复杂度 $O(n^2),ドル空间复杂度 $O(1)$。
- **链表选择排序**:每次从未排序部分找到最小的节点,和当前节点交换。重复操作,直到链表有序。时间复杂度 $O(n^2),ドル空间复杂度 $O(1)$。
- **链表插入排序**:每次取一个节点,插入到已排序部分的合适位置。不断重复,直到全部节点有序。时间复杂度 $O(n^2),ドル空间复杂度 $O(1)$。
- **链表归并排序**:把链表分成两半,递归排序,再合并。适合链表,时间复杂度 O$(n \log n),ドル空间复杂度 $O(1)$。
- **链表快速排序**:选一个基准值,把小于基准的放左边,大于的放右边。对两边递归排序。时间复杂度 $O(n \log n),ドル空间复杂度 $O(1)$。
- **链表计数排序**:先找最大最小值,用数组统计每个值出现次数,再按顺序重建链表。适合值域不大时用。时间复杂度 $O(n + k),ドル空间复杂度 $O(k)$。
- **链表桶排序**:把节点分到不同的桶里,每个桶内单独排序,再合并所有桶。适合数据分布均匀时用。时间复杂度 $O(n),ドル空间复杂度 $O(n + m)$。
- **链表基数排序**:按个位、十位、百位等分多轮,把节点分到不同的桶里,再合并。适合数字位数不多时用。时间复杂度 $O(n \times k),ドル空间复杂度 $O(n + k)$。

## 参考资料

Expand Down
84 changes: 61 additions & 23 deletions docs/02_linked_list/02_03_linked_list_bubble_sort.md
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -1,29 +1,57 @@
## 1. 链表冒泡排序算法描述
## 1. 链表冒泡排序算法思想

1. 使用三个指针 `node_i`、`node_j` 和 `tail`。其中 `node_i` 用于控制外循环次数,循环次数为链节点个数(链表长度)。`node_j` 和 `tail` 用于控制内循环次数和循环结束位置。
2. 排序开始前,将 `node_i` 、`node_j` 置于头节点位置。`tail` 指向链表末尾,即 `None`。
3. 比较链表中相邻两个元素 `node_j.val` 与 `node_j.next.val` 的值大小,如果 `node_j.val > node_j.next.val`,则值相互交换。否则不发生交换。然后向右移动 `node_j` 指针,直到 `node_j.next == tail` 时停止。
4. 一次循环之后,将 `tail` 移动到 `node_j` 所在位置。相当于 `tail` 向左移动了一位。此时 `tail` 节点右侧为链表中最大的链节点。
5. 然后移动 `node_i` 节点,并将 `node_j` 置于头节点位置。然后重复第 3、4 步操作。
6. 直到 `node_i` 节点移动到链表末尾停止,排序结束。
7. 返回链表的头节点 `head`。
> **链表冒泡排序基本思想**:
>
> **通过相邻节点比较和交换,将最大值逐步「冒泡」到链表末尾**。

## 2. 链表冒泡排序算法实现代码
与数组冒泡排序类似,但需要处理链表的指针操作。

## 2. 链表冒泡排序算法步骤

链表冒泡排序的算法步骤如下:

1. **外层循环**:控制排序轮数,每轮将当前最大值「冒泡」到末尾
2. **内层循环**:比较相邻节点,必要时交换值
3. **尾指针优化**:每轮结束后,末尾已排序部分不再参与比较

```
初始状态:head → 4 → 2 → 1 → 3 → null
node_i, node_j

第1轮内循环:
- 比较 4 和 2:4 > 2,交换 → 2 → 4 → 1 → 3 → null
- 比较 4 和 1:4 > 1,交换 → 2 → 1 → 4 → 3 → null
- 比较 4 和 3:4 > 3,交换 → 2 → 1 → 3 → 4 → null

第1轮结束:tail 指向 4,4 已排好序
第2轮内循环:只在 2 → 1 → 3 范围内比较
...
```

## 3. 链表冒泡排序实现代码

```python
class Solution:
def bubbleSort(self, head: ListNode):
if not head or not head.next:
return head

# 外层循环:控制排序轮数
node_i = head
tail = None
# 外层循环次数为 链表节点个数
tail = None # 尾指针,右侧为已排序部分

while node_i:
node_j = head
node_j = head # 内层循环指针

# 内层循环:比较相邻节点
while node_j and node_j.next != tail:
if node_j.val > node_j.next.val:
# 交换两个节点的值
# 交换相邻节点的值
node_j.val, node_j.next.val = node_j.next.val, node_j.val
node_j = node_j.next
# 尾指针向前移动 1 位,此时尾指针右侧为排好序的链表

# 更新尾指针,右侧已排好序
tail = node_j
node_i = node_i.next

Expand All @@ -33,21 +61,31 @@ class Solution:
return self.bubbleSort(head)
```

## 3. 链表冒泡排序算法复杂度分析
## 4. 链表冒泡排序算法分析

- **时间复杂度**:$O(n^2)$。
- **空间复杂度**:$O(1)$。
| 指标 | 复杂度 | 说明 |
|------|--------|------|
| **最佳时间复杂度** | $O(n)$ | 链表已有序,配合"提前终止"优化仅需一趟 |
| **最坏时间复杂度** | $O(n^2)$ | 链表逆序,多轮遍历与相邻节点交换 |
| **平均时间复杂度** | $O(n^2)$ | 一般情况下需要二重循环比较交换 |
| **空间复杂度** | $O(1)$ | 原地排序,仅使用常数个指针变量 |
| **稳定性** | ✅ 稳定 | 相等元素的相对次序保持不变 |

## 4. 总结

链表冒泡排序使用三个指针进行操作。`node_i` 控制外层循环次数,`node_j` 和 `tail` 控制内层循环。每次比较相邻节点的值,需要交换时就交换。每次内循环结束后,最大的节点会移动到链表末尾。
**适用场景**:

这个算法的时间复杂度是 $O(n^2),ドル因为需要进行两层循环。空间复杂度是 $O(1),ドル因为只使用了固定数量的指针变量,没有使用额外空间。
- **小规模数据**:节点数量 < 100
- **教学演示**:理解排序算法原理
- **特殊要求**:需要稳定排序且空间受限

链表冒泡排序适合小规模数据排序。对于大规模数据,其他排序算法可能更高效。实现时需要注意指针移动和节点交换的操作。
## 5. 总结

## 练习题目
链表中的冒泡排序是最简单的链表排序之一,通过相邻节点比较交换实现排序。虽然实现简单,但效率较低。

**优点**:实现简单,稳定排序,空间复杂度低
**缺点**:时间复杂度高,交换次数多

- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md)(链表冒泡排序会超时,仅做练习)
## 练习题目

- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md) (链表冒泡排序会超时,仅做练习)
- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE)
71 changes: 53 additions & 18 deletions docs/02_linked_list/02_04_linked_list_selection_sort.md
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -1,46 +1,81 @@
## 1. 链表选择排序算法描述
## 1. 链表选择排序算法思想

1. 使用两个指针 `node_i`、`node_j`。`node_i` 既可以用于控制外循环次数,又可以作为当前未排序链表的第一个链节点位置。
2. 使用 `min_node` 记录当前未排序链表中值最小的链节点。
3. 每一趟排序开始时,先令 `min_node = node_i`(即暂时假设链表中 `node_i` 节点为值最小的节点,经过比较后再确定最小值节点位置)。
4. 然后依次比较未排序链表中 `node_j.val` 与 `min_node.val` 的值大小。如果 `node_j.val < min_node.val`,则更新 `min_node` 为 `node_j`。
5. 这一趟排序结束时,未排序链表中最小值节点为 `min_node`,如果 `node_i != min_node`,则将 `node_i` 与 `min_node` 值进行交换。如果 `node_i == min_node`,则不用交换。
6. 排序结束后,继续向右移动 `node_i`,重复上述步骤,在剩余未排序链表中寻找最小的链节点,并与 `node_i` 进行比较和交换,直到 `node_i == None` 或者 `node_i.next == None` 时,停止排序。
7. 返回链表的头节点 `head`。
> **链表选择排序基本思想**:
>
> 在未排序部分中找到最小元素,然后将其放到已排序部分的末尾。

## 2. 链表选择排序实现代码
## 2. 链表选择排序算法步骤

1. **初始化**:使用两个指针 `node_i` 和 `node_j`。`node_i` 指向当前未排序部分的第一个节点,同时也用于控制外循环。

2. **寻找最小值**:在未排序部分中,使用 `min_node` 记录值最小的节点。初始时假设 `node_i` 为最小值节点。

3. **比较交换**:遍历未排序部分,比较每个节点的值。如果发现更小的值,则更新 `min_node`。

4. **交换操作**:一趟排序结束后,如果 `min_node` 不等于 `node_i`,则交换两个节点的值。

5. **移动指针**:将 `node_i` 向右移动一位,继续处理剩余未排序部分。

6. **终止条件**:当 `node_i` 为 `None` 或 `node_i.next` 为 `None` 时,排序完成。

## 3. 链表选择排序实现代码

```python
class Solution:
def sectionSort(self, head: ListNode):
def selectionSort(self, head: ListNode):
node_i = head
# node_i 为当前未排序链表的第一个链节点

# 外层循环:遍历每个节点
while node_i and node_i.next:
# min_node 为未排序链表中的值最小节点
# 假设当前节点为最小值节点
min_node = node_i
node_j = node_i.next

# 内层循环:在未排序部分寻找最小值
while node_j:
if node_j.val < min_node.val:
min_node = node_j
node_j = node_j.next
# 交换值最小节点与未排序链表中第一个节点的值

# 如果找到更小的值,则交换
if node_i != min_node:
node_i.val, min_node.val = min_node.val, node_i.val

# 移动到下一个节点
node_i = node_i.next

return head

def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
return self.sectionSort(head)
return self.selectionSort(head)
```

## 3. 链表选择排序算法复杂度分析
## 4. 链表选择排序算法复杂度分析

| 指标 | 复杂度 | 说明 |
|------|--------|------|
| **最佳时间复杂度** | $O(n^2)$ | 无论初始顺序如何,都需 $O(n^2)$ 次比较 |
| **最坏时间复杂度** | $O(n^2)$ | 无论初始顺序如何,都需 $O(n^2)$ 次比较 |
| **平均时间复杂度** | $O(n^2)$ | 比较次数与数据状态无关 |
| **空间复杂度** | $O(1)$ | 原地排序,仅使用常数额外空间 |
| **稳定性** | ❌ 不稳定 | 可能改变相等节点的相对次序 |

**适用场景**:

- **小规模数据**:节点数量较少的场景(如 < 100)
- **对空间复杂度严格**:仅使用常数额外空间的需求
- **交换代价高的场景**:选择排序交换次数少(最多 \(n-1\) 次)


## 5. 总结

链表中的选择排序是一种简单直观的链表排序算法,通过在未排序部分中选择最小节点并将其放到已排序部分的末尾来完成排序。虽然实现简单,但效率较低。

- **时间复杂度**:$O(n^2)$。
- **空间复杂度**:$O(1)$。
**优点**:实现简单,空间复杂度低,交换次数少
**缺点**:时间复杂度高,不稳定,不适合大规模数据

## 练习题目

- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md)(链表选择排序会超时,仅做练习)
- [0148. 排序链表](https://github.com/ITCharge/AlgoNote/tree/main/docs/solutions/0100-0199/sort-list.md)(链表选择排序会超时,仅做练习)

- [链表排序题目列表](https://github.com/ITCharge/AlgoNote/tree/main/docs/00_preface/00_06_categories_list.md#%E9%93%BE%E8%A1%A8%E6%8E%92%E5%BA%8F%E9%A2%98%E7%9B%AE)
Loading

AltStyle によって変換されたページ (->オリジナル) /