1. 程式人生 > >Java-十種內部排序實現(選擇,冒泡,插入,希爾,堆,歸併,快速,基數,計數,桶)及程式碼下載

Java-十種內部排序實現(選擇,冒泡,插入,希爾,堆,歸併,快速,基數,計數,桶)及程式碼下載

  1. 選擇排序
  2. 氣泡排序
  3. 插入排序
  4. 希爾排序
  5. 堆排序
  6. 歸併排序
  7. 快速排序
  8. 基數排序
  9. 計數排序
  10. 桶排序

1. 選擇排序

這個排序方法最簡單,廢話不多說,直接上程式碼:

public class SelectSort {
    /**
     * 選擇排序
     * 思路:每次迴圈得到最小值的下標,然後交換資料。
     * 如果交換的位置不等於原來的位置,則不交換。
     */
    public static void main(String[] args) {
        selectSort(Datas.data);
        Datas.prints("選擇排序"
); } public static void selectSort(int[] data){ int index=0; for (int i = 0; i < data.length; i++) { index = i; for (int j = i; j < data.length; j++) { if (data[index]>data[j]) { index = j; } } if
(index != i) { swap(data,index,i); } } } public static void swap(int[] data,int i,int j){ int temp = data[i]; data[i] = data[j]; data[j] = temp; } }

選擇排序兩層迴圈,第一個層迴圈遍歷陣列,第二層迴圈找到剩餘元素中最小值的索引,內層迴圈結束,交換資料。內層迴圈每結束一次,排好一位資料。兩層迴圈結束,資料排好有序。

2 氣泡排序
氣泡排序也簡單,上程式碼先:

public class BubbleSort {
    /**
     * 氣泡排序
     * 思路:內部迴圈每走一趟排好一位,依次向後排序
     */
    public static void main(String[] args) {
        bubbleSort(Datas.data);
    }

    private static void bubbleSort(int[] data) {
        int temp;
        for (int i = 0; i < data.length; i++) {
            for (int j = i+1; j < data.length; j++) {
                if (data[i]>data[j]) {
                    temp =data[i];
                    data[i]=data[j];
                    data[j] = temp;
                }
            }
        }
        Datas.prints("氣泡排序");
    }
}

氣泡排序和選擇排序有點像,兩層迴圈,內層迴圈每結束一次,排好一位資料。不同的是,資料像冒泡一樣,不斷的移動位置,內層迴圈結束,剛好移動到排序的位置。
這裡寫圖片描述
該圖對應上面的程式碼進行的說明,沒有用專門的畫圖工具,使用的是window的maspint,大家湊合著看哈^_^明白意思就成!

3 插入排序
插入排序也是簡單的排序方法,程式碼量不多,先看程式碼:

public class InsertSort {
    /**
     * 插入排序
     * 思路:將資料插入到已排序的陣列中。
     */
    public static void main(String[] args) {
        int[] data = Datas.data;
        int temp;
        for (int i = 1; i < data.length; i++) {
            temp = data[i];//儲存待插入的數值
            int j = i;
            for (; j>0 && temp<data[j-1]; j--) {
                data[j] = data[j-1];
                //如果帶插入的數值前面的元素比該值大,就向後移動一位
            }
            //內部迴圈結束,找到插入的位置賦值即可。
            data[j]=temp;
        }
        Datas.prints("插入排序");
    }
}

這裡寫圖片描述
該圖是上面插入排序的說明圖,插入排序,其過程就是其名字說明的一樣,將待排序的資料插入到已排序的資料當中。兩層迴圈,內層迴圈結束一次,插入排序排好一位資料。

4 希爾排序
希爾排序,也叫縮減增量排序,其中增量的設定影響著程式的效能。最好的增量的設定為1,3,5,7,11,。。。這樣一組素數,並且各個元素之間沒有公因子。這樣的一組增量 叫做Hibbard增量。使用這種增量的希爾排序的最壞清醒執行時間為θ(這裡寫圖片描述
當不使用這種增量時,希爾排序的最壞情形執行時間為θ(這裡寫圖片描述

這裡電腦列印這些太麻煩,乾脆手寫拍照啦哈哈哈哈。。。。。
好了,廢話不多說,上程式碼;

public class ShellSort {

    /**
     * 希爾排序(縮減增量排序)
     * 想想也不難。
     * 思路:三層迴圈
     * 第一層迴圈:控制增量-增量隨著程式的進行依次遞減一半
     * 第二層迴圈:遍歷陣列
     * 第三層迴圈:比較元素,交換元素。
     * 這裡需要注意的是:比較的兩個元素和交換的兩個元素是不同的。
     */
    public static void main(String[] args) {
        int[] data = Datas.data;
        int k;
        for (int div = data.length/2; div>0; div/=2) {
            for (int j = div; j < data.length; j++) {
                int temp = data[j];
                for (k=j; k>=div && temp<data[k-div] ; k-=div) {
                    data[k] = data[k-div];
                }
                data[k] = temp;
            }
        }
        Datas.prints("希爾排序");
    }
}

這裡寫圖片描述
程式中,需要注意的是第三層迴圈,第三層迴圈的程式碼中,if語句的比較和內部的交換是分別不同的兩個資料。原因是:把大的資料後移,小的資料前移,形成這樣一種趨勢,才能實現排序。
當然可以試試,if語句比較的兩個資料和內部移動的資料一致的話,會出現什麼問題?出現的問題就是移動的資料打破了之前形成的大的資料在後,小的資料在前的趨勢。無法排序。

5 堆排序

堆排序,要知道什麼是堆?說白了,堆就是完全二叉樹,堆是優先佇列。要求父元素比兩個子元素要大。這就好辦了。陣列元素構建堆,根節點最大,刪除根節點得到最大值,剩下的元素再次構建堆,接著再刪除根節點,得到第二大元素,剩下的元素再次構建堆,依次類推,得到一組排好序的資料。為了更好地利用空間,我們把刪除的元素不使用新的空間,而是使用堆的最後一位儲存刪除的資料。
程式碼上來:

public class HeapSort {
    /**
     * 堆排序(就是優先佇列)
     * 也就是完全二叉樹
     * 第一步:建堆.其實就是講陣列中的元素進行下慮操作,
     *      使得陣列中的元素滿足堆的特性。
     * 第二步:通過將最大的元素轉移至堆的末尾,
     *      然後將剩下的元素在構建堆。
     *      完成排序。
     * 最重要的過程就是構建堆的過程。
     * 裡面的比較思路和希爾排序中的比較思路一致。
     * 將大的元素上浮,小的元素下浮。始終和temp比較。
     * temp除了第一次比較可能改變外,其他次數的比較不改變該值。
     * 這樣的處理就是讓較大的元素趨於上浮,較小的元素下浮。
     */
    public static void main(String[] args) {
        int[] data = Datas.data;
        for (int i = data.length/2; i >=0; i--) {
            buildHeap(data,i,data.length);
        }
        Datas.prints("堆排序-構建樹");
        System.out.println("============================");
        for (int i = data.length-1; i>0; i--) {
            swap(data, 0, i);
            buildHeap(data, 0, i);
        }
        Datas.prints("堆排序-排序後");
    }
    static void swap(int[] data,int i,int j){
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
    static void buildHeap(int[] data,int i,int len){
        int leftChild = leftChild(i);
        int temp = data[i];
        for (; leftChild<len;) {
            if (leftChild != len-1 && data[leftChild]<data[leftChild+1]) {
                leftChild++;
            }
            if (temp<data[leftChild]) {
                data[i] = data[leftChild];
            }else {
                /**braek說明兩個兒子都比父節點小,
                * 父節點大於兩個兒子
                * 所以直接停止比較,減小比較的次數。
                */
                break;
            }
            i = leftChild;
            leftChild = leftChild(i);
        }
        data[i] = temp;
    }
    //返回節點i的左兒子的index
    static int leftChild(int i){
        return 2*i+1;
    }
}

這裡寫圖片描述
堆排序,最重要的就是構建堆,構建堆是核心!我們程式碼中使用的是陣列形式的二叉樹,也就是優先佇列。要真正看懂這部分的程式碼,需要知道優先佇列部分的知識,不難,看看就懂啦。說白了就是二叉樹。

6 歸併排序

歸併排序思路就是將兩個已經排好序的陣列插入到第三個陣列當中。核心就是將原有陣列分割兩部分,排好序,插入到第三個與原有陣列大小一致的陣列中。程式碼上來:

public class MergeSort {
    /**
     * 歸併排序
     * 思路:如果是兩個已排序的陣列,進行合併非常簡單。
     * 所以就對原有陣列進行分割,分割成各個排序的陣列,
     * 然後遞迴合併。
     */
    public static void main(String[] args) {
        int[] data = Datas.data;
        merge(data);
        Datas.prints("歸併排序");
    }
    public static void merge(int[] data){
        int[] temp = new int[data.length];
        merge0(data, temp, 0, data.length-1);
    }
    public static void merge0(int[] data,int[] temp,int left,int rigth){
        if (left<rigth) {
            int center = (left+rigth)/2;
            merge0(data, temp, left, center);
            merge0(data, temp, center+1, rigth);
            mergeSort(data,temp,left,center,rigth);
        }
    }
    public static void mergeSort(int[] data,int[] temp,int left,int center,int right){
        int leftEnd = center;
        int rightStar = center+1;
        int len = right-left+1;
        int tempPos = left;
        /**
         * 這裡的三個迴圈很容易理解。
         * 其實現實兩個已經排序的陣列進行比較,
         * 將元素新增到temp陣列中儲存。
         */
        while (left<=leftEnd&&rightStar<=right) {
            if (data[left]<=data[rightStar]) {
                temp[tempPos++] = data[left++];
            }else {
                temp[tempPos++] = data[rightStar++];
            }
        }
        while (left<=leftEnd) {
            temp[tempPos++]=data[left++];
        }
        while (rightStar<=right) {
            temp[tempPos++]=data[rightStar++];
        }
        /**
         * 關鍵的一步是下面的拷貝工作。
         * 為什麼陣列中的拷貝是從right--開始???
         * 原因是:通過說明圖中,我們知道,元素比較之後,
         * 會將元素賦值給temp陣列相對應的位置上,並不會影響其他位置的資料。
         * 並且下面的迴圈中也沒有使用其他位置上面的資料,僅僅拷貝
         * 本次已經排序的元素。
         * 下面的拷貝是從right開始,right位置是本次排序最右邊的元素
         * 其實也可以從left開始,只不過left在上面的排序中值已經改變,
         * 可以定義一個int leftFlag = left;儲存初始最左邊的位置,
         * 此時下面的迴圈可以改為:
         * for (int i = 0; i < len; i++,leftFlag++) {
         *  data[leftFlag]=temp[leftFlag];
         * }
         * 執行程式,你會發現,正確輸出結果。
         */
        for (int i = 0; i < len; i++,right--) {
            data[right]=temp[right];
        }
    }
}

這裡寫圖片描述

說明圖中已經說明了關於陣列分割,排序、歸併的步驟。歸併排序其實就是分割,排序,歸併,最後得到排序的結果。
排序要等到分割完成之後進行,歸併要等盜排序之後進行。
分割通過遞迴呼叫進行,排序通過程式中的三個while迴圈進行,即完成了歸併。最後將資料拷貝要原來的陣列中去。

7 快速排序
快速排序有點類似於歸併排序,其實也是分割,不同的是,快速排序的分割是按照中值進行分割的,所以中值的好壞影響著程式的效能。最常見的情形是三數中值分割法!!
該方法的思路是選取左,右、中三個數進行交換,把三個數中最小值放在左邊,中間值放在中間,最大值放在右邊,這樣以中值為界形成了兩部分,要注意這時候還沒有排序,只是進行了樞紐元的選取。中間值就是樞紐元!

然後以樞紐元為中心,分別交換左右兩側的資料,把大的資料放在樞紐元的右側,把小的資料放在樞紐元的左側,最終形成大致排序的兩組資料,樞紐元排好序,然後遞迴呼叫快速排序。
同時,為了移動資料方便,我們把樞紐元的位置放在right-1的位置。
大家可能要問了:為什麼把樞紐元放在right-1的位置呢?
原因是:right的位置放置的是比樞紐元大的數,在選取樞紐元的時候,我們把大的數放在right的位置,最小的數放在left的位置,把樞紐元放在right-1的位置,這樣當完成資料交換之後樞紐元只需一次交換。如果把樞紐元放在中間的位置,要知道的是,中間位置並不一定就是樞紐元要排序的位置。這個地方要搞清楚,還得看程式碼:

public class QuickSort {
    /**
     * 快速排序
     * 首先找到三數中值,然後分別移動左右兩邊的資料,
     * 以中值數分割成兩組,一組比中值數大,一組比中值數小。
     * 然後遞迴快排兩組陣列。
     * 當待排序的陣列小於CUTOFF時,使用插入排序。
     */
    public static void main(String[] args) {
        quickSort(Datas.data,0,Datas.data.length-1);
        Datas.prints("快速排序");
    }
    public static void quickSort(int[] data,int left,int right){
        int CUTOFF = 1;
        if (left+CUTOFF<right) {
            //找到中值數
            int media = media3(data, left, right);
            //儲存左右界,left,right值不變
            int i =left;
            int j = right-1;
            //迴圈移動左右兩邊的元素
            while (true) {
                while(data[++i]<media);
                while (data[--j]>media);
                if (i>j) {
                    break;
                }
                swap(data, i, j);
            }
            //將中值數移動到i處。中值數即排在i處。
            swap(data, i, right-1);
            //遞迴排序中值數兩邊的資料
            quickSort(data, left, i-1);
            quickSort(data, i+1, right);
        }else {
            /**
             * 插入排序
             * 當待排序的元素少於20個時候,
             * 快速排序效能不如直接插入排序好。
             * 所以else語句裡面,在待排序基本有序的情況下
             * 可以使用直接插入排序更好。
             */
            InsertSort.main(null);
        }
    }
    //找到中值數
    public static int media3(int[] data,int left,int right){
        int center = (left+right)/2;
        /**
         * 前兩個if語句的比較,
         * 使得最小值放在最左邊。
         */
        if (data[center]<data[left]) {
            swap(data, center, left);
        }
        if (data[right]<data[left]) {
            swap(data, right, left);
        }
        /**
         * 第三個if語句使得最大值放在最右邊。
         * 中間值,放在中間位置。
         */
        if (data[right]<data[center]) {
            swap(data, right, center);
        }
        //把中間的位置放在right-1的位置。
        swap(data, center, right-1);
        return data[right-1];
    }
    //交換資料
    public static void swap(int[] data,int i,int j){
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
}

這裡寫圖片描述

快速排序說明圖,示意了i、j遊標的移動位置。結合程式應該能看懂。
不過值得大家注意的是,程式中不僅僅使用快速排序的思路,而在最後,當left與right的差值在CUTOFF的時候,直接使用直接插入排序,不再使用快速排序。原因我在註釋中已經給出。
如果不使用CUTOFF時候的插入排序,最終的結果並不是我們想要的。如果僅僅使用快速排序得到最終結果,則程式碼是不正確的。
上面的程式碼必須在最後使用一次插入排序才能得到最終的結果。

8 基數排序

桶排序之前不瞭解,我看的《資料結構與演算法分析》一書中並沒有給出大量的講解,反而程式碼是通過例題的形式給出的。桶排序其實就是形成大的容器,通過比較資料各個位上的數進行排序。
別的不多說了,直接上程式碼:

public class RadixSort {
    /**
     * 基數排序
     * 二維陣列構成桶
     * 一維陣列記錄每個位存放的個數。
     * 每次構建桶完成,拷貝資料到原來的陣列中去。
     * 繼續下一輪桶的構建。
     * 分別個位,十位,百位。。。
     * 程式必須知道最大值的位數。
     */
    public static void main(String[] args) {
        radixSort(Datas.data,3);
        Datas.prints("基數排序");
    }
    public static void radixSort(int[] data,int maxLen){
        //maxLen表示最大值的長度
        //LSD最低位優先排序  MSD最高位優先排序    l從0開始 迴圈三次
        int k = 0;
        int n = 1;
        int[][] bucket = new int[10][data.length];//桶
        /**
         * 表示桶的每一行也就是每一位存放的個數
         */
        int[] orders = new int[10];
        int temp = 0;
        for (int l = 0; l < maxLen; l++) {
            for (int i = 0; i < data.length; i++) {
                temp = (data[i]/n)%10;
                bucket[temp][orders[temp]] = data[i];
                orders[temp]++;
            }
            //將桶中的數值儲存會原來的陣列中
            for (int i = 0; i < 10; i++) {
                for (int j = 0; j < orders[i]; j++) {
                    if (orders[i]>0) {
                        data[k]=bucket[i][j];
                        k++;
                    }
                }
                //拷貝完成清除記錄的個數,設為0
                orders[i]=0;
            }
            //n乘以10 取十位  百位的數值
            n*=10;
            k=0;
            //k值記錄拷貝資料到原有陣列中的位置,拷貝完成恢復0
        }
    }
}

這裡寫圖片描述
這個是例項,程式列印結果的話,不好看,只好手寫大家看效果。
bucket二維陣列存放原始資料。orders陣列存放每一位數存放的原始資料的個數。外層迴圈每執行一次,就把資料拷貝給原來的陣列。然後進行下一輪迴圈。分別進行個位、十位、百位、、、、的迴圈。這是從最低位開始排序。也有最高位開始進行的排序。

9 計數排序
這個直接上程式碼:

public class CuntingSort {

    /**
     * 計數排序
     * 思路:構建一個與待排序中最大值相同大小的陣列,
     * 該陣列存放待排序陣列中每個數字出現的個數。
     */
    public static void main(String[] args) {
        cunting(Datas.data, 333);
        Datas.prints("計數排序");
    }

    public static void cunting(int[] data,int max){
        int[] temp = new int[max+1];
        int[] result = new int[data.length];
        /**
         * 該迴圈設定初始值為0
         */
        for (int i = 0; i < temp.length; i++) {
            temp[i]=0;
        }
        /**
         * 該for語句迴圈遍歷原陣列,將陣列中元素出現的個數存放在
         * temp陣列中相對應的位置上。
         * temp陣列長度與最大值的長度一致。保證每個元素都有一個對應的位置。
         */
        for (int i = 0; i < data.length; i++) {
            temp[data[i]]+=1;
        }
        /**
         * 累計每個元素出現的個數。
         * 通過該迴圈,temp中存放原陣列中資料小於等於它的個數。
         * 也就是說此時temp中存放的就是對應的元素排序後,在陣列中存放的位置+1。
         */
        for (int i = 1; i < temp.length; i++) {
            temp[i]=temp[i]+temp[i-1];
        }
        /**
         * 這裡從小到大遍歷也可以輸出正確的結果,但是不是穩定的。
         * 只有從大到小輸出,結果才是穩定的。
         * result中存放排序會的結果。
         */
        for (int i = data.length-1; i>=0; i--) {
            int index = temp[data[i]];
            result[index-1]= data[i];
            temp[data[i]]--;
        }
        Datas.data = result;
    }
}

這個排序還真沒法畫圖,其實這個排序相當容易理解。找出待排序陣列中最大的元素,構建一個與最大元素數+1長度的陣列temp,這樣保證待排序陣列中的每一個元素都能在temp陣列中找到自己的位置,但是temp不是用來存放元素的,而是存放每個元素在待排序陣列中出現的個數。這一步通過第二個for迴圈得到。
接著第三個for迴圈,迴圈遍歷temp,目的就是得到每個元素排序後存放在原有陣列中的位置。大家想一想,每個位置存放自己出現的個數,那麼小於自己的元素出現的個數加到一起,即可得到自己排序後陣列中的存放位置。是不是真的很巧妙!!!!!
到此,基本就是該排序演算法的核心啦!!!

10 桶排序
桶排序是另外一種以O(n)或者接近O(n)的複雜度排序的演算法. 它假設輸入的待排序元素是等可能的落在等間隔的值區間內.一個長度為N的陣列使用桶排序, 需要長度為N的輔助陣列. 等間隔的區間稱為桶, 每個桶內落在該區間的元素. 桶排序是基數排序的一種歸納結果。
演算法的主要思想: 待排序陣列A[1…n]內的元素是隨機分佈在[0,1)區間內的的浮點數.輔助排序陣列B[0….n-1]的每一個元素都連線一個連結串列. 將A內每個元素乘以N(陣列規模)取底,並以此為索引插入(插入排序)陣列B的對應位置的連表中. 最後將所有的連結串列依次連線起來就是排序結果.

這個過程可以簡單的分步如下:
1、設定一個定量的陣列當作空桶子
2、尋訪序列,並且把專案一個一個放到對應的桶子去
3、對每個不是空的桶子進行排序
4、從不是空的桶子裡把專案再放回原來的序列中

例如要對大小為[1..1000]範圍內的n個整數A[1..n]排序,可以把桶設為大小為10的範圍,具體而言,設集合B[1]儲存[1..10]的整數,集合B[2]儲存(10..20]的整數,……集合B[i]儲存((i-1)*10, i*10]的整數,i = 1,2,..100。總共有100個桶。然後對A[1..n]從頭到尾掃描一遍,把每個A[i]放入對應的桶B[j]中。 然後再對這100個桶中每個桶裡的數字排序,這時可用冒泡,選擇,乃至快排,一般來說任何排序法都可以。最後依次輸出每個桶裡面的數字,且每個桶中的數字從小到大輸出,這樣就得到所有數字排好序的一個序列了。

/** 
     * 桶排序演算法,對arr進行桶排序,排序結果仍放在arr中 
     * @param arr 
     */  
    public static void main(String[] args){
        bucketSort(Datas.datad);
        for (int i = 0; i < Datas.datad.length; i++) {
            System.out.println(Datas.datad[i]+",");
        }
    }
    public static void bucketSort(double arr[]){  

        int n = arr.length;

        ArrayList<Double> arrList[] = new ArrayList[n];
        //把arr中的數均勻的的分佈到[0,1)上,每個桶是一個list,存放落在此桶上的元素   
        for(int i =0;i<n;i++){
            int temp = (int) Math.floor(n*arr[i]);
            if(null==arrList[temp])
                arrList[temp] = new ArrayList<>();
            arrList[temp].add(arr[i]);
        }  

        //對每個桶中的數進行插入排序   
        for(int i = 0;i<n;i++){  
            if(null!=arrList[i])  
                insert(arrList[i]);  
        }  

        //把各個桶的排序結果合併   
        int count = 0; 

        for(int i = 0;i<n;i++){  
            if(null!=arrList[i]){  
                Iterator<Double> iter = arrList[i].iterator();  
                while(iter.hasNext()){  
                    Double d = (Double)iter.next();  
                    arr[count] = d;
                    count++;  
                }  
            }  
        }  
    }  

    /** 
     * 用插入排序對每個桶進行排序 
     * @param list 
     */  
    public static void insert(ArrayList<Double> list){  
        if(list.size()>1){  
            for(int i =1;i<list.size();i++){  
                if((Double)list.get(i)<(Double)list.get(i-1)){  
                    double temp = (Double) list.get(i);  
                    int j = i-1;  
                    for(;j>=0&&((Double)list.get(j)>(Double)list.get(j+1));j--)  
                        list.set(j+1, list.get(j));  
                    list.set(j+1, temp);  
                }
            }
        }
    }
}

原文地址:http://www.tuicool.com/articles/3emMVz
這個是我看到的關於桶排序說的最好的一篇文章。
舉例說明:
假如待排序列K= { 49、 38 、 35、 97 、 76、 73 、 27、 49 }。這些資料全部在1—100之間。因此我們定製10個桶,然後確定對映函式f(k)=k/10。則第一個關鍵字49將定位到第4個桶中(49/10=4)。依次將所有關鍵字全部堆入桶中,並在每個非空的桶中進行快速排序後得到如下圖所示:
這裡寫圖片描述
對上圖只要順序輸出每個B[i]中的資料就可以得到有序序列了。
這個例子有點類似於基數排序。因此,可以說,桶排序是基數排序的一種。

網上很多講述基數排序,計數排序,桶排序的文章,但是很多都搞混了。如果大家希望看到最權威的講述就看《演算法導論》這本書。這本書專門一章講述基數排序,計數排序和桶排序。不過,演算法導論有點難呦~~

本文中全部程式碼下載,[**

**]