1. 程式人生 > >基數排序詳解以及java實現

基數排序詳解以及java實現

前言

基數排序(radix sort)又稱桶排序(bucket sort),相對於常見的比較排序,基數排序是一種分配式排序,即通過將所有數字分配到應在的位置最後再覆蓋到原陣列完成排序的過程。我在上一篇講到的計數排序也屬於這種排序模式,上一篇結尾處提到了計數排序的穩定性,即排序前和排序後相同的數字相對位置保持不變。今天我們要說的基數排序就要利用到排序穩定性這一點。

思考過程

我們回想一下我們小時候是怎麼學習比較數字大小的?我們是先比位數,如果一個位數比另一個位數多,那這個數肯定更大。如果位數同樣多,就按位數遞減依次往下進行比較,哪個數在這一位上更大那就停止比較,得出這個在這個位上數更大的數字整體更大的結論。當然我們也可以從最小的位開始比較,這其實就對應了基數排序裡的MSD(most significant digital)和LSD(least significant digital)兩種排序方式。

     想清楚了這一點之後,我們就要考慮如何儲存每一位排序結果的問題了,首先既然作為分配式排序,聯想計數排序,每一位排序時儲存該次排序結果的資料結構應該至少是一個長度為10的陣列(對應十進位制該位0-9的數字)。同時可能存在以下情況:原陣列中所有元素在該位上的數字都相同,那一維陣列就沒法滿足我們的需要了,我們需要一個10*n(n為陣列長度)的二維陣列來儲存每次位排序結果。熟悉計數排序結果的讀者可能會好奇:為什麼不能像計數排序一樣,在每個位置只儲存出現該數字的次數,而不儲存具體的值,這樣不就可以用一維陣列了?這個我們不妨先思考一下,在對基數排序分析完之後再來看這個問題。

      現在我們可以儲存每次位排序的結果了,為了在下一位排序前用到這一位排序的結果,我們要將桶裡排序的結果還原到原陣列中去,然後繼續對更改後的原陣列執行前一步的位排序操作,如此迴圈,最後的結果就是陣列內元素先按最高位排序,最高位相同的依次按下一位排序,依次遞推。得到排序的結果陣列。

演算法過程

  1. 初始化:構造一個10*n的二維陣列,一個長度為n的陣列用於儲存每次位排序時每個桶子裡有多少個元素。
  2. 迴圈操作:從低位開始(我們採用LSD的方式),將所有元素對應該位的數字存到相應的桶子裡去(對應二維陣列的那一列)。然後將所有桶子裡的元素按照桶子標號從小到大取出,對於同一個桶子裡的元素,先放進去的先取出,後放進去的後取出(保證排序穩定性)。這樣原陣列就按該位排序完畢了,繼續下一位操作,直到最高位排序完成。

下面給出一個例項幫助理解:

我們現有一個數組:73, 22, 93, 43, 55, 14, 28, 65, 39, 81

下面是排序過程(二維數組裡每一列對應一個桶,因為桶空間沒用完,因此沒有將二維陣列畫全):

1.按個位排序

0

1

2

3

4

5

6

7

8

9

81

22

73

14

55

28

39

93

65

43

按第一位排序後陣列結果:

81,22,73,93,43,14,55,65,28,39

可以看到陣列已經按個位排序了。

2根據個位排序結果按百位排序

0

1

2

3

4

5

6

7

8

9

14

22

39

43

55

65

73

81

93

28

取出排序結果:

14,22,28,39,43,55,65,73,81,93

可以看到在個位排序的基礎上,百位也排序完成(對於百位相同的數子,如22,28,因為個位已經排序,而取出時也保持了排序的穩定性,所以這兩個數的位置前後是根據他們個位排序結果決定的)。因為原陣列元素最高只有百位,原陣列也完成了排序過程。

總結

我們現在來看看之前遺留的兩個問題:為什麼不能用一維陣列,一定要用二維陣列這樣的類似桶的結構來儲存中間位排序結果?其實之所以要寫這個問題,是因為我覺得這個問題是理解基數排序的關鍵。基數排序本身原理很簡單,但是實現中有兩個問題需要考慮:1.怎麼保留前一位的排序結果,這個問題用之前提到的排序穩定性可以解決。2.怎麼關聯該位排序結果和原陣列元素,二維陣列正是為了解決這個問題使用的辦法。在計數排序裡,雖然保留了所有相等的元素的相對位置,但是這些相等的元素在計數排序裡實際是沒有差別的,因此我們可以只儲存數組裡有多少個這樣的元素即可。而基數排序裡不同,有些元素雖然在某一位上相同,但是他們其他位上很可能不同,如果只儲存該位上有多少個5或者多少個6,那關於元素其他位的資訊就都丟棄了,這樣也就沒法對這些元素更高位進行排序了。

     弄清基數排序的過程後,我們來看看這個演算法的時間複雜度是多少?每次迴圈遍歷陣列將元素放在指定位置Θ(n),在從桶中取出資料Θ(n),迴圈d次(d是位數),時間複雜度就是Θ(r*n)

         最後附上基數排序的java實現:

package sort;

public class RadixSort {
private static void radixSort(int[] array,int d)
{
    int n=1;//代表位數對應的數:1,10,100...
    int k=0;//儲存每一位排序後的結果用於下一位的排序輸入
    int length=array.length;
    int[][] bucket=new int[10][length];//排序桶用於儲存每次排序後的結果,這一位上排序結果相同的數字放在同一個桶裡
    int[] order=new int[length];//用於儲存每個桶裡有多少個數字
    while(n<d)
    {
        for(int num:array) //將陣列array裡的每個數字放在相應的桶裡
        {
            int digit=(num/n)%10;
            bucket[digit][order[digit]]=num;
            order[digit]++;
        }
        for(int i=0;i<length;i++)//將前一個迴圈生成的桶裡的資料覆蓋到原陣列中用於儲存這一位的排序結果
        {
            if(order[i]!=0)//這個桶裡有資料,從上到下遍歷這個桶並將資料儲存到原陣列中
            {
                for(int j=0;j<order[i];j++)
                {
                    array[k]=bucket[i][j];
                    k++;
                }
            }
            order[i]=0;//將桶裡計數器置0,用於下一次位排序
        }
        n*=10;
        k=0;//將k置0,用於下一輪儲存位排序結果
    }
    
}
public static void main(String[] args)
{
    int[] A=new int[]{73,22, 93, 43, 55, 14, 28, 65, 39, 81};
    radixSort(A, 100);
    for(int num:A)
    {
        System.out.println(num);
    }
}
}

下面是程式執行結果: