Algorithm第四版笔记-排序
1 初级排序算法
- 排序算法的模板
- less()方法对元素比较
- exch()方法将元素交换位置
- sort()方法对数组进行排序
package code; import edu.princeton.cs.algs4.In; import edu.princeton.cs.algs4.StdOut; 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 (int i = 0; i < a.length; i++) { StdOut.print(a[i] + " "); } StdOut.println(); } public static boolean isSorted(Comparable[] a) { // 测试数组元素是否有序 for (int i = 0; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { String[] a = In.readStrings(); sort(a); assert isSorted(a); show(a); } }
1.1 运行时间
- 排序成本模型: 在研究排序算法时,我们需要计算比较和交换的数量.对于不交换元素的算法,我们会计算访问数组的次数.
1.2 额外的内存使用
- 排序算法可以分为两类
- 除了函数调用所需的栈和固定数目的实例变量之外无需额外内存的原地排序算法.
- 需要额外内存空间来存储另一份数组副本的其他排序算法.
1.3 数据类型
- 排序算法模板适用于任何实现了
Comparable
接口的数据类型. - 对于
v<w
,v=w
,v=>w
三种情况,Java的习惯是在v.compareTo(w)
被调用时候分别返回一个负数,零,和一个正整数(-1,0和1). - compareTo必须实现一个完整的比较序列,即:
- 自反性: 对于所有的v,
v=v
- 反对称性: 对于所有的
v<w
都有w>v
,且v=w
时w=v
- 传递性: 对于所有v,w和x,如果
v<=w
且w<=x
,则v<=x
- 自反性: 对于所有的v,
1.4 选择排序
- 原理: 找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换).
再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置,如此往复.
- 对于长度为N的数组,选择排序需要大约 \(N^2/2\) 次比较和N次交换.
- 最坏情况下,比较次数为
N-1
到1的等差数列求和.所以是大约 \(N^2/2\) 次比较
- 最坏情况下,比较次数为
- 选择排序的特点
- 运行时间与输入无关.
- 数据移动是最小的.
- 选择排序代码如下
package code; import edu.princeton.cs.algs4.In; import edu.princeton.cs.algs4.StdOut; public class Selection { public static void sort(Comparable[] a) { // 将a[]按升序排列 // 数组的长度 int N = a.length; for (int i = 0; i < N; i++) { int min = i; for (int j = i + 1; j < N; j++) { if (less(a[j], a[min])) min = j; } exch(a, i, min); } } 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 (int i = 0; i < a.length; i++) { StdOut.print(a[i] + " "); } StdOut.println(); } public static boolean isSorted(Comparable[] a) { // 测试数组元素是否有序 for (int i = 0; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { String[] a = In.readStrings(); sort(a); assert isSorted(a); show(a); } }
1.5 插入排序
- 插入排序为了给插入元素腾出空间,需要将其余所有元素在插入之前都向右移动一位.但是当索引到达数组的最右端时,数组排序就完成了.
- 与选择排序不同,插入排序所需的时间取决于输入中元素的初始顺序.
- 对于随机排列的长度为N且主键不重复的数组,平均情况下插入排序需要约 \(N^2/4\) 次比较以及 \(N^2/4\) 次交换.
最坏情况下需要 \(N^2/2\) 次比较和 \(N^2/2\) 次交换,最好情况下需要 N-1
次比较和0次交换.
- 插入排序的代码如下:
package code; import edu.princeton.cs.algs4.In; import edu.princeton.cs.algs4.StdOut; 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[i-3]...之中 for (int j = i; j > 0 && less(a[j], a[j - 1]); j--) { exch(a, j, j - 1); } } } 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 (int i = 0; i < a.length; i++) { StdOut.print(a[i] + " "); } StdOut.println(); } public static boolean isSorted(Comparable[] a) { // 测试数组元素是否有序 for (int i = 0; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { String[] a = In.readStrings(); sort(a); assert isSorted(a); show(a); } }
- 插入排序对于倒置数量很少的数组运行时间比较短.
- 插入排序需要的交换操作和数组中的倒置的数量相同,需要的比较次数大于等于倒置的数量,小于等于倒置的数量加上数组的大小再减一.