概述
在算法系列(四)排序算法中篇--归并排序和快速排序一文中,我们介绍了归并排序和快速排序,最坏的情况下,最快的排序算法的时间复杂度是O(nlogn),是否有更好的算法呢?到目前为止,没有特殊的规则,O(nlogn)已经是最好的排序算法了,也就是说通用排序算法的时间复杂度下界就是O(nlogn)。如果限定一些规则,是可以打破这个下界的。下面说一下尽在O(n)时间内就能实现对数组排序的算法。
基于排序的规则
基于什么样的规则才能突破排序的下界呢?我们需要分析一下排序消耗的时间。排序需要遍历,比较,交换。能否省略其中的一些步骤呢?这就是要定义的规则,通过规则减少排序步骤。下面举一个最简单的例子。
一组待排序的元素仅有1和2,没有其它值,对这组数进行排序。
输入A0,A1,A2,A3......An-1,Ai为1或者2
排序步骤
1、令k=0
2、令i从0到n-1依次取值,如果A[i]=1,k自增1
3、令i从0到k-1依次取值,将A[i]赋值为1
4、令i从k到n-1依次取值,将A[i]赋值为2
这样我们完成了排序,花费的时间为O(n)
之前我们所说的算法都是通过比较元素对来确定顺序,那种排序叫做比较排序。凡是比较排序,通用下界为O(nlogn)
刚才所说的简单例子,是一个简单计数排序。下面详细说明一下
使用基数排序超越排序下界
简单例子每个元素仅有两种可能的取值,扩展一下,如果每个元素有m个不同取值,只要取值是m个连续整数之内的整数,算法是通用的。
首先,通过计算出有多少个元素的排序关键字等于某个值,随后就能就算出有多少个元素的排序关键字小于每个可能的排序。
基本思想
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。
计数排序算法详细描述
该算法需要三个基本方法
COUNT-KEY-EQUAL(A,n,m)
输入 A 一个数组,
n 数组A中的元素个数
m数组A中元素的取值范围
输出一个数组equal[0......m],是equal[j]等于数组A中元素值为j的元素个数
1、创建一个新数组equal[0......m]
2、令equal数组每个元素都为0
3、i从0到n-1依次取值,每次将equal[A[i]]的值自增1
4、返回equal
COUNT-KEY-LESS(equal,m)
输入值 COUNT-KEY-EQUAL方法对应的值equal,m
输出一个数组less[0......m],less[j]=equal[0]+equal[1]+......+equal[j-1]
1、创建一个新数组less[0...m]
2、令less[0]=0
3、j从1取到m,less[j]=less[j-1]+equal[j-1](这是普通的迭代算法)
4、返回less
REARRANGE(A,less,n,m)
输入 COUNT-KEY-EQUAL COUNT-KEY-LESS方法对应的A,less,n,m
输出 数组B,B中包含A中所有元素,并且已经排好序
1、创建新数组B[0...n-1],next[0.....m]
2、j从0到m依次取值
令next[j]=less[j]+1
3、令i从0到n-1依次取值
key=A[i];index=next[key],B[index]=A[i],next[key]++
4、返回数组B
代码实现
进行了逻辑整合,基本思路相同
package com.algorithm.sort; /** * 计数排序 * * @author chao * */ public class CountSort { public static void main(String[] args) { int[] num = { 1, 1 }; sort(num); for (int i = 0; i < num.length; i++) System.out.print(num[i] + " "); } /** * 计数排序 * * @param num */ public static void sort(int[] num) { int len = num.length; int[] orign = new int[len]; int max = 0;// 我们只对正整数排序 for (int i = 0; i < len; i++) { orign[i] = num[i]; if (num[i] > max) { max = num[i]; } } max = max + 1; int[] count = new int[max]; for (int i = 0; i < max; i++) { count[i] = 0; } for (int i = 0; i < len; i++) { count[num[i]]++; } int t1, t2; t1 = count[1]; count[0] = count[1] = 0; for (int i = 2; i < max; i++) { t2 = count[i]; count[i] = t1 + count[i - 1]; t1 = t2; } int key, index; for (int i = 0; i < len; i++) { key = orign[i]; index = count[key]; num[index] = orign[i]; count[key]++; } } }
复杂度分析
计数排序的复杂性为O(n),但是有空间代价,如果最大数很大的话,空间代价非常大。
还有一种排序叫做基数排序,是基于计数排序的,有约束条件,时间复杂度为O(n)
桶排序,基数排序跟计数排序类似,不再详细说明,堆排序(很常用,在树形算法分析中会再说明)
代码实现可以看github,地址https://github.com/robertjc/simplealgorithm
github代码也在不断完善中,有些地方可能有问题,还请多指教
欢迎扫描二维码,关注公众账号