線性排序的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的大小,還要比較空間的使用。


總結:

上面的程序裏的參數還需要根據輸入元素的區間範圍再去修改,三種線性排序都是穩定排序。選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法;冒泡,插入,歸併和三種線性排序都是穩定排序算法。

參考:

《算法導論》



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