1. 比较排序算法的下界算法
到目前为止,咱们已经介绍了几种能在O(nlgn)时间内排序n个数的算法:归并排序和堆排序达到了最坏状况下的上界;快速排序在平均状况下达到该上界。数组
若是仔细观察,咱们会发现:在排序的最终结果中,各元素之间的次序依赖于它们之间的比较。咱们把这类排序算法统称为比较排序。到目前为止咱们介绍的排序算法都是比较排序。下面咱们来论证一个事实:任何比较排序算法在最坏状况下都要通过Ω(n lgn)次比较。spa
在证实以前,咱们先介绍一种由比较排序抽象而来的决策树模型。决策树是一棵彻底二叉树,它能够表示在给定输入规模状况下,某一特定排序算法对全部元素的比较操做,而控制,数据移动等操做都被忽略了。以下图,它显示的是插入排序算法做用于包含三个元素的输入序列的决策树状况。3d
从决策树中咱们能够看出:从根结点到任意一个可到达叶结点之间的最长简单路径的长度,表示的就是对应排序算法中最坏状况下的比较次数。所以,一个比较排序算法中的最坏状况的排序次数就等于决策树的高度。而且,当决策树中全部排列都是以可到达的叶结点的形式出现时,该决策树高度的下界也就是比较排序算法运行时间的下界。下面咱们正式给出证实。blog
考虑一棵高度为h,具备l个可到达叶结点的决策树。它对应一个对n个元素进行的比较排序。由于输入数据有n!种可能的排列都是叶结点,因此n!≤l。因为在一棵高度为h的二叉树中,叶结点的数目很少于2^h,咱们获得:排序
n! ≤ l ≤ 2^h,ci
两边取对数得:get
h ≥ lg(n!) = Ω(nlgn)it
2. 计数排序table
咱们先假设待排序序列各元素均在区间[0, k]上。
计数排序的思想是:在待排序序列中,若是咱们能统计出有多少元素小于或等于某一个元素,咱们也就知道了该元素的正确位置。例如,对于待排序序列{2,5,3,0,2,3,0,3},咱们统计出有8个元素小于等于5(包括5本身),那么5这个元素就应该被排序到第8位。
下面给出算法的伪代码描述:
其中数组A[1~n]是待排序数组;数组B[1~n]用来存放已排好序的元素。C[0~k]用来存放上面所说的统计数(具体的说C[i]就表示在数组A中,小于或等于i的元素的总个数)。
下面这幅图描述的是对序列{2,5,3,0,2,3,0,3}排序的过程:
下面咱们给出算法的Java实现代码:
public static void main(String[] args) { int[] array = { 2, 5, 3, 0, 2, 3, 0, 3 }; printArray(countingSort(array, 5)); } /** * 计数排序 * * @param array * 待排序数组(假定各元素的范围是0~max,包括0和max) * @param max * 待排序数组中的最大值 */ public static int[] countingSort(int[] array, int max) { int[] result = new int[array.length]; int[] temp = new int[max + 1]; // 如下循环操做完成后,temp的第i个位置保存着array中,值为i的元素的总个数 for (int i : array) { temp[i]++; } // 如下循环操做完成后,temp的第i个位置保存着array中,值小于或等于i的元素的总个数 for (int i = 1; i < temp.length; i++) { temp[i] += temp[i - 1]; } for (int i = array.length - 1; i > -1; i--) { result[temp[array[i]] - 1] = array[i]; temp[array[i]]--; } return result; } /** * 打印数组 */ public static void printArray(int[] array) { for (int i : array) { System.out.print(i + " "); } System.out.println(); }
3. 算法分析
咱们如今来分析计数排序的时间代价。
在伪代码中,第2~3行时间代价θ(k);第4~5行时间为θ(n);第7~8行时间为θ(k),第10~12行时间为θ(n)。所以,总的运行时间是θ(k+n)。当k= O(n)时,运行时间为θ(n)。
能够看出,计数排序的下界优于咱们上面论证的比较排序算法的下界时间Ω(nlgn)。这是由于计数排序并非比较排序算法。事实上,在代码中从未出现比较某两个元素大小的代码。相反,计数排序是使用输入元素的实际值来肯定其在数组中的位置。此时,比较排序算法的模型对计数排序再也不适用。