算法學習之線性時間排序之基數排序,計數排序及java實現 – JAVA編程語言程序開發技術文章

先介紹一下概念
  計數排序和基數排序都是非比較排序,就是不用進行比較就能排序,相對於堆排序,快速排序,插入排序等都是比較排序,比較排序算法的最壞情況下屆都要做0(nlgn)次的比較,堆排序和合並排序都是漸近最有的比較排序算法,線性時間排序的時間復雜度都是O(n)。
  計數排序的基本思想,假設n個輸入元素中的每一個都是介於0到k的整數,此處k為某個整數。當k=O(n)時,計數排序運行時間是O(n),計數排序基本思想是對每一個輸入元素x,確定出小於x的元素個數,這樣可以把x直接放在最終輸出的位置上。例如,有15個元素小於x,那麼x就放在第18個輸出位置。當有元素相同時,放完後小於個數要減1。
  另外需要兩個數組,存放最終結果的數組和存放臨時存儲的數組(即小於x的個數)。
  它是穩定的。具有相同值的元素在輸出數組中的相對次序與他們在輸入數組的次序相同。

  基數排序的基本思想,基數排序可以說是擴展瞭的桶式排序,比如當待排序列在一個很大的范圍內,比如0到999999內,那麼用桶式排序是很浪費空間的。而基數排序把每個排序碼拆成由d個排序碼,比如任何一個6位數(不滿六位前面補0)拆成6個排序碼,分別是個位的,十位的,百位的。。。。
  一般有兩種方式:
  1) 高位優先(MSD): 從高位到低位依次對序列排序
  2)低位優先(LSD): 從低位到高位依次對序列排序
  計算機一般采用低位優先法(人類一般使用高位優先),但是采用低位優先時要確保排序算法的穩定性。
  基數排序借助桶式排序,每次按第N位排序時,采用桶式排序。對於如何安排每次落入同一個桶中的數據有兩種安排方法:
  1)順序存儲:每次使用桶式排序,放入r個桶中,,相同時增加計數。
  2)鏈式存儲:每個桶通過一個靜態隊列來跟蹤。
  舉個例子:
  現在有數組:2,10,78,189,354,8,756,390,356。第一次根據各位數將數組劃分為10個隊列(當然其中的某些隊列可能不含有元素)
  0:10,390
  1:
  2:2
  3:
  4:354
  5:
  6:756,356
  7:
  8:78,8
  9:189
  然後收集成序列:
  10,390,2,354,756,356,78,8,189
  在進行第二次分組:
  0:2,8
  1:10
  2:
  3:
  4:
  5:354,756,356
  6:
  7:78
  8:189
  9:390
  第二次收集:
  2,8,10,354,756,356,78,189,390
  第三次分配:
  0:2,8,10,78
  1:189
  2:
  3:354,356,390
  4:
  5:
  6:
  7:756
  8:
  9:
  最後得到序列:2,8,10,78,189,354,356,390,756。完成排序!
  基數排序其實是利用多關鍵字先達到局部有序,再調整達到全局有序。
  雖然根據常見情況,有b=O(lgn),基數排序運行時間為Θ(n),看上去比快排的平均Θ(nlgn)更好吧。不過,隱含在Θ符號中的常數因子是不同的。對於要處理的n個關鍵字,盡管基數排序執行的遍數比快排少,但每一遍的時間要長,所以,哪一個排序更好,取決於底層機器的實現特性(比如,快排通常可以比基數更好的利用硬件緩存),同時還取決於輸入數據。此外,利用計數排序作為中間穩定排序的基數排序不是原地排序,而很多 Θ(nlgn)的比較排序算法則可以做到原地排序。因此,內存容量寶貴時,像快排這樣的原地排序算法也許更可貴。(來自算法導論)
  桶排序:其基本思想:將區間[0,1]劃分為n個相同大小的子區間,也叫桶,然後將n個輸入分佈到桶中。由於輸入均勻,所以,一般不會有一個桶有很多數的情況。輸出時,先將每個桶進行排序,然後依次輸出各桶元素。其偽代碼如下:

  代碼寫的比較詳細,不多說,具體看代碼,註釋都說的比較清楚瞭。

  具體實現:
  
[java]
package sort; 
 
import java.util.*; 
 
public class RadixSort { 
    /**
     * 基數排序算法思想: 
     * 先找出最大的數有幾位數,這樣就確定瞭要進行幾次排序, 
     * 然後建立10個數組進行存放第n位數為0,1,2…9的數字,
     * 存放完之後要進行收集10個數組中的數字到原來的數組中,
     * 然後在進行排序 
     * 第i位數為0,1,2…9的計算方法:array[j] % Math.pow(10, i+1) / Math.pow(10,i)
     * 即第一位數直接除以10的餘數就行瞭,第二位數,則除以100得餘數,再除以10得商即為第二位數,以此類推。
     * 
     * @param array
     *            要進行基數排序的數組
     */ 
    public void radixSort(int[] array) { 
        int max, time; 
        // 確定最大的數有幾位數,確定排序的次數 
        max = array[0]; 
        for (int i = array.length – 1; i > 0; i–) { 
            if (max < array[i]) { 
                max = array[i]; 
            } 
        } 
        time = 0; 
        while (max > 0) { 
            max /= 10; 
            time++; 
        } 
        // 建立10個數組 
        ArrayList<Integer>[] quene = new ArrayList[10]; 
        for (int i = 0; i < 10; i++) { 
            quene[i] = new ArrayList<Integer>(); 
        } 
 
        // 分別進行time次分配和收集數組,根據不同的位數進行分配到數組中,再收集到原來的數組中 
        for (int i = 0; i < time; i++) { 
            for (int j = 0; j < array.length; j++) { 
                int index = (int) (array[j] % (int) Math.pow(10, i + 1) / Math 
                        .pow(10, i)); 
                quene[index].add(array[j]); 
            } 
            // 收集完瞭之後進行分配 
            int count = 0; 
            for (int k = 0; k < 10; k++) { 
                while (quene[k].size() > 0) { 
                    array[count] = quene[k].remove(0); 
                    count++; 
                } 
            } 
        } 
    } 
 
    /**
     * 計數排序基本思想: 
     * 計算數組中每個元素x中比x小的元素個數, 
     * 然後就可以把x放在相應位置上 
     * 如:有15個數小於x,則x位於第16個位置上。
     * 
     * @param data
     *            待排序數組
     * @param k
     *            排序數組中最大的數
     */ 
    public int[] countingSort(int[] data, int k) { 
        int result[] = new int[data.length]; 
        int[] temp = new int[k + 1]; 
        // 先初始化臨時數組,即保存小於元素x的個數的數組 
        for (int i = 0; i < temp.length; i++) { 
            temp[i] = 0; 
        } 
        // temp [i]即包含等於i的元素個數 
        for (int i = 0; i < data.length; i++) { 
            temp[data[i]] = temp[data[i]] + 1; 
        } 
        // temp [i]即包含小於等於i的元素個數 
        for (int i = 1; i < temp.length; i++) { 
            temp[i] = temp[i] + temp[i – 1]; 
        } 
        // 將結果放入result數組,A[j]直接放在最終位置(小於等於A[j]的個數加1的位置) 
        // temp[data[j]]+1上,由於數組下標和位置相差1 
        for (int j = data.length – 1; j >= 0; j–) { 
            int index = temp[data[j]]; 
            result[index – 1] = data[j]; 
            temp[data[j]]–; 
        } 
        return result; 
    } 
 
    /**
     * 得到數組中的最大數,用於計數排序
     * 
     * @param data
     *            待計算數組
     * @return 返回最大數
     */ 
    public int getMax(int[] data) { 
        int max = data[0]; 
        for (int i = data.length – 1; i > 0; i–) { 
            if (max < data[i]) { 
                max = data[i]; 
            } 
        } 
        return max; 
    } 
 
    public static void main(String[] args) { 
        RadixSort redix = new RadixSort(); 
        int[] testData = { 2,10,78,189,354,8,756,390,356 }; 
        // 基數排序測試 
        System.out.println("***基數排序測試***"); 
        redix.radixSort(testData); 
        for (int data : testData) { 
            System.out.print(data + ","); 
        } 
 
        // 計數排序測試 
        System.out.println("\n***計數排序測試***"); 
        int[] testData2 = { 9, 3, 5, 3, 6, 5, 3, 7, 4, 9 }; 
        int max = redix.getMax(testData2); 
        int[] countSort = redix.countingSort(testData2, max); 
        for (int data : countSort) { 
            System.out.print(data + ","); 
        } 
    } 
 

  結果截圖如下:

  

發佈留言