排序算法整理小結(基數排序,計數排序,桶排序)

    這次整理的爲三個原理上相近的排序算法,便於比較對比分析,通過他們之間的不同理解三個算法的特點,從而能夠設計出符合三者的算法。首先呢,這三個算法都不是基於比較的算法,這和前面的那些算法不同,其核心內容不再通過比較然後交換兩者的位置了,而這次要說的是在排序的時候將其順序和“桶”的順序相關聯起來,通過不同的排序規則,進行排序,然後按照順序進行輸出,這些就是此三類算法的大致流程。

計數排序

     解釋這一通是爲了有個大體印象,不至於在閱讀代碼的時候產生渾然不知。首先我們看一個較爲簡單的,叫做計數排序,這個排序可以很好的應用到整數排序,其主要的核心思想爲尋找出數組中最大和最小元素,然後開闢max-min大小的數組,然後對原數組進行遍歷,原數組某位置的元素值爲多少,則在新開闢的輔助數組中的對應位置(輔助數組下標爲原數組元素值)加1(可能有重複的數字),然後將其按照某一順序重新寫回原來的數組。來看下代碼是怎麼完成這個過程的:

public class CountSort {
    public static void countSort(int[] array, int size){
        
        int max_vaue = array[0];//序列中的最大值
        int min_value = array[0];//序列中的最小值

        //找到序列中的最大值和最小值
        for(int i = 0; i < size; i++){
            if(array[i] >= max_vaue) {
                max_vaue = array[i];
            }
            if(array[i] <= min_value){
                min_value = array[i];
            }
        }

        //計算輔助數組的長度並開闢相應的空間
        int range = max_vaue - min_value + 1;
        int[] helpCount = new int [range];

        //helpCount[array[i]-min_value]++; 將對應的數字放置
        //即求得array[i]相對於min_value值的距離是多少
        //到對應的位置
        for(int i = 0; i < size; i++){
            helpCount[array[i] - min_value] ++;
        }

        int index = 0;

        //遍歷輔助空間
        for(int i = 0; i < range; i++){
            while (helpCount[i] -- > 0){//下標是幾則說明出現了幾次
                //將下標處的數字拉回原來數組
                array[index++] = i + min_value;
            }
        }
    }
}

其實一點本質的東西就是將原數組的元素的值和help數組的下標值聯繫起來,因爲我們在進行比較排序的時候比較的也是元素的值,也即說明順序包含在原數組的值當中,所以我們改變一下策略,使得原數組元素值對因爲新數組的下標索引值。

好了,如果理解好了上面這個計數排序算法之後,再來一個桶排序吧,所謂的循序漸進嘛:

桶排序

    說道桶排序,其核心的思想有點和散列表有些向,有原來的一排桶中現在變爲了多排桶,桶的第一排來接受經過計算後被分配到這一列桶,後續插入的元素則在這一咧往下排列,這麼說抽象哈,可以參考下面的圖和博客。

https://www.cnblogs.com/ECJTUACM-873284962/p/6935506.html

    是不是很像定址法散列表的樣子?那該怎麼設計呢,參考下面的代碼:

public class BucketSort {

    public  void bucketSort(int[] array, int bucketSize){
        //用於存放排序後的結果
        int[] result = new int[array.length];

        //開闢bucket的大小爲bucketSize
        Node[] bucket = new Node[bucketSize];

        //初始化每個節點
        for(int i = 0 ; i < bucket.length; i++){
            bucket[i] = new Node();
        }

        //核心部分,將各個元素按照剛纔所示的圖的形式進行分組放入
        //桶中
        for(int i = 0; i < array.length; i++){
            int bucketIndex = hash(array[i]);
            Node node = new Node(array[i]);
            Node p = bucket[bucketIndex];

            if(p.next == null){//無節點的情況
                p.next = node;
            }else{
                //插入到連表中的情況,鏈表的插入操作還是比較快速的
                while (p.next != null && p.next.val <= node.val){
                    p = p.next;
                }
                //更新鏈表節點
                node.next = p.next;
                p.next = node;
            }
        }

        int j = 0;
        
        //將桶中的結果放入到結果數組中
        for(int i = 0; i < bucket.length; i++){
            for (Node p = bucket[i].next; p != null; p = p.next){
                result[j++] = p.val;
            }
        }

        //將結果拷貝回原數組
        for(int i = 0; i < array.length; i++){
            array[i] = result[i];
        }
    }

    //此哈希非彼哈希,這個主要是爲了能夠將元素“散列”到各個桶之中
    public int hash(int val){
        return  val / 10;
    }

    //定義節點Node類型
     class Node{
        public Node(){
            this(0);
        }
        public Node(int val){
            this.val = val;
        }
        private int val;
        private Node next;
    }
}

基數排序

     基數排序是一種來自於老式的穿卡機上的算法,一個卡片有80列,每一列可以在12個位置上穿孔,排序器可以被機器機械的“程序化”,其利用這樣的方式方便了老式機器對卡片進行檢查。對於我們通用的10進制數字來說,每一列中只用到10個位置,一個d位數字佔用d個列,因爲卡片器一次只能查看一個數列。直覺上可以利用高位數字進行判別,但是其是不然,因爲如果這樣排序的話,10個位置的9個必須放置到一邊,這個過程產生了較大的空間浪費,如果以低位進行,則會有效的解決這個問題。此時的基數排序則相當於對於數列先按照個位數進行排列,然後按照十位進行,然後不斷的進行到最高位(若沒有的話,用0代替)。對於此算法來說重要的問題就是排序算法一定要穩定。說了這麼多不如一個圖,也許你看下圖就明白了:

算法核心較爲簡單,但是如何設計這個算法呢,有這麼幾個地方需要注意:首先要注意排序的順序,是由低位到高位,其次還要注意算法的另一個問題,如何保存每次排序的結果,在一個就是,如何比較長度不同的數字,其次就是如何正確的將其放入到正確的桶的位置,可以參考一下代碼:

public class RadixSort {

    /**
     * 獲取array數組中的最大值
     * @param array   待排序數組
     * @return 返回數組中的最大值
     */
    private static int getMaxValue(int[] array){
        int max_value;

        max_value = array[0];

        for(int i = 1; i < array.length; i++){
            if(array[i] > max_value){
                max_value = array[i];
            }
        }
        return max_value;
    }

    public static void radixSortCore(int[] array, int exp){

        //開闢輔助數據,用於存放每次得到的排序結果
        int[] output = new int [array.length];
        //bucket用於存放每一位數字對應的在0-9的位置,其值
        //爲每個數字的數量
        int[] bucket = new int[10];
        //按照每次指定了exp(此爲對於某一位進行取餘然後尋找位置)
        for(int i = 0; i < array.length; i++){
            bucket[(array[i] / exp) % 10]++;
        }

        //此過程可以更改bucket,使得更改後的bucket數組中的值
        // 爲array數組中數據,在經過排序之後在output數組中的下標
        //需要注意的是,需要結合下面兩個for循環過程
        for(int i = 1; i < 10; i++){
            bucket[i] += bucket[i - 1];
        }

        //例如array = 12,34,63,79,86,45;則bucket中的索引值從0-9所對
        //對應的內容爲;0,0,1,1,1,1,1,0,0,1,經過上面的for循環後
        //可以得到0,0,1,2,3,4,5,5,5,6 此時,
        // [bucket[(array[array[i] / exp]) % 10] - 1,可以分析爲:若按照個位計算,
        //(array[i] / exp) % 10 爲bucket數組的索引,若i = 5 時,可以得到
        //(array[i] / exp) % 10 爲5,則bucket [(array[i] / exp) % 10]爲4
        //則output的索引值(array[i] / exp) % 10] - 1爲3,即output的第四個位置
        //而排序之後的結果爲:12,63,34,45,86,79。可以發現符合結果
        for (int i = array.length - 1; i >= 0; i--){
            output[bucket [(array[i] / exp) % 10] - 1 ] = array[i];
            //當有重複數字的時候有效,即當此位置的數字個數-1
            bucket[(array[i] / exp)% 10 ] --;
        }

        /**
         * 移動到原來的數組
         */
        for (int i = 0; i < array.length; i++){
            array[i] = output[i];
        }
        output = null;
        bucket = null;
    }

    public static void radix_sort(int[] array){
        int max = getMaxValue(array);

        for(int exp = 1; max / exp > 0; exp *= 10){
            radixSortCore(array,exp);
        }
    }
}

 

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