计数排序的变形

计数排序是一种非比较排序,有其独特的应用背景,基本的计数排序有一个假设:待排序的n个元素都是取值为0-k的整数。计数排序的时间复杂度为O(n+k)。当k=O(n),计数排序是一个线性时间的排序算法。基本的计数排序的空间复杂度也为O(n+k)。关于基本的计数排序《算法导论》中给出了详细描述,伪代码如下:

BASIC-COUNTING-SORT(A, B, k)        //数组B存放排序好的元素,显然书中给出的计数排序版本不是就地排序
 1  for i ← 0 to k
 2     do C[i] ← 0           //数组C记录待排序数中小于等于j的数的个数
 3  for j ← 1 to length[A]
 4     do C[A[j]] ← C[A[j]] + 1
 5  ▹ C[i] now contains the number of elements equal to i.
 6  for i ← 1 to k
 7     do C[i] ← C[i] + C[i - 1]
 8  ▹ C[i] now contains the number of elements less than or equal to i.
 9  for j ← length[A] downto 1  //这里倒序遍历原数组A,逐个将元素放到正确的位置,倒序遍历的技巧保证了排序是稳定的
10     do B[C[A[j]]] ← A[j]        
11        C[A[j]] ← C[A[j]] - 1

以上版本中的数组B并不是必须的,实际上可以直接在数组A上进行交换操作,实现就地排序。就地排序的代价是:由于在数组A上进行原址交换操作,所以在得到数组C后,要遍历A数组k趟,完成排序。如果k=O(n),这样的时间复杂度为O(n*n)。

看上去这种方法为了实现就地排序,牺牲了时间,视乎不太可取。但是设想这样一种应用背景:内存空间非常宝贵,数组长度很大(海量数据),k的取值是一个常量,只有固定的几种取值。这样一来的话,实现就地的计数排序的时间复杂度依然为O(n),并且节省了内存。下面以一道算法题举例说明(leetcode Sort Color):

问题:有若干的球,球有三种不同的颜色:红色、白色和蓝色,球的排列没有规则,现在的任务是将相同颜色的球排列在一起,并且红色的球在最前面,白色随之,蓝色最后。

解法一(计数排序):将红白蓝三色的球依次看做0,1,2,现在的目标是将包含0,1,2的乱序数组按先0后1再2的顺序排序,并且相同的数字相邻。这个问题显然可以用就地的计数排序来解决。代码如下:

void sortColors_inPlace(int A[], int n) {
	int count[3],num[3];
		
	memset(count,0,3*sizeof(int));
	for(int i=0;i<n;++i)
		count[A[i]]++;	
	count[0]--;
	for(int i=1;i<3;++i)
		count[i]+=count[i-1];
		
	num[0]=-1;	                         //num数组做为每趟遍历的结束标记
	for(int i=1;i<3;++i)
		num[i]=count[i-1];
		
	for(int i=0;i<3;++i){                    //分3次遍历A,依次将一种颜色放到正确位置 
		for(int j=n-1;j>=0;--j){         //倒叙遍历,保证稳定排序 
			if(A[j]==i){             //对于值为i的元素在做交换
				swap(A[count[i]],A[j])
				count[i]--;
				j++; //之所以将j加回去,是防止与A[i]交换的A[count[i]]的值也是i,并且这种+1的方式保证了排序仍是稳定的 
				if(count[i]==num[i])//条件成立说明值为i的元素都放到了正确位置,退出本次循环
					break;
			}
		}
	}		        
}


上述解法中当交换A[j]和A[count[i]]后,将j++不能少。为了保证稳定排序,即使A[j]和A[c ount[i]]都为i,也要将其交换一次,随着j的退回,原来A[j]会在下次的交换中放到位于它前面的位置。


关于该题还有另外的巧妙的解法,随后给出。本文主要说明就地计数排序。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值