第二章 初级排序算法
1 总览
1.1 排序算法的模板
public class Example {
public static void sort(Comparable[] a) { /*排序代码*/ }
private static boolean less(Comparable v, Comparable w) { return v.compareTo(w)<0; }
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i]; a[i] = a[j]; a[j] = t;
}
private static void show(Comparable[] a) {
for (Comparable c : a) {
System.out.print(c+" ");
}
System.out.println();
}
public static boolean isSorted(Comparable[] a) {
for (int i=0; i<a.length-1; i++) {
if (less(a[i], a[i+1])) {
return false;
}
}
return true;
}
public static void main(String[] args) {
Comparable[] a = ...; //data source
sort(a);
assert isSorted(a);
show(a);
}
}
1.2 排序算法
- 选择排序
- 插入排序
- 希尔排序
- 归并排序
- 快速排序
- 堆排序
- …
1.3 性能指标
交换, 比较 的次数.
2 初级排序算法
2.1 选择排序
2.1.1 原理
在数组中找到最小的元素, 然后将其与第一个元素交换位置(, 如果是本身的话就和自己交换). 然后找到剩下元素中最小的元素, 将它与第二个元素交换位置. 如此反复, 直到排序完成.
2.1.2 实现
public class Selection {
public static void sort(Comaprable[] a) {
int N = a.length;
for (int i=0; i<N; i++) {
int minIndex = i;
for (int j=i+1; j<N; j++) {
if less(a[j], a[i]) {
minIndex = j;
}
}
exch(a, i, j);
}
}
}
2.1.3 代价分析
交换次数 | 比较次数 |
---|---|
为什么访问次数是 ?
对于第一个元素, 最多对比 次. 对于第二个元素, 最多对比 次, … , 对于第倒数第二个元素, 需要访问 1 次.
2.2 插入排序
2.2.1 原理
插入排序的原理就和整理扑克牌一样, 当抽了一张牌以后, 需要把它插入相应的位置, 这就意味着比当前大的所有 “牌” 都需要向右移动一个位置. 插入排序所需的时间取决于输入中元素的初始顺序.
2.2.2 实现
public class Insertion {
public static void sort(Comparable[] a) {
//将 a 按升序排序 (小->大)
int N = a.length;
for (int i=0; i<N; i++) {
//将 a[i] 插入到 a[i-1], a[i-2], ... , a[0] 中
for (int j=i; j>0 && less(a[j], a[j-1]); j--) {
exch(a, j, j-1);
}
}
}
}
2.2.3 代价分析
交换次数 | 比较次数 | |
---|---|---|
平均 | ||
最坏 | ||
最好 |
最坏情况: 倒序
数组 | 交换次数 (当前) | 比较次数 (当前) |
---|---|---|
5, 4, 3, 2, 1 | - | - |
4, 5, 3, 2, 1 | 1 | 1 |
3, 4, 5, 2, 1 | 2 | 2 |
2, 3, 4, 5, 1 | 3 | 3 |
1, 2, 3, 4, 5 | 4 | 4 |
总计 | 10 | 10 |
最好情况: 已经排序完成
数组 | 交换次数 (当前) | 比较次数 (当前) |
---|---|---|
1, 2, 3, 4, 5 | - | - |
1, 2, 3, 4, 5 | 0 | 1 |
1, 2, 3, 4, 5 | 0 | 1 |
1, 2, 3, 4, 5 | 0 | 1 |
1, 2, 3, 4, 5 | 0 | 1 |
总计 | 0 | 4 |
2.2.4 部分有序
2.2.4.1 定义
倒置: 数组中的两个顺序颠倒的元素.
- E X A M P L E 中, 有 E-A, X-A, X-M, X-P, X-L, X-E, M-L, M-E, P-L, P-E, L-E 这些倒置.
部分有序: 如果数组中倒置的数量小于数组大小的某个倍数, 那么我们说这个数组是 部分有序 的.
2.2.4.2 典型的部分有序数组
- 数组中每个元素距离它的最终位置都不远;
- 一个有序的大数组接一个小数组;
- 数组中只有几个元素的位置不确定.
2.2.4.3 事实
插入排序对部分有序的数组很有效. 当倒置的数量很少时, 插入排序很可能比其他任何算法都要快.
2.3 希尔排序
2.3.1 原理
是基于插入排序的排序算法.
希尔排序为了加快速度, 简单地改进了插入排序. (交换不相邻的元素以对数组的局部进行排序, 并最终用插入排序将局部有序的数组排序.
思想: 使数组中任意间隔 h 的元素都是有序的. 这样的数组被称为 h 有序数组. (一个 h 有序数组就是 h 个互相独立的有序数组编织在一起组成的一个数组.)
L E E A M H L E P S O L T S X
L ----- M ----- P ----- T
E ----- H ----- S ----- S
E ----- L ----- O ----- X
2.3.2 实现
public class Shell {
public static void sort(Comparable[] a) {
int N = a.length;
int h = 1;
while (h<N/3) { h = 3*h+1; } //?
while (h>=1) {
for (int i=h; i<N; i++) {
for (int j=i; j>=h && less(a[j], a[j-h]); j-=h) {
exch(a, j, j-h);
}
}
h/=3;
}
}
}
2.3.3 代价分析
略
3 习题
3.1 题目1
写下选择排序是如何将数组 E A S Y Q U E S T I O N 排序的.
i | Array |
---|---|
0 | A E S Y Q U E S T I O N |
1 | A E S Y Q U E S T I O N |
2 | A E E S Y Q U S T I O N |
3 | A E E I S Y Q U S T O N |
4 | A E E I N S Y Q U S T O |
5 | A E E I N S Y Q U S T O |
6 | A E E I N S S Y Q U T O |
7 | A E E I N S S T Y Q U O |
8 | A E E I N S S T O Y Q U |
9 | A E E I N S S T O Q Y U |
10 | A E E I N S S T O Q U Y |
3.2 题目2
写下插入排序是如何将数组 E A S Y Q U E S T I O N 排序的.
i | Array |
---|---|
0 | E A S Y Q U E S T I O N |
1 | A E S Y Q U E S T I O N |
2 | A E S Y Q U E S T I O N |
3 | A E S Y Q U E S T I O N |
4 | A E Q S Y U E S T I O N |
5 | A E Q S U Y E S T I O N |
6 | A E E Q S U Y S T I O N |
7 | A E E Q S S U Y T I O N |
8 | A E E Q S S T U Y I O N |
9 | A E E I Q S S T U Y O N |
10 | A E E I O Q S S T U Y N |
11 | A E E I N O Q S S T U Y |