Skip to content
2 changes: 1 addition & 1 deletion Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class Solution:
- **最坏时间复杂度**:$O(n^2)$。最差的情况下(初始时序列已经是降序排列,或者最小值元素处在序列的最后),则需要进行 $n$ 趟排序,总共进行 $∑^n_{i=2}(i−1) = \frac{n(n−1)}{2}$ 次元素之间的比较,因此,冒泡排序算法的最坏时间复杂度为 $O(n^2)$。
- **空间复杂度**:$O(1)$。冒泡排序为原地排序算法,只用到指针变量 $i$、$j$ 以及标志位 $flag$ 等常数项的变量。
- **冒泡排序适用情况**:冒泡排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,冒泡排序方法比较适合于参加排序序列的数据量较小的情况,尤其是当序列的初始状态为基本有序的情况。
- **排序稳定性**:由于元素交换是在相邻元素之间进行的,不会改变值相同元素的相对位置,因此,冒泡排序法是一种 **稳定排序算法**。
- **排序稳定性**:由于元素交换是在相邻元素之间进行的,不会改变相等元素的相对顺序,因此,冒泡排序法是一种 **稳定排序算法**。

## 参考资料

Expand Down
2 changes: 1 addition & 1 deletion Contents/01.Array/02.Array-Sort/02.Array-Selection-Sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Solution:
- **空间复杂度**:$O(1)$。选择排序算法为原地排序算法,只用到指针变量 $i$、$j$ 以及最小值位置 $min\underline{}i$ 等常数项的变量。
- **选择排序适用情况**:选择排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,选择排序方法比较适合于参加排序序列的数据量较小的情况。选择排序的主要优点是仅需要原地操作无需占用其他空间就可以完成排序,因此在空间复杂度要求较高时,可以考虑选择排序。

- **排序稳定性**:由于值最小元素与未排序区间第 $1$ 个元素的交换动作是在不相邻的元素之间进行的,因此很有可能会改变值相同元素的前后位置,因此,选择排序法是一种 **不稳定排序算法**。
- **排序稳定性**:由于值最小元素与未排序区间第 $1$ 个元素的交换动作是在不相邻的元素之间进行的,因此很有可能会改变相等元素的相对顺序,因此,选择排序法是一种 **不稳定排序算法**。



2 changes: 1 addition & 1 deletion Contents/01.Array/02.Array-Sort/03.Array-Insertion-Sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ class Solution:
- **最差时间复杂度**:$O(n^2)$。最差的情况下(初始时区间已经是降序排列),每个元素 $nums[i]$ 都要进行 $i - 1$ 次元素之间的比较,元素之间总的比较次数达到最大值,为 $∑^n_{i=2}(i − 1) = \frac{n(n−1)}{2}$。
- **平均时间复杂度**:$O(n^2)$。如果区间的初始情况是随机的,即参加排序的区间中元素可能出现的各种排列的概率相同,则可取上述最小值和最大值的平均值作为插入排序时所进行的元素之间的比较次数,约为 $\frac{n^2}{4}$。由此得知,插入排序算法的平均时间复杂度为 $O(n^2)$。
- **空间复杂度**:$O(1)$。插入排序算法为原地排序算法,只用到指针变量 $i$、$j$ 以及表示无序区间中第 $1$ 个元素的变量等常数项的变量。
- **排序稳定性**:在插入操作过程中,每次都讲元素插入到相等元素的右侧,并不会改变相等元素原本的顺序。因此,插入排序方法是一种 **稳定排序算法**。
- **排序稳定性**:在插入操作过程中,每次都讲元素插入到相等元素的右侧,并不会改变相等元素的相对顺序。因此,插入排序方法是一种 **稳定排序算法**。
2 changes: 1 addition & 1 deletion Contents/01.Array/02.Array-Sort/04.Array-Shell-Sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@ class Solution:
- 从算法中也可以看到,外层 `while gap > 0` 的循环次数为 $\log n$ 数量级,内层插入排序算法循环次数为 $n$ 数量级。当子数组分得越多时,子数组内的元素就越少,内层循环的次数也就越少;反之,当所分的子数组个数减少时,子数组内的元素也随之增多,但整个数组也逐步接近有序,而循环次数却不会随之增加。因此,希尔排序算法的时间复杂度在 $O(n \times \log^2 n)$ 与 $O(n^2)$ 之间。

- **空间复杂度**:$O(1)$。希尔排序中用到的插入排序算法为原地排序算法,只用到指针变量 $i$、$j$ 以及表示无序区间中第 $1$ 个元素的变量、间隔数 $gap$ 等常数项的变量。
- **排序稳定性**:在一次插入排序是稳定的,不会改变相同元素的相对顺序,但是在不同的插入排序中,相同的元素可能在各自的插入排序中移动。因此,希尔排序方法是一种 **不稳定排序算法**。
- **排序稳定性**:在一次插入排序是稳定的,不会改变相等元素的相对顺序,但是在不同的插入排序中,相等元素可能在各自的插入排序中移动。因此,希尔排序方法是一种 **不稳定排序算法**。
2 changes: 1 addition & 1 deletion Contents/01.Array/02.Array-Sort/05.Array-Merge-Sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ class Solution:

- **时间复杂度**:$O(n \times \log n)$。归并排序算法的时间复杂度等于归并趟数与每一趟归并的时间复杂度乘积。子算法 `merge(left_nums, right_nums):` 的时间复杂度是 $O(n)$,因此,归并排序算法总的时间复杂度为 $O(n \times \log n)$。
- **空间复杂度**:$O(n)$。归并排序方法需要用到与参加排序的数组同样大小的辅助空间。因此,算法的空间复杂度为 $O(n)$。
- **排序稳定性**:因为在两个有序子数组的归并过程中,如果两个有序数组中出现相同元素,`merge(left_nums, right_nums):` 算法能够使前一个数组中那个相同元素先被复制,从而确保这两个元素的相对次序不发生改变。因此,归并排序算法是一种 **稳定排序算法**。
- **排序稳定性**:因为在两个有序子数组的归并过程中,如果两个有序数组中出现相等元素,`merge(left_nums, right_nums):` 算法能够使前一个数组中那个相等元素先被复制,从而确保这两个元素的相对顺序不发生改变。因此,归并排序算法是一种 **稳定排序算法**。
4 changes: 2 additions & 2 deletions Contents/01.Array/02.Array-Sort/06.Array-Quick-Sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
1. 按照基准数的位置将数组拆分为左右两个子数组。
2. 对每个子数组分别重复「哨兵划分」和「递归分解」,直到各个子数组只有 $1$ 个元素,排序结束。

接下来,我们以 $[4, 7, 5, 2, 6, 1, 3]$ 为例,演示一下快速排序的整个步骤。
我们以 $[4, 7, 5, 2, 6, 1, 3]$ 为例,演示一下快速排序的整个步骤。

我们先来看一下单次「哨兵划分」的过程。

Expand Down Expand Up @@ -132,7 +132,7 @@ class Solution:
- **最坏时间复杂度**:$O(n^2)$。每一次选择的基准数都是数组的最终位置上的值,此时算法时间复杂度满足的递推式为 $T(n) = T(n - 1) + \Theta(n)$,累加可得 $T(n) = O(n^2)$。
- **平均时间复杂度**:$O(n \times \log n)$。在平均情况下,每一次选择的基准数可以看做是等概率随机的。其期望时间复杂度为 $O(n \times \log n)$。
- **空间复杂度**:$O(n)$。无论快速排序算法递归与否,排序过程中都需要用到堆栈或其他结构的辅助空间来存放当前待排序数组的首、尾位置。最坏的情况下,空间复杂度为 $O(n)$。如果对算法进行一些改写,在一趟排序之后比较被划分所得到的两个子数组的长度,并且首先对长度较短的子数组进行快速排序,这时候需要的空间复杂度可以达到 $O(log_2 n)$。
- **排序稳定性**:快速排序是一种 **不稳定排序算法**。
- **排序稳定性**:在进行哨兵划分时,基准数可能会被交换至相等元素的右侧。因此,快速排序是一种 **不稳定排序算法**。

## 参考资料

Expand Down
71 changes: 36 additions & 35 deletions Contents/01.Array/02.Array-Sort/08.Array-Counting-Sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,62 @@

> **计数排序(Counting Sort)基本思想**:
>
> 使用一个额外的数组 `counts`,其中 `counts[i]` 表示原数组 `arr` 中值等于 `i` 的元素个数。然后根据数组 `counts` 来将 `arr` 中的元素排到正确的位置
> 通过统计数组中每个元素在数组中出现的次数,根据这些统计信息将数组元素有序的放置到正确位置,从而达到排序的目的

## 2. 计数排序算法步骤

1. 找出待排序序列中最大值元素 `arr_max` 和最小值元素 `arr_min`。
2. 定义大小为 `arr_max - arr_min + 1` 的数组 `counts`,初始时,`counts` 中元素值全为 `0`。
3. 遍历数组 `arr`,统计值为 `num` 的元素出现的次数。将其次数存入 `counts` 数组的第 `num - arr_min` 项(`counts[num - arr_min]` 表示元素值 `num` 出现的次数)。
4. 对所有的计数累加,从 `counts` 中的第一个元素开始,每一项和前一项相加。此时 `counts[i]` 表示值为 `i` 的元素排名。
5. 反向填充目标数组:
1. 逆序遍历数组 `arr`。对于每个元素值 `arr[i]`,其对应排名为 `counts[arr[i] - arr_min]`。
2. 根据排名,将 `arr[i]` 放在数组对应位置(因为数组下标是从 `0` 开始的,所以对应位置为排名减 `1`)。即 `res[counts[arr[i] - arr_min] - 1] = arr[i]`。
3. 放入之后, 将 `arr[i]` 的对应排名减 `1`,即 `counts[arr[i] - arr_min] -= 1`。
1. **计算排序范围**:遍历数组,找出待排序序列中最大值元素 $nums\underline{}max$ 和最小值元素 $nums\underline{}min$,计算出排序范围为 $nums\underline{}max - nums\underline{}min + 1$。
2. **定义计数数组**:定义一个大小为排序范围的计数数组 $counts$,用于统计每个元素的出现次数。其中:
1. 数组的索引值 $num - nums\underline{}min$ 表示元素的值为 $num$。
2. 数组的值 $counts[num - nums\underline{}min]$ 表示元素 $num$ 的出现次数。

## 3. 计数排序动画演示
3. **对数组元素进行计数统计**:遍历待排序数组 $nums$,对每个元素在计数数组中进行计数,即将待排序数组中「每个元素值减去最小值」作为索引,将「对计数数组中的值」加 $1$,即令 $counts[num - nums\underline{}min]$ 加 $1$。
4. **生成累积计数数组**:从 $counts$ 中的第 $1$ 个元素开始,每一项累家前一项和。此时 $counts[num - nums\underline{}min]$ 表示值为 $num$ 的元素在排序数组中最后一次出现的位置。
5. **逆序填充目标数组**:逆序遍历数组 $nums$,将每个元素 $num$ 填入正确位置。
1. 将其填充到结果数组 $res$ 的索引 $counts[num - nums\underline{}min]$ 处。
2. 放入后,令累积计数数组中对应索引减 $1$,从而得到下个元素 $num$ 的放置位置。

![计数排序](http://qcdn.itcharge.cn/images/20220818140454.gif)
我们以 $[3, 0, 4, 2, 5, 1, 3, 1, 4, 5]$ 为例,演示一下计数排序的整个步骤。

## 4. 计数排序算法分析

- **时间复杂度**:$O(n + k)$。其中 $k$ 代表待排序序列的值域。
- **空间复杂度**:$O(k)$。其中 $k$ 代表待排序序列的值域。由于用于计数的数组 `counts` 的长度取决于待排序数组中数据的范围(大小等于待排序数组最大值减去最小值再加 `1`)。所以计数排序算法对于数据范围很大的数组,需要大量的内存。
- **计数排序适用情况**:计数排序一般用于整数排序,不适用于按字母顺序、人名顺序排序。
- **排序稳定性**:计数排序是一种 **稳定排序算法**。
![计数排序](https://qcdn.itcharge.cn/images/20230822135634.png)

## 5. 计数排序代码实现
## 3. 计数排序代码实现

```python
class Solution:
def countingSort(self, arr):
# 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_min
arr_min, arr_max = min(arr), max(arr)
def countingSort(self, nums: [int]) -> [int]:
# 计算待排序数组中最大值元素 nums_max 和最小值元素 nums_min
nums_min, nums_max = min(nums), max(nums)
# 定义计数数组 counts,大小为 最大值元素 - 最小值元素 + 1
size = arr_max - arr_min + 1
size = nums_max - nums_min + 1
counts = [0 for _ in range(size)]

# 统计值为 num 的元素出现的次数
for num in arr:
counts[num - arr_min] += 1
for num in nums:
counts[num - nums_min] += 1

# 计算元素排名
for j in range(1, size):
counts[j] += counts[j - 1]
# 生成累积计数数组
for i in range(1, size):
counts[i] += counts[i - 1]

# 反向填充目标数组
res = [0 for _ in range(len(arr))]
for i in range(len(arr) - 1, -1, -1):
# 根据排名,将 arr[i] 放在数组对应位置
res[counts[arr[i] - arr_min] - 1] = arr[i]
# 将 arr[i] 的对应排名减 1
counts[arr[i] - arr_min] -= 1
res = [0 for _ in range(len(nums))]
for i in range(len(nums) - 1, -1, -1):
num = nums[i]
# 根据累积计数数组,将 num 放在数组对应位置
res[counts[num - nums_min] - 1] = num
# 将 num 的对应放置位置减 1,从而得到下个元素 num 的放置位置
counts[nums[i] - nums_min] -= 1

return res

def sortArray(self, nums: List[int]) -> List[int]:
def sortArray(self, nums: [int]) -> [int]:
return self.countingSort(nums)
```


## 4. 计数排序算法分析

- **时间复杂度**:$O(n + k)$。其中 $k$ 代表待排序数组的值域。
- **空间复杂度**:$O(k)$。其中 $k$ 代表待排序序列的值域。由于用于计数的数组 $counts$ 的长度取决于待排序数组中数据的范围(大小等于待排序数组最大值减去最小值再加 $1$)。所以计数排序算法对于数据范围很大的数组,需要大量的内存。
- **计数排序适用情况**:计数排序一般用于整数排序,不适用于按字母顺序、人名顺序排序。
- **排序稳定性**:由于向结果数组中填充元素时使用的是逆序遍历,可以避免改变相等元素之间的相对顺序。因此,计数排序是一种 **稳定排序算法**。
Loading