Skip to content

Commit 96fa41f

Browse files
committed
📝 Writing docs.
1 parent 3bb5565 commit 96fa41f

File tree

10 files changed

+1018
-0
lines changed

10 files changed

+1018
-0
lines changed

docs/SUMMARY.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# Summary
22

33
* [自述](README.md)
4+
* [排序](sort/README.md)
5+
* [冒泡排序](sort/bubble-sort.md)
6+
* [快速排序](sort/quick-sort.md)
7+
* [直接插入排序](sort/insert-sort.md)
8+
* [希尔排序](sort/shell-sort.md)
9+
* [简单选择排序](sort/selection-sort.md)
10+
* [堆排序](sort/heap-sort.md)
11+
* [归并排序](sort/merge-sort.md)
12+
* [基数排序](sort/radix-sort.md)
413

514
------
615

docs/sort/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# 排序
2+
3+
## 目录
4+
5+
* [冒泡排序](bubble-sort.html)
6+
* [快速排序](quick-sort.html)
7+
* [直接插入排序](insert-sort.html)
8+
* [希尔排序](shell-sort.html)
9+
* [简单选择排序](selection-sort.html)
10+
* [堆排序](heap-sort.html)
11+
* [归并排序](merge-sort.html)
12+
* [基数排序](radix-sort.html)

docs/sort/bubble-sort.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
---
2+
title: 冒泡排序
3+
date: 2015/03/03
4+
categories:
5+
- algorithm
6+
tags:
7+
- algorithm
8+
- sort
9+
---
10+
11+
# 冒泡排序
12+
13+
## 要点
14+
15+
冒泡排序是一种交换排序。
16+
17+
什么是交换排序呢?
18+
19+
> 交换排序:两两比较待排序的关键字,并交换不满足次序要求的那对数,直到整个表都满足次序要求为止。
20+
21+
## 算法思想
22+
23+
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
24+
25+
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,故名。
26+
27+
假设有一个大小为 N 的无序序列。冒泡排序就是要每趟排序过程中通过两两比较,找到第 i 个小(大)的元素,将其往上排。
28+
29+
![冒泡排序示例图.png](http://oyz7npk35.bkt.clouddn.com//image/algorithm/sort/bubble-sort.png)
30+
31+
以上图为例,演示一下冒泡排序的实际流程:
32+
33+
假设有一个无序序列 { 4. 3. 1. 2, 5 }
34+
35+
- 第一趟排序:通过两两比较,找到第一小的数值 1 ,将其放在序列的第一位。
36+
37+
- 第二趟排序:通过两两比较,找到第二小的数值 2 ,将其放在序列的第二位。
38+
39+
- 第三趟排序:通过两两比较,找到第三小的数值 3 ,将其放在序列的第三位。
40+
41+
至此,所有元素已经有序,排序结束。
42+
43+
要将以上流程转化为代码,我们需要像机器一样去思考,不然编译器可看不懂。
44+
45+
- 假设要对一个大小为 N 的无序序列进行升序排序(即从小到大)。
46+
47+
每趟排序过程中需要通过比较找到第 i 个小的元素。
48+
49+
所以,我们需要一个外部循环,从数组首端(下标 0) 开始,一直扫描到倒数第二个元素(即下标 N - 2) ,剩下最后一个元素,必然为最大。
50+
51+
- 假设是第 i 趟排序,可知,前 i-1 个元素已经有序。现在要找第 i 个元素,只需从数组末端开始,扫描到第 i 个元素,将它们两两比较即可。
52+
53+
所以,需要一个内部循环,从数组末端开始(下标 N - 1),扫描到 (下标 i + 1)。
54+
55+
**核心代码**
56+
57+
```java
58+
public void bubbleSort(int[] list) {
59+
int temp = 0; // 用来交换的临时数
60+
61+
// 要遍历的次数
62+
for (int i = 0; i < list.length - 1; i++) {
63+
// 从后向前依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上
64+
for (int j = list.length - 1; j > i; j--) {
65+
// 比较相邻的元素,如果前面的数大于后面的数,则交换
66+
if (list[j - 1] > list[j]) {
67+
temp = list[j - 1];
68+
list[j - 1] = list[j];
69+
list[j] = temp;
70+
}
71+
}
72+
73+
System.out.format("第 %d 趟:\t", i);
74+
printAll(list);
75+
}
76+
}
77+
```
78+
79+
## 算法分析
80+
81+
**冒泡排序算法的性能**
82+
83+
| 参数 | 结果 |
84+
| --------- | ----- |
85+
| 排序类别 | 交换排序 |
86+
| 排序方法 | 冒泡排序 |
87+
| 时间复杂度平均情况 | O(N2) |
88+
| 时间复杂度最坏情况 | O(N3) |
89+
| 时间复杂度最好情况 | O(N) |
90+
| 空间复杂度 | O(1) |
91+
| 稳定性 | 稳定 |
92+
| 复杂性 | 简单 |
93+
94+
### 时间复杂度
95+
96+
若文件的初始状态是正序的,一趟扫描即可完成排序。所需的关键字比较次数C和记录移动次数M均达到最小值:Cmin = N - 1, Mmin = 0。所以,冒泡排序最好时间复杂度为O(N)。
97+
98+
若初始文件是反序的,需要进行 N -1 趟排序。每趟排序要进行 N - i 次关键字的比较(1 ≤ i ≤ N - 1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:
99+
100+
Cmax = N(N-1)/2 = O(N2)
101+
102+
Mmax = 3N(N-1)/2 = O(N2)
103+
104+
冒泡排序的最坏时间复杂度为O(N2)。
105+
106+
因此,冒泡排序的平均时间复杂度为O(N2)。
107+
108+
总结起来,其实就是一句话:当数据越接近正序时,冒泡排序性能越好。
109+
110+
### 算法稳定性
111+
112+
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。
113+
114+
所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
115+
116+
### 优化
117+
118+
对冒泡排序常见的改进方法是加入标志性变量exchange,用于标志某一趟排序过程中是否有数据交换。
119+
120+
如果进行某一趟排序时并没有进行数据交换,则说明所有数据已经有序,可立即结束排序,避免不必要的比较过程。
121+
122+
**核心代码**
123+
124+
```java
125+
// 对 bubbleSort 的优化算法
126+
public void bubbleSort_2(int[] list) {
127+
int temp = 0; // 用来交换的临时数
128+
boolean bChange = false; // 交换标志
129+
130+
// 要遍历的次数
131+
for (int i = 0; i < list.length - 1; i++) {
132+
bChange = false;
133+
// 从后向前依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上
134+
for (int j = list.length - 1; j > i; j--) {
135+
// 比较相邻的元素,如果前面的数大于后面的数,则交换
136+
if (list[j - 1] > list[j]) {
137+
temp = list[j - 1];
138+
list[j - 1] = list[j];
139+
list[j] = temp;
140+
bChange = true;
141+
}
142+
}
143+
144+
// 如果标志为false,说明本轮遍历没有交换,已经是有序数列,可以结束排序
145+
if (false == bChange)
146+
break;
147+
148+
System.out.format("第 %d 趟:\t", i);
149+
printAll(list);
150+
}
151+
}
152+
```
153+
154+
## 示例代码
155+
156+
[我的 Github 测试例](https://github.com/dunwu/algorithm-notes/blob/master/codes/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java)
157+
158+
样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。

docs/sort/heap-sort.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
---
2+
title: 堆排序
3+
date: 2015/03/08
4+
categories:
5+
- algorithm
6+
tags:
7+
- algorithm
8+
- sort
9+
---
10+
11+
# 堆排序
12+
13+
## 要点
14+
15+
在介绍堆排序之前,首先需要说明一下,堆是个什么玩意儿。
16+
17+
****是一棵**顺序存储****完全二叉树**
18+
19+
其中每个结点的关键字都**不大于**其孩子结点的关键字,这样的堆称为**小根堆**
20+
21+
其中每个结点的关键字都**不小于**其孩子结点的关键字,这样的堆称为**大根堆**
22+
23+
举例来说,对于 n 个元素的序列 {R0, R1, ... , Rn} 当且仅当满足下列关系之一时,称之为堆:
24+
25+
- **Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)**
26+
27+
- **Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)**
28+
29+
其中 i=1,2,…,n/2 向下取整;
30+
31+
![堆排序示例图.png](http://oyz7npk35.bkt.clouddn.com//image/algorithm/sort/heap-sort.png)
32+
33+
如上图所示,序列 R{3, 8,15, 31, 25} 是一个典型的小根堆。
34+
35+
堆中有两个父结点,元素 3 和元素 8。
36+
37+
元素 3 在数组中以 R[0] 表示,它的左孩子结点是 R[1],右孩子结点是 R[2]
38+
39+
元素 8 在数组中以 R[1] 表示,它的左孩子结点是 R[3],右孩子结点是 R[4],它的父结点是 R[0]。可以看出,它们**满足以下规律**
40+
41+
设当前元素在数组中以 **R[i]** 表示,那么,
42+
43+
- 它的**左孩子结点**是:**R[2\*i+1]**;
44+
45+
- 它的**右孩子结点**是:**R[2\*i+2]**;
46+
47+
- (3) 它的**父结点**是:**R[(i-1)/2]**;
48+
49+
- R[i] <= R[2*i+1] 且 R[i] <= R[2i+2]
50+
51+
52+
## 算法思想
53+
54+
- 首先,按堆的定义将数组R[0..n]调整为堆(这个过程称为创建初始堆),交换R[0]和R[n]
55+
56+
- 然后,将R[0..n-1]调整为堆,交换R[0]和R[n-1]
57+
58+
- 如此反复,直到交换了R[0]和R[1]为止。
59+
60+
61+
以上思想可归纳为两个操作:
62+
63+
1. 根据初始数组去**构造初始堆**(构建一个完全二叉树,保证所有的父结点都比它的孩子结点数值大)。
64+
65+
2. 每次**交换第一个和最后一个元素,输出最后一个元素**(最大值),然后把剩下元素**重新调整**为大根堆。
66+
67+
68+
当输出完最后一个元素后,这个数组已经是按照从小到大的顺序排列了。
69+
70+
先通过详细的实例图来看一下,如何构建初始堆。
71+
72+
设有一个无序序列 { 1, 3,4, 5, 2, 6, 9, 7, 8, 0 }。
73+
74+
![堆排序示例图2.png](http://oyz7npk35.bkt.clouddn.com//image/algorithm/sort/heap-sort-02.png)
75+
76+
构造了初始堆后,我们来看一下完整的堆排序处理:
77+
78+
还是针对前面提到的无序序列 { 1,3, 4, 5, 2, 6, 9, 7, 8, 0 } 来加以说明。
79+
80+
![堆排序示例图3.png](http://oyz7npk35.bkt.clouddn.com//image/algorithm/sort/heap-sort-03.png)
81+
82+
相信,通过以上两幅图,应该能很直观的演示堆排序的操作处理。
83+
84+
**核心代码**
85+
86+
```java
87+
public void HeapAdjust(int[] array, int parent, int length) {
88+
int temp = array[parent]; // temp保存当前父节点
89+
int child = 2 * parent + 1; // 先获得左孩子
90+
91+
while (child < length) {
92+
// 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
93+
if (child + 1 < length && array[child] < array[child + 1]) {
94+
child++;
95+
}
96+
97+
// 如果父结点的值已经大于孩子结点的值,则直接结束
98+
if (temp >= array[child])
99+
break;
100+
101+
// 把孩子结点的值赋给父结点
102+
array[parent] = array[child];
103+
104+
// 选取孩子结点的左孩子结点,继续向下筛选
105+
parent = child;
106+
child = 2 * child + 1;
107+
}
108+
109+
array[parent] = temp;
110+
}
111+
112+
public void heapSort(int[] list) {
113+
// 循环建立初始堆
114+
for (int i = list.length / 2; i >= 0; i--) {
115+
HeapAdjust(list, i, list.length);
116+
}
117+
118+
// 进行n-1次循环,完成排序
119+
for (int i = list.length - 1; i > 0; i--) {
120+
// 最后一个元素和第一元素进行交换
121+
int temp = list[i];
122+
list[i] = list[0];
123+
list[0] = temp;
124+
125+
// 筛选 R[0] 结点,得到i-1个结点的堆
126+
HeapAdjust(list, 0, i);
127+
System.out.format("第 %d 趟: \t", list.length - i);
128+
printPart(list, 0, list.length - 1);
129+
}
130+
}
131+
```
132+
133+
## 算法分析
134+
135+
**堆排序算法的总体情况**
136+
137+
| 参数 | 结果 |
138+
| --------- | --------- |
139+
| 排序类别 | 选择排序 |
140+
| 排序方法 | 堆排序 |
141+
| 时间复杂度平均情况 | O(nlog2n) |
142+
| 时间复杂度最坏情况 | O(nlog2n) |
143+
| 时间复杂度最好情况 | O(nlog2n) |
144+
| 空间复杂度 | O(1) |
145+
| 稳定性 | 不稳定 |
146+
| 复杂性 | 较复杂 |
147+
148+
### 时间复杂度
149+
150+
堆的存储表示是**顺序的**。因为堆所对应的二叉树为完全二叉树,而完全二叉树通常采用顺序存储方式。
151+
152+
当想得到一个序列中第 **k** 个最小的元素之前的部分排序序列,最好采用堆排序。
153+
154+
因为堆排序的时间复杂度是 **O(n+klog2n)**,若 **k ≤ n/log2n**,则可得到的时间复杂度为 **O(n)**
155+
156+
### 算法稳定性
157+
158+
堆排序是一种**不稳定**的排序方法。
159+
160+
因为在堆的调整过程中,关键字进行比较和交换所走的是该结点到叶子结点的一条路径,
161+
162+
因此对于相同的关键字就可能出现排在后面的关键字被交换到前面来的情况。
163+
164+
## 示例代码
165+
166+
[我的 Github 测试例](https://github.com/dunwu/algorithm-notes/blob/master/codes/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java)
167+
168+
样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。

0 commit comments

Comments
 (0)