5、内部排序-基数排序

   计数排序

    前面所讲的排序都是基于比较的排序,都可称为”比较排序“,其下界为O(nlogn)。那么有没有时间复杂度为O(n)的线性时间排序算法呢?计数排序便是很基础的一种线性时间排序,它是基数排序的基础基本思想是:对每一个元素x,确定小于x的元素个数,就可以把x直接放到它在有序序列中的位置上。具体说来即:假设待排序序列a中值的范围[0,k],其中k表示待排序序列中的最大值。首先用一个辅助数组count记录各个值在a中出现的次数,比如count[i]表示数值i在a中的个数。然后依次改变count中元素值,使count[i]表示a中不大于i的元素个数。然后从后往前扫描a数组,a中的元素根据count中的信息直接复制给a。

    

 

	/*
	 * 计数排序后的顺序为从小到大 
	 * arr[0,...,len-1]为待排数组,每个元素均是0-k中的一个值
	 * crr[0,...,k]保存0,...,k中每个值在数组arr中出现的次数
	 */
	public static int[] countSort(int[] arr, int k) {

		if (arr == null || k < 1)
			return null;

		// 创建一个容量为k的数组crr,并且将crr中的所有数据都初始化为0
		int[] crr = new int[k];
		for (int i = 0; i < k; i++)
			crr[i] = 0;

		// 1. 计数
		for (int i = 0; i < arr.length; i++)
			crr[arr[i]]++;

		// 2. 排序
		for (int i = 0, j = 0; i < k; i++) {
			while ((crr[i]--) > 0) {
				arr[j++] = i;
			}
		}
		return arr;
	}

    最后我们稍微总结下计数排序的特点:
    1、不是基于比较的排序,因此可以达到线性排序时间;
    2、采取空间换时间的思想,需要brr和crr等辅助空间,但是时间复杂度仅为O(n+k);
    3、稳定性好,这也是计数排序最重要的一个特性。
    在实际工作中,当k=O(n)时,我们一般才会采取计数排序,如果k很大,则不宜采取该算法,尤其在如下情形下:待排序元素为:1、3、8、5、10000000,这样会造成很大的资源浪费。

    

   基数排序

    在计数排序中,当k很大时,时间和空间的开销都会增大(可以想一下对序列{8888,1234,9999}用计数排序,此时不但浪费很多空间,而且时间方面还不如比较排序)。基数排序的基本思想就是把待排序记录分解成个位(第一位)、十位(第二位)....然后分别以第一位、第二位...对整个序列进行计数排序。这样的话分解出来的每一位不超过9,即用计数排序序列中最大值是9.

    实现过程如下图所示:

    

    

    每一步都需要对各个位上的数进行排序,为了保证基数排序的稳定性,我们对每个位上的数进行排序时可以选用计数排序。

    实现代码

 

public class RadixSort {

	/*
	 * 在第一种计数排序的实现形式上做了些修改 计数排序后的顺序为从小到大 arr[0,...,len-1]为待排数组,我们这里采用三位数
	 * brr[0,...,len-1]为排序后的有序数组 w[0,...,len-1]用来保存取出的每一位上的数,其每个元素均是0-k中的一个值
	 * crr[0,...,k]保存0,...,k中每个值出现的次数
	 */
	public static void countSort(int[] arr, int[] brr, int[] w, int[] crr,int len, int k) {
		int i;
		// 数组crr各元素置0
		for (i = 0; i <= k; i++)
			crr[i] = 0;
		// 统计数组w中每个元素重复出现的个数
		for (i = 0; i < len; i++)
			crr[w[i]]++;
		// 求数组w中小于等于i的元素个数
		for (i = 1; i <= k; i++)
			crr[i] += crr[i - 1];
		// 把arr中的元素放在brr中对应的位置上
		for (i = len - 1; i >= 0; i--) {
			brr[crr[w[i]] - 1] = arr[i];
			// 如果有相同的元素,则放在下一个位置上
			crr[w[i]]--;
		}
		// 再将brr中的元素复制给arr,这样arr就有序了
		for (i = 0; i < len; i++) {
			arr[i] = brr[i];
		}
	}

	/*
	 * 基数排序后的顺序为从小到大 其中参数d为元素的位数
	 */
	public static void radixSort(int[] arr, int[] brr, int[] w, int[] crr,
			int len, int k, int d) {
		int i, j, val = 1;
		// 从低位到高位依次进行计数排序
		for (i = 1; i <= d; i++) { // w中保存的是arr中每个元素对应位上的数
			// 范围在0-k之间
			for (j = 0; j < len; j++)
				w[j] = (arr[j] / val) % 10;
			// 对当前位进行计数排序
			countSort(arr, brr, w, crr, len, k);
			val *= 10;
		}
	}

	public static void main(String args[]) {
		int i;
		// 待排序数组,每个元素的每一位均在0-7之间
		int arr[] = { 217, 125, 362, 136, 733, 522 };
		int brr[] = new int[6]; // 用来保存每次计数排序后的结果
		int w[] = new int[6]; // 每次循环时,保存该位上的数
		int crr[] = new int[8]; // 每次循环时,保存该位上的数出现的次数
		radixSort(arr, brr, w, crr, 6, 7, 3);
		for (i = 0; i < 6; i++)
			System.out.print(arr[i] +" ");

	}

}

 

    最后我们同样对基数排序稍微做下总结
    1、同样不是基于比较的排序,因此可以达到线性排序时间;
    2、同样采取空间换时间的思想,需要额外的辅助空间,但是时间复杂度仅为O(d(n+k));
    3、基数排序的稳定性同样也很好。

    时间复杂度:O(d(n+k)),对n个记录(假设记录含有d个关键字,每个关键字有k个取值)基数排序。每一趟分配的时间复杂度是O(n),每一趟收集的时间复杂度是O(k),总共需要d趟分配和收集。

    空间复杂度:O(k * n)(链式基数排序中空间复杂度不同)

 

    另附:计数排序、基数排序、桶排序的另一篇博文,来自博主:兰亭风雨

    http://blog.csdn.net/ns_code/article/details/20478753

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值