算法系列(五)排序算法下篇--如何超越排序算法下界

概述

算法系列(四)排序算法中篇--归并排序和快速排序一文中,我们介绍了归并排序和快速排序,最坏的情况下,最快的排序算法的时间复杂度是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代码也在不断完善中,有些地方可能有问题,还请多指教

欢迎扫描二维码,关注公众账号






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值