一、初级排序算法
1.选择排序
算法描述:
选择排序是一种最简单的排序算法,其算法流程是对于输入的数组,首先扫描数组中的所有元素,找到最小的那一个,与数组第一个元素交换(如果第一个元素就是最小的元素那么就和它自身交换),其次再扫描剩下的元素再寻找一个最小的,与第二个元素进行交换,如此往复,一直到整个数组都有序即结束。因为每一次对数组中未排好序的元素进行扫描都会选择出一个最小的值,所有叫做选择排序。
算法分析:
在选择排序中,每次扫描选择的最小值与当前位置交换之后即确定了该最小值的最终位置,所以对于一个有N个元素的数组来说,其交换次数为N次,而考虑比较次数时,每确定一个元素的最终位置,下一个元素能够比较的次数就减少一次,比如第一个元素要比较N-1次,第二个元素就比较N-2次,第三个元素就比较N-3次,以此类推,第i个元素比较N-i次,所以比较次数为(N-1)+(N-2)+...+2+1=N*(N-1)/2~N2/2次比较。
算法特点:
运行时间与输入无关:不管待排序的数组是有序还是无序,都要进行一遍又一遍的扫描,效率比较低。
数据移动是最少的:只需要两个元素交换即可,数据移动与元素个数呈线性相关。
package sortAltTest; /* * 选择排序 */ public class Selection { public static void sort(Comparable[] 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); } } // 判断元素v是否小于元素w 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++) { System.out.print(a[i] + " "); } System.out.println(); } // 比较是否已经排好序 public static boolean isSorted(Comparable[] a) { for (int i = 1; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { // TODO Auto-generated method stub Integer[] aIntegers = { 21, 22, 2, 4, 1, 34, 6, 3, 4 }; String[] aStrings = { "s", "o", "r", "t", "e", "x", "a" }; System.out.println("排序前:"); show(aIntegers); System.out.println("排序后:"); sort(aIntegers); show(aIntegers); System.out.println("----------------"); System.out.println("排序前:"); show(aStrings); System.out.println("排序后:"); sort(aStrings); show(aStrings); } }
2.插入排序
算法描述:
每当指针指到当前元素时,就要与前面的元素进行比较,将该元素插入到前面元素中合适的位置,直到指针指到最后一个元素排序结束。所以当前指针位置左侧的元素是已经排好序的,而右侧元素还是未排序的,为了要实现插入元素,每次插入元素之前都需要将所有元素向右移动腾出空间,所以叫插入排序。
算法分析:
对于一个长度为N的随机排列的数组,最坏情况下(倒序),当前指针的指向的元素需要和左侧所有的元素进行比较和交换,所以需要~N2/2次比较和~N2/2次交换,最好情况下(正序),需要N-1次比较和0次交换,所以平均情况下需要~N2/4次比较和~N2/4次交换。
算法特点:
数组的混乱程度会影响排序时间,对于一个已经排好序的算法,只需要比较就行,无需交换,其运行时间是线性的,数组越混乱其排序的时间越长,所以插入排序对于接近有序的数组是很快的。
指针所指的当前元素左侧已经排好序的元素,其位置不一定是最终的位置,在指针未指向最后一个元素时,每个元素的位置都是不确定的,都有可能会移动。
package sortAltTest; /* * 插入排序 * */ public class Inertion { public static void sort(Comparable[] a) { for (int i = 1; i < a.length; i++) { for (int j = i; j > 0 && less(a[j], a[j - 1]); j--) { exch(a, j, j - 1); } } } // 判断元素v是否小于元素w 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++) { System.out.print(a[i] + " "); } System.out.println(); } // 比较是否已经排好序 public static boolean isSorted(Comparable[] a) { for (int i = 1; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { // TODO Auto-generated method stub Integer[] aIntegers = { 21, 22, 2, 4, 1, 34, 6, 3, 4 }; String[] aStrings = { "s", "o", "r", "t", "e", "x", "a" }; System.out.println("排序前:"); show(aIntegers); System.out.println("排序后:"); sort(aIntegers); show(aIntegers); System.out.println("----------------"); System.out.println("排序前:"); show(aStrings); System.out.println("排序后:"); sort(aStrings); show(aStrings); } }
3.希尔排序
算法描述:
插入排序每次比较和交换相邻的元素,那么在最坏情况下就会显得非常慢,而希尔排序是一种基于插入排序的快速排序算法,通过设定增量h可以对间隔h的元素进行交换排序,形成h有序数组,然后通过减少增量h将这些有序数组整合起来从而达到整体有序。
算法分析:
该算法的复杂度取决于如何确定增量h,所以增量h对希尔排序起着决定性作用,通常来说,其算法复杂度不会达到平方级别,这已经比前面两种排序要快了。
算法特点:
根据增量h对间隔h的元素进行排序,最开始各个子数组很短,排序之后子数组由混乱数组变成部分有序,这就很适合插入排序。
希尔排序对大规模随机数组效果非常好,所以对于排序选择可以先考虑该方法,在此基础之上再进行算法改进和考虑更加复杂的算法。
package sortAltTest; public class Shell { public static void sort(Comparable[] a) { // 确定增量h,设定增量规则 int h = 1; while (h < a.length / 3) { h = h * 3 + 1; } while (h >= 1) { for (int i = h; i < a.length; i++) { for (int j = i; j >= h && less(a[j], a[j - h]); j -= h) { exch(a, j, j - h); } } h = h / 3; } } // 判断元素v是否小于元素w 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++) { System.out.print(a[i] + " "); } System.out.println(); } // 比较是否已经排好序 public static boolean isSorted(Comparable[] a) { for (int i = 1; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { // TODO Auto-generated method stub Integer[] aIntegers = { 21, 22, 2, 4, 1, 34, 6, 3, 4 }; String[] aStrings = { "s", "o", "r", "t", "e", "x", "a" }; System.out.println("排序前:"); show(aIntegers); System.out.println("排序后:"); sort(aIntegers); show(aIntegers); System.out.println("----------------"); System.out.println("排序前:"); show(aStrings); System.out.println("排序后:"); sort(aStrings); show(aStrings); } }
二、归并排序
算法描述:
对一个大的数组进行排序,先将大的数组递归的分成左右两半,分别对左右两半进行排序,最后将小数组归并起来,完成该大数组的归并。
1.自顶向下的归并排序
先将大数组进行递归切分,一直到不能切分为止,开始排序合并,每合并一次就排序一次,最后整个数组排序完成,是一个由大到小的过程。假设数组中有16个数,按照树的形式如下划分:
算法分析:
对于数组的长度为N,其对应如上的依赖树的层数为n=log2N,对于0~n-1之间的任意k表示第k层,第k层共有2k个子数组,每个子数组的长度为2n-k,归并最多需要比较2n-k次,因此每层比较次数为2k*2n-k=2n,n层共需要n2n=NlgN,所以归并排序的复杂度为NlgN,明显要快于前面三个排序算法。
算法特点:
在对小规模数据进行排序时,归并算法的效率要比插入排序效率低,但是当面对大规模数据时,归并排序就非常有效。
本归并排序使用了额外的数组对原始数组进行复制,导致了产生了空间开销,是一种空间换时间的策略。
package sortAltTest; import java.util.Comparator; /* 自顶向下的归并排序*/ public class Merge { private static Comparable[] aux; public static void sort(Comparable[] a) { aux = new Comparable[a.length]; sort(a, 0, a.length - 1); } public static void sort(Comparable[] a, int lo, int hi) { if (hi <= lo) return; int mid = lo + (hi - lo) / 2; sort(a, lo, mid); sort(a, mid + 1, hi); merge(a, lo, mid, hi); } public static void merge(Comparable[] a, int lo, int mid, int hi) { int i = lo, j = mid + 1; for (int k = i; k <= hi; k++) { aux[k] = a[k]; } for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j++]; } else if (j > hi) { a[k] = aux[i++]; } else if (less(aux[j], aux[i])) { a[k] = aux[j++]; } else { a[k] = aux[i++]; } } } // 判断元素v是否小于元素w 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++) { System.out.print(a[i] + " "); } System.out.println(); } // 比较是否已经排好序 public static boolean isSorted(Comparable[] a) { for (int i = 1; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { // TODO Auto-generated method stub Integer[] aIntegers = { 21, 22, 2, 4, 1, 34, 6, 3, 4 }; String[] aStrings = { "s", "o", "r", "t", "e", "x", "a" }; System.out.println("排序前:"); show(aIntegers); System.out.println("排序后:"); sort(aIntegers); show(aIntegers); System.out.println("----------------"); System.out.println("排序前:"); show(aStrings); System.out.println("排序后:"); sort(aStrings); show(aStrings); } }
2.自底向上归并
算法描述:
一开始就归并这些小的数组,然后再归并大数组,没有递归切分的过程,引入归并长度sz,一开始归并sz=1,然后归并sz=2,再归并sz=4,以此类推,这样实现代码量较小。
package sortAltTest; /* 自底向上归并*/ public class MergeBU { private static Comparable[] aux; public static void sort(Comparable[] a) { aux = new Comparable[a.length]; for (int sz = 1; sz < a.length; sz = sz + sz) { for (int lo = 0; lo < a.length - sz; lo += sz + sz) { merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, a.length - 1)); } } } public static void merge(Comparable[] a, int lo, int mid, int hi) { int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j++]; } else if (j > hi) { a[k] = aux[i++]; } else if (less(aux[j], aux[i])) { a[k] = aux[j++]; } else { a[k] = aux[i++]; } } } // 判断元素v是否小于元素w 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++) { System.out.print(a[i] + " "); } System.out.println(); } // 比较是否已经排好序 public static boolean isSorted(Comparable[] a) { for (int i = 1; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { // TODO Auto-generated method stub Integer[] aIntegers = { 21, 22, 2, 4, 1, 34, 6, 3, 4 }; String[] aStrings = { "s", "o", "r", "t", "e", "x", "a" }; System.out.println("排序前:"); show(aIntegers); System.out.println("排序后:"); sort(aIntegers); show(aIntegers); System.out.println("----------------"); System.out.println("排序前:"); show(aStrings); System.out.println("排序后:"); sort(aStrings); show(aStrings); } }
三、快速排序
1.最初的快速排序
算法描述:
快速排序也是一种分治的算法,不同于归并排序,它要选择数组中第一个元素作为划分数组的中枢,划分后的子数组左边小于中枢元素,右边大于中枢元素,然后分别再对子数组重新选择对应的中枢进行划分,这样每次划分之后子数组都是排好序的,合并之后无需再调整顺序,这就是快速排序。
算法分析:
对于长度为N的无重复数组排序,快速排序平均需要~2NlgN次比较,以及1/6的交换。
算法特性:
快速排序和归并排序的思想是一样的,都是采用递归的思想,但是归并排序递归调用发生在处理整个数据前,而快速排序递归调用发生在处理整个数据后。
快速排序只需要很小的辅助栈,并且排序时间和NlgN成正比,这是前面排序所没有的。
package sortAltTest; /* 快速排序 */ public class Quick { public static void sort(Comparable[] a) { sort(a, 0, a.length - 1); } public static void sort(Comparable[] a, int lo, int hi) { if (hi <= lo) { return; } int j = partition(a, lo, hi); sort(a, lo, j - 1); sort(a, j + 1, hi); } public static int partition(Comparable[] a, int lo, int hi) { int i = lo, j = hi + 1; Comparable v = a[lo]; while (true) { while (less(a[++i], v)) { if (i == hi) break; } while (less(v, a[--j])) { if (j == lo) { break; } } if (i >= j) { break; } exch(a, i, j); } exch(a, lo, j); return j; } // 判断元素v是否小于元素w 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++) { System.out.print(a[i] + " "); } System.out.println(); } // 比较是否已经排好序 public static boolean isSorted(Comparable[] a) { for (int i = 1; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { // TODO Auto-generated method stub Integer[] aIntegers = { 21, 22, 2, 4, 1, 34, 6, 3, 4 }; String[] aStrings = { "s", "o", "r", "t", "e", "x", "a" }; System.out.println("排序前:"); show(aIntegers); System.out.println("排序后:"); sort(aIntegers); show(aIntegers); System.out.println("----------------"); System.out.println("排序前:"); show(aStrings); System.out.println("排序后:"); sort(aStrings); show(aStrings); } }
2.三向切分的快速排序
算法描述:
三项切分的快速排序目的是为了解决数组中存在大量重复的元素,将数组切分为三段,选择中枢元素v,指针lo到lt为小于元素v的元素,指针lt到i为等于元素v的元素,指针i到gt为未排序的元素,指针gt到hi为大于元素v的元素,对于扫描到未排序的元素时,会出现以下三种情况:
- a[i]元素小于v,将a[lt]与a[i]交换,指针lt和i加一;
- a[i]元素等于v,不用交换,指针i加一;
- a[i]元素大于v,将a[gt]与a[i]交换,指针gt减一,i加一。
package sortAltTest; /* 三向切分的快速排序 */ public class Quick3way { public static void sort(Comparable[] a) { sort(a, 0, a.length - 1); } public static void sort(Comparable[] a, int lo, int hi) { if (hi <= lo) { return; } int lt = lo, i = lo + 1, gt = hi; Comparable v = a[lo]; while (i <= gt) { int cmp = a[i].compareTo(v); if (cmp < 0) { exch(a, lt++, i++); } else if (cmp > 0) { exch(a, i, gt--); } else { i++; } } sort(a, lo, lt - 1); sort(a, gt + 1, hi); } // 判断元素v是否小于元素w 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++) { System.out.print(a[i] + " "); } System.out.println(); } // 比较是否已经排好序 public static boolean isSorted(Comparable[] a) { for (int i = 1; i < a.length; i++) { if (less(a[i], a[i - 1])) return false; } return true; } public static void main(String[] args) { // TODO Auto-generated method stub Integer[] aIntegers = { 21, 22, 2, 4, 1, 34, 6, 3, 4 }; String[] aStrings = { "s", "o", "r", "t", "e", "x", "a" }; System.out.println("排序前:"); show(aIntegers); System.out.println("排序后:"); sort(aIntegers); show(aIntegers); System.out.println("----------------"); System.out.println("排序前:"); show(aStrings); System.out.println("排序后:"); sort(aStrings); show(aStrings); } }
四、堆排序
算法描述:
要使用堆排序一定要分两步走:
-
- 堆构建,首先对输入的数组构建成一个堆,其实就是对数组索引的操作,最开始的数组是原始堆,构建堆的过程实际是将最大值(或最小值)移动到根节点上
- 堆调整,根据要求,若要进行升序排序,那么就将构建好堆的根节点(最大值)与最后一个节点交换,然后再次对前n-1个节点进行调整(降序亦然)
- 注意:在堆调整的过程中,先调整小堆,再调整大堆,也是一个递归的过程
算法分析:
若将N个元素排序,堆排序只需少于(2NlgN+2N)次比较,和一半次数的交换,其中2N用于构造,2NlgN用于下沉操作。
算法特点:
堆排序是同时最优利用空间和时间的算法,最坏情况下也能保证~2NlgN次比较。
package sortAltTest; /* 堆排序*/ import java.util.Arrays; import java.util.PriorityQueue; import java.util.Scanner; import java.util.Stack; public class Queue { public static boolean less(Comparable[] comparables, int i, int j) { return comparables[i].compareTo(comparables[j]) < 0; } public static void exch(Comparable[] comparables, int i, int j) { Comparable tmp = comparables[i]; comparables[i] = comparables[j]; comparables[j] = tmp; } //上浮操作 public static void swim(Comparable[] comparables, int k) { while (k > 1 && less(comparables, k / 2, k)) { exch(comparables, k / 2, k); k = k / 2; } } //下沉操作 public static void sink(Comparable[] comparables, int k, int n) { while ((2 * k) <= n) { int j = 2 * k; if (j < n && less(comparables, j, j + 1)) { j++; } if (!less(comparables, k, j)) { break; } exch(comparables, k, j); k = j; } } public static void sort(Comparable[] comparables) { int n = comparables.length - 1; //堆构造 for (int i = n / 2; i >= 1; i--) { sink(comparables, i, n); } //堆调整 while (n > 1) { exch(comparables, 1, n--); sink(comparables, 1, n); } } // 显示数组 private static void show(Comparable[] a) { for (int i = 1; i < a.length; i++) { System.out.print(a[i] + " "); } System.out.println(); } public static void main(String[] args) { // TODO Auto-generated method stub Integer[] aIntegers = { 21, 22, 2, 4, 1, 34, 6, 3, 4 }; String[] aStrings = { "s", "o", "r", "t", "e", "x", "a" }; System.out.println("排序前:"); show(aIntegers); System.out.println("排序后:"); sort(aIntegers); show(aIntegers); System.out.println("----------------"); System.out.println("排序前:"); show(aStrings); System.out.println("排序后:"); sort(aStrings); show(aStrings); } }