线性排序的Java实现

       线性排序算法是不用进行元素比较的算法,因此它的时间复杂度不受Ω(NlogN)的限制,它的排序时间复杂度是线性 。这里将会介绍最常见的三种线性排序算法:桶排序,计数排序,基数排序。

一 桶排序

桶排序(bucke sort)假设输入数据服从均匀分布,平均情况下它的时间代价为O(N),最坏为O(N^2)。桶排序将[0,1)区间换分为k个相同大小的子区间,也叫桶。然后根据输入的数据,将它们放在各个桶中,再对每个桶单独排序,最后遍历每个桶,就得到了结果。实际中,并不一定按0-1区分,也可以按照输入值的范围进行桶大小的确定。比如,输入的数据范围是0-99,这里用10个桶,第一个桶装0-9,第二个装10-19,以此类堆。因为每个桶中可能会有多个数,因此需要用其他数据结构装载,这里用链表,每个桶中用快排来排序。如果数据分布不均匀,把所有数据放在一个桶中,时间代价就是O(N^2)。Java代码如下:

//桶排序  假设a数组的值在0-99之间 
//可以设10个桶 桶0装0-10,桶1装1-20,以此类推,相同的值需要用到链表,桶值为链表
public static void bucketSort(int[] a)
{
	LinkedList[] ll=new LinkedList[10]; //不能创建泛型数组,要将类型变量去掉才行
		
	for(int i=0;i<a.length;i++)  //将每个值插入到桶中,如果该桶(链表)未建立,则建立再插入
	{
		int tmp=a[i]/10;
		if(ll[tmp]!=null)
		{
			ll[tmp].add(a[i]);
		}
		else
		{
			ll[tmp]=new LinkedList<Integer>();//等有数据插入时才创建
			ll[tmp].add(a[i]);
		}
	}
	for(int i=0;i<10;i++)  //对每个桶在进行排序
	{
		if(ll[i]!=null)
			Collections.sort(ll[i]);
	}
	int c=0;
	for(int i=0;i<10;i++)  //排序好的输回到数组a中
	{
		while(ll[i]!=null && ll[i].size()!=0)
		{
			a[c]=(Integer)ll[i].pop();
			c++;
		}
	}
	//输出
	for(int e:a)
		System.out.print(e+" ");
}


结果:

桶排序就适合于那些分布均匀的数据。


二 计数排序

计数排序(counting sort)假设每个元素都在0-k区间,建立一个临时数组c[0..k],将元素值作为数组c的座标(其实类似于Hash),c[i]存放小于或者等于i的元素的个数。最后输出时数组c其实就是存储了各个元素的位置,还需要一个临时数据b作为输出。计数排序适用于k不大的情况,负责考虑采用基数排序。

//计数排序 顾名思义,对数组中的每个值存储它的个数
//假设输入值0-k,则要建立数组c[k+1](为<=i的值的个数)存储个数,建立B[]数组存放输出
public static void countingSort(int[] a)
{
	int[] b=new int[a.length];
	int[] c=new int[21]; //假设输入a的范围:0-20
	for(int i=0;i<a.length;i++)
	{
		c[a[i]]=c[a[i]]+1;    //这里c[i]代表值i的个数,下面的循环将c[i]变为<=i的值的个数
	}
	for(int i=1;i<21;i++)
	{
		c[i]=c[i]+c[i-1];
	}
	for(int i=a.length-1;i>=0;i--) //倒序输出是保证稳定性
	{
		b[c[a[i]]-1]=a[i]; //下标从0开始,而c[i]存储的是长度,这里b[]里面减一
		c[a[i]]=c[a[i]]-1;
	}
	//输出
	for(int e:b)
		System.out.print(e+" ");
}

结果:


它的时间复杂度O(n+k)(最坏和平均都是它),k为元素最大值。此外,计数排序是稳定的,辅助空间n+k,这个空间是比较大的,计数排序对待排序序列有约束条件(如前面我们假设待排序序列a中值的范围[0,k],其中k表示待排序序列中的最大值),元素值需是非负数,k太大的话会大大降低效率。


三 基数排序

在计数排序中,如果k太大则不能使用,比如{8888,1234,9999}用计数排序,此时不但浪费很多空间,而且时间方面还不如比较排序。于是可以把待排序记录分解成个位(第一位)、十位(第二位)....然后分别以第一位、第二位...对整个序列进行排序。这样的话分解出来的每一位不超过9,即用计数排序序列中最大值是9。对每一位的排序可以使用计数排序,也可以用桶排序。下面的程序使用计数排序。

//基数排序 将数字按位数排序 可以从高位到低位,也可以从低位到高位 
//每一个位数的排序再采用稳定排序,可以用桶排序或者计数排序 
//如果给的数值过大,计数排序不再适用,可考虑基数排序
//假设a的值最大是9999
public static void radixSort(int[] a)
{
	int[] c=new int[10];
	int[] b=new int[a.length];
	for(int j=0;j<4;j++)
	{
		int ss=(int)Math.pow(10,j);
		for(int i=0;i<a.length;i++)
		{
			c[a[i]/ss%10]=c[a[i]/ss%10]+1;    //这里c[i]代表值i的个数,下面的循环将c[i]变为<=i的值的个数
		}
		for(int i=1;i<10;i++)
		{
			c[i]=c[i]+c[i-1];
		}
		for(int i=a.length-1;i>=0;i--) //倒序输出是保存稳定性
		{
			b[c[a[i]/ss%10]-1]=a[i]; //下标从0开始,而c[i]存储的是长度,这里b[]里面减一
			c[a[i]/ss%10]=c[a[i]/ss%10]-1;
		}
		for(int i=0;i<a.length;i++)  //将按位排好的值重新赋给数组a
			a[i]=b[i];
		for(int i=0;i<10;i++)  //清空数组c
			c[i]=0;
	}
	//输出
	for(int e:b)
		System.out.print(e+" ");
}

结果:

基数排序也是稳定排序,它不是原址排序,时间复杂度O(d(n+k))(最坏和平均)。n是元素的个数,d是数据的位数,d一般不会很大。辅助空间n+k。那选择基数排序还是比较排序呢?不仅要比较d和logn的大小,还要比较空间的使用。


总结:

上面的程序里的参数还需要根据输入元素的区间范围再去修改,三种线性排序都是稳定排序。选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法;冒泡,插入,归并和三种线性排序都是稳定排序算法。

参考:

《算法导论》



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章