三分鐘搞懂桶排序

bigsai發表於2020-07-29

前言

在資料結構與演算法的排序中,我們很多人可能更多的熟悉氣泡排序、快速排序、歸併排序。可能對堆排序、桶排序、計數排數等比較生疏,其實這個也沒啥複雜的,演算法的排序中,我們很多人可能更多的熟悉氣泡排序、快速排序、歸併排序。可能對堆排序、桶排序、計數排數等比較生疏,其實這個也沒啥複雜的,桶排序是所有排序中最簡單的排序之一。 沒毛病老鐵,就是最簡單的之一。 並且桶排序和計數排序基數排序有很多相似和淵源之處。後面會進行對比分析記得先關注!

桶排序思想

其實桶排序重要的是它的思想,而不是具體實現,桶排序從字面的意思上看:

  • 桶:若干個桶,說明此類排序將資料放入若干個桶中。
  • 桶:每個桶有容量,桶是有一定容積的容器,所以每個桶中可能有多個元素。
  • 桶:從整體來看,整個排序更希望桶能夠更勻稱,即既不溢位(太多)又不太少。

但是這些桶跟排序又有什麼關係呢?
首先先說下桶排序的思想,百度百科是這麼說的

工作的原理是將陣列分到有限數量的桶子裡。每個桶子再個別排序(有可能再使用別的排序演算法或是以遞迴方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種歸納結果。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並不是 比較排序,他不受到 O(n log n) 下限的影響。

用通俗易懂的話來理解:

  • 將待排序的序列分到若干個桶中,每個桶內的元素再進行個別排序。
  • 時間複雜度最好可能是線性O(n),桶排序不是基於比較的排序

當然,桶排序是一種用空間換取時間的排序。

既然是排序,那麼最終的結果肯定是從小到大的,桶排序藉助桶的位置完成一次初步的排序——將待排序元素分別放至各個桶內。

而我們通常根據待排序元素整除的方法將其較為均勻的放至桶中,如8 5 22 15 28 9 45 42 39 19 27 47 12這個待排序序列,假設放入桶編號的規則為:n/10。這樣首先各個元素就可以直接通過整除的方法放至對應桶中。而右側所有桶內資料都比左側的要大!

在剛剛放入桶中的時候,各個桶的大小相對可以確定,右側都比左側大,但桶內還是無序的,對各個桶內分別進行排序,再依次按照桶的順序、桶內序列順序得到一個最終排序的序列。

以上就是桶排序在演算法上的思想了。如果使用java具體實現的話思路也很簡單:用List[]型別的集合陣列表示桶,每個List代表一個桶,將資料根據整除得到的值直接放到對應編號的集合裡面去,再依次進行排序就可以了。

桶排序演算法分析

上面講了桶排序的具體思想,但是你是不是總覺得心理沒那麼踏實呢,這就完了?總覺得怪怪的是吧?


桶排序確實有很多不一樣的地方,無論是演算法時間複雜度還是整個演算法的流程,都不如啥快排啦、歸併啦這些傳統排序來的實在。

時間複雜度分析

桶排序的時間複雜度到底是多少?

我們假設有n個待排序數字。分到m個桶中,如果分配均勻這樣平均每個桶有n/m個元素。首先在這裡我鄭重說明一下桶排序的演算法時間複雜度有兩部分組成:

  • 1.遍歷處理每個元素,O(n)級別的普通遍歷
  • 2.每個桶內再次排序的時間複雜度總和

對於第一個部分,我想大家都應該理解最後排好序的取值遍歷一趟的O(n);而第二部分我們們可以進行這樣的分析:

  • 如果桶內元素分配較為均勻假設每個桶內部使用的排序演算法為快速排序,那麼每個桶內的時間複雜度為(n/m) log(n/m)。有m個桶,那麼時間複雜度為m * (n/m)log(n/m)=n (log n-log m).

    所以最終桶排序的時間複雜度為:O(n)+O(n*(log n- log m))=O(n+n*(log n -log m)) 其中m為桶的個數。我們有時也會寫成O(n+c),其中c=n*(log n -log m);

在這裡如果到達極限情況n=m時。就能確保避免桶內排序,將數值放到桶中不需要再排序達到O(n)的排序效果,當然這種情況屬於計數排序,後面再詳解計數排序記得再回顧。

桶排序適用情況

桶排序並且像常規排序那樣沒有限制,桶排序有相當的限制。因為桶的個數和大小都是我們人為設定的。而每個桶又要避免空桶的情況。所以我們在使用桶排序的時候即需要對待排序數列要求偏均勻,又要要求桶的設計兼顧效率和空間。

待排序序列要求均勻?我要不均勻又會怎麼樣呢?
會這樣:

這樣其實相當於只用了有效的很少個數桶,而再看桶排序的時間複雜度:O(n+n*(log n -log m))m取向1,log m去向0.整個複雜度變成O(n+nlogn)從級別來看就是O(nlogn),你瞅瞅你瞅瞅,這種情況就跟沒用桶一樣,就是快排(或其他排序)的時間複雜度。

那,那不能我搞100000個桶嘛?
不可以,真的不可以,如果100000個桶,你看看會造成什麼情況:

這才短短不到100個數,你為了一一對映用100000個空間,空間內容浪費不說,你遍歷雖然O(n)也是100000次也比100個的O(nlogn)大上很多啊,真是折了夫人又折兵。

所以現在明白前面說的話了吧:數要相對均勻分佈,桶的個數也要合理設計。總之桶排序是一種用空間換取時間的排序。在設計桶排序,你需要知道輸入資料的上界和下界,看看資料的分佈情況,再考慮是否用桶排序,當然如果能用好桶排序,效率還是很高的!

實現一個桶排序

在這裡用java給大家實現一個桶排序。假設序列為:1 8 7 44 42 46 38 34 33 17 15 16 27 28 24;我們選用5個桶進行桶排序。

import java.util.ArrayList;
import java.util.List;
//微信公眾號:bigsai
public class test3 {
	public static void main(String[] args) {
		int a[]= {1,8,7,44,42,46,38,34,33,17,15,16,27,28,24};
		List[] buckets=new ArrayList[5];
		for(int i=0;i<buckets.length;i++)//初始化
		{
			buckets[i]=new ArrayList<Integer>();
		}
		for(int i=0;i<a.length;i++)//將待排序序列放入對應桶中
		{
			int index=a[i]/10;//對應的桶號
			buckets[index].add(a[i]);
		}
		for(int i=0;i<buckets.length;i++)//每個桶內進行排序(使用系統自帶快排)
		{
			buckets[i].sort(null);
			for(int j=0;j<buckets[i].size();j++)//順便列印輸出
			{
				System.out.print(buckets[i].get(j)+" ");
			}
		}	
	}
}

列印結果為:

結語

至此,桶排序就講完了,當然本文可能有地方講的不好或者擁有紕漏之處還請大佬指出,後面還會講解計數排序、基數排序並且將三者進行歸納總結!

筆者微信公眾號:bigsai 一個有趣的小夥子,時常會通過公眾號和大家做一些有趣的專案和活動,歡迎關注,您的關注是我源源不斷的動力。

相關文章