Skip to content

Commit 9d35aef

Browse files
committed
Add details for quick sort
1 parent 505fb99 commit 9d35aef

6 files changed

Lines changed: 86 additions & 16 deletions

File tree

src/main/java/sort/bubble/bubble_sort.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
## 复杂度分析
1212

13-
### 前提
13+
### 假设
1414

1515
* 数组长度为n
1616

@@ -28,4 +28,8 @@
2828

2929
在原数组上操作,空间复杂度为O(1)
3030

31+
## 稳定性分析
32+
33+
冒泡排序是稳定的排序算法,重复元素在排序前后的相对位置保持不变
34+
3135
## 扩展/优化

src/main/java/sort/insertion/insertion_sort.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,28 @@
1010

1111
## 复杂度分析
1212

13-
### 前提
13+
### 假设
1414

1515
* 数组长度为n
1616

1717
### 时间复杂度
1818

1919
外循环遍历数组需要n时间,内循环遍历数组需要n时间
2020

21-
最优时间复杂度:数组已有序 O(n)
21+
**最优时间复杂度**:数组已有序 O(n)
2222

23-
最差时间复杂度O(n<sup>2</sup>)
23+
**最差时间复杂度**:数组逆序 O(n<sup>2</sup>)
2424

25-
平均时间复杂度:O(n<sup>2</sup>)
25+
**平均时间复杂度**:O(n<sup>2</sup>)
2626

2727
### 空间复杂度
2828

2929
在原数组上操作,空间复杂度为O(1)
3030

31+
## 稳定性分析
32+
33+
插入排序是稳定的排序算法,重复元素在排序前后的相对位置保持不变
34+
3135
## 扩展/优化
3236

3337
* 折半插入

src/main/java/sort/merge/MergeSort.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ private void merge(int[] nums, int start, int mid, int end) {
2222
int l = start, r = mid + 1, i = 0;
2323

2424
while (l <= mid && r <= end) {
25-
if (nums[l] < nums[r]) {
25+
if (nums[l] <= nums[r]) {
2626
res[i++] = nums[l++];
2727
} else {
2828
res[i++] = nums[r++];

src/main/java/sort/merge/merge_sort.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@
44

55
分治法
66

7-
将待排序数组分为若干个子序列,确保每个子序列是有序的,然后再将有序子序列合并为整体有序序列
7+
将待排序数组分为若干个子序列,确保每个子序列是有序的,然后再将有序子序列合并为整体有序序列
88

99
## 实现方法
1010

1111
递归
1212

1313
## 复杂度分析
1414

15-
### 前提
15+
### 假设
1616

1717
* 数组长度为n
1818

1919
### 时间复杂度
2020

21-
$T(n) = 2T(\frac{n}{2}) + C(n)$
21+
$T(n) = 2T(\frac{n}{2}) + O(n)$
2222

23-
其中,T(n/2)是对每个子序列排序所需要的时间,C(n)是合并两个有序子序列所需要的时间
23+
其中,T(n/2)是对每个子序列排序所需要的时间,O(n)是合并两个有序子序列所需要的时间
2424

2525
**时间复杂度推导**
2626
![](imgs/img.png)
@@ -33,6 +33,10 @@ $T(n) = 2T(\frac{n}{2}) + C(n)$
3333

3434
### 空间复杂度
3535

36-
需要额外的空间去存储合并后的新序列,所以空间复杂度为O(n)
36+
主要是递归造成的栈空间的使用和需要额外的空间用来存储合并后的新数组:$\log n + n$。所以空间复杂度为O(n)
37+
38+
## 稳定性分析
39+
40+
取决于merge函数里的逻辑,当待合并的两个子序列中有等值的元素时,优先选取前半段子序列中的元素,这样可以保证归并排序算法的稳定性。
3741

3842
## 扩展/优化

src/main/java/sort/quick/quick_sort.md

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,76 @@
1818

1919
### 时间复杂度
2020

21+
```
22+
public void sort(int[] nums, int start, int end) {
23+
if (start >= end) {
24+
return;
25+
}
26+
27+
int pivotIdx = partition(nums, start, end); // 时间复杂度为O(n)
28+
sort(nums, start, pivotIdx - 1); // 时间复杂度为T()
29+
sort(nums, pivotIdx + 1, end);
30+
}
31+
```
32+
33+
在快排算法中,数组会被分成两个子序列,长度分别为pivotIdx和n-pivotIdx-1,对两个子序列调用递归排序的时间复杂度为T(pivotIdx)和T(n-pivotIdx-1);另外,partition部分的时间复杂度为O(n)
34+
。所以快排的总时间复杂度为三者相加,即T(n) = T(pivotIdx)+T(n-pivotIdx-1)+O(n)。特别地,T(0) = T(1) = 0。
35+
2136
**最优时间复杂度**:$O(n\log n)$
2237

23-
**最差时间复杂度**:O(n<sup>2</sup>)
38+
最优的情况是每次数组分割得到的两个子序列长度均等。这种情况下,算法的时间复杂度T(n) = T($\frac{n}{2}$)+T($\frac{n}{2}$)+O(n)
39+
40+
*时间复杂度推导*
41+
42+
$$\begin{aligned} T(n) &= 2T(\frac{n}{2})+n \\ &= 2(2T(\frac{n}{2})+\frac{n}{2})+n \\ &= 4T(\frac{n}{4})+2n \\ &= 4(2T(
43+
\frac{n}{8})+\frac{n}{4})+2n \\ &= 8T(\frac{n}{8})+3n \\ &= ... ... \\ &= 2^kT(\frac{n}{2^k})+kn \\ &= nT(1)+n\log n \\
44+
&= O(n\log n) \\ \end{aligned}$$
45+
46+
**最差时间复杂度**:$O(n^2)$
47+
48+
最差的情况是每次数组分割得到的两个子序列长度为n-1和0,即每次都选择了当前最大或者最小的元素作为基准。这种情况下,算法的时间复杂度T(n) = T(n-1)+T(0)+O(n) = T(n-1)+O(n)
49+
50+
*时间复杂度推导*
51+
52+
$$\begin{aligned} T(n) &= T(n-1)+n \\ &= T(n-2)+(n-1)+n \\ &= T(n-3)+(n-2)+(n-1)+n \\ &= ... ... \\ &= T(0)+1+2+...+(
53+
n-1)+n \\ &= T(0)+\frac{n*(n+1)}{2} \\ &= O(n^2) \\ \end{aligned}$$
2454

2555
**平均时间复杂度**:$O(n\log n)$
2656

2757
### 空间复杂度
2858

59+
partition函数是在原有数组上进行操作,空间复杂度为O(1),所以快排的空间复杂度主要来自于递归所造成的的栈空间的使用,所以需要知道递归栈的深度。
60+
2961
**最优空间复杂度**:$O(\log n)$
3062

31-
**最差空间复杂度**:O(n)
63+
最优的情况下,每次数组都被分割为两个均等的子序列,设栈的深度为k,则有$n(\frac{1}{2})^k$=1,可得$k=\log n$,即空间复杂度为O($\log n$)。
64+
65+
**最差空间复杂度**:$O(n)$
66+
67+
最差的情况下,每次数组都被分割成长度为n-1和0的两个子序列,所以递归的深度为n,每次递归所需要的空间是固定的,所以总的空间复杂度为O(n)。
3268

3369
**平均空间复杂度**:$O(\log n)$
3470

3571
## 稳定性分析
3672

73+
快排是不稳定的排序算法,重复元素在排序前后的相对位置可能会发生改变,e.g. [4, 3, 5, 3],基准选取策略为选取第一个元素。
74+
3775
## 扩展/优化
76+
77+
快排算法的优化主要体现在pivot的选取上,常见的选取方法有三种:固定选取,随机选取,三数取中。
78+
79+
**固定选取**
80+
81+
选择待排序数组中固定位置的元素作为基准,一般选择第一个元素或者最后一个元素。
82+
83+
**随机选取**
84+
85+
随机选取待排序数组中的一个元素作为基准。
86+
87+
**三数取中**
88+
89+
取待排序数组的第一个元素、中间元素和最后一个元素,取三个数排序后中间的那个元素作为基准。这种方法可以有效避免最差情况的发生(i.e. 待排序数组已有序,包括正序和逆序,基准为最大值或者最小值,导致时间复杂度和空间复杂度最大)。
90+
91+
## Refs
92+
93+
1. [【算法】快速排序](https://www.cnblogs.com/HDK2016/p/6876313.html#a31)

src/main/java/sort/selection/selection_sort.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
## 复杂度分析
1212

13-
### 前提
13+
### 假设
1414

1515
* 数组长度为n
1616

@@ -28,6 +28,8 @@
2828

2929
在原数组上操作,空间复杂度为O(1)
3030

31-
## 扩展/优化
31+
## 稳定性分析
32+
33+
选择排序是不稳定的排序方法。不能保证重复元素原本的相对位置。e.g. [5, 8, 5, 2, 9]
3234

33-
选择排序是不稳定的排序方法。不能保证元素原本的相位置。e.g. [5, 8, 5, 2, 9]
35+
## 扩展/优化

0 commit comments

Comments
 (0)