1. 程式人生 > >基本排序算法(冒泡排序 選擇排序 插入排序 快速排序 歸並排序 基數排序 希爾排序)

基本排序算法(冒泡排序 選擇排序 插入排序 快速排序 歸並排序 基數排序 希爾排序)

找到 too 原理 int 十個 尋找 ble pri 第四次

冒泡排序:兩兩比較,大數冒泡

原理:

比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
每一輪對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最後一對。第一輪結束,最後的元素應該會是最大的數。
針對所有的元素重復以上的步驟n-1輪,分別倒序排好倒數第二大、倒數第三大……的元素。直到沒有任何一對數字需要比較。
形象排序:

原始數據: 6 2 8 5 1
第一輪: 2 6 5 1 |8
第二輪: 2 5 1 |6 8
第三輪: 2 1 |5 6 8
第四輪: 1 |2 5 6 8

1
2
3
4
5
具體解釋:

第一輪中:
第一次:比較第一個和第二個元素(即6和2),發現6比2大,則交換6和2(目前數字排列為 2 6 8 5 1)
第二次:比較第二個和第三個元素(即6和8),發現6比8小,則保持原樣(目前數字排列為 2 6 8 5 1)
第三次:比較第三個和第四個元素(即8和5),發現8比5大,則交換8和5(目前數字排列為 2 6 5 8 1)
第四次:比較第四個和第五個元素(即8和1),發現8比1大,則交換8和1(目前數字排列為 2 6 5 1 8)
最後:這樣,第一輪就把最大的元素放到了最右邊

最後一個元素已經有序,則第二輪不用比較最後一個元素和倒數第二個元素的大小(目前數字排列為 2 6 5 1 |8)

第二輪中:
第一次:比較第一個和第二個元素(即2和6),發現2比6小,則保持原樣(目前數字排列為 2 6 5 1 |8)
第二次:比較第二個和第三個元素(即6和5),發現6比5大,則交換6和5(目前數字排列為 2 5 6 1 |8)
第三次:比較第三個和第四個元素(即6和1),發現6比1大,則交換6和1(目前數字排列為 2 5 1 6 |8)
最後:這樣,第二輪就把倒數第二大的元素放到了倒數第二的位置

倒數兩個元素已經有序,則第三輪不用比較倒數第三個元素和倒數第二個元素的大小(目前數字排列為2 5 1 |6 8)
第三輪中:
第一次:比較第一個和第二個元素(即2和5),發現2比5小,則保持原樣(目前數字排列為 2 5 1 |6 8)

第二次:比較第二個和第三個元素(即5和1),發現5比1大,則交換5和1(目前數字排列為 2 1 5 |6 8)
最後:這樣,第三輪就把倒數第三大的元素放到了倒數第三的位置

倒數三個元素已經有序,則第四輪不用比較倒數第四個元素和倒數第三個元素的大小(目前數字排列為2 1 |5 6 8)
第四輪中:
第一次:比較第一個和第二個元素(即2和1),發現2比1大,則交換2和1(目前數字排列為 1 2 |5 6 8)
最後:這樣,第四輪就把倒數第四大的元素放到了倒數第四的位置,所有的元素就按升序排好了

升序:

技術分享圖片
public static void bubbleSort(int[] arr){
        int lgn = arr.length;
        for (int i = 0; i < lgn - 1; i++) {
            for (int j = 0; j < lgn - 1 - i; j++) {
                if(arr[j] > arr[j + 1]){
                    int temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }
技術分享圖片

降序:

...

選擇排序:選擇剩余元素中最小(最大)的元素放置到初始選擇集合中(空)

1. 選擇排序法
(如果不想看解釋分析,直接往後拉看代碼)

實質:

第一輪:通過對比數組中前一個元素和後一個元素的大小,先找到數組中最小的數字,並且記錄它的下標。如果標記的下標不是第一個元素的下標,則交換兩個元素。

第二輪:從第二個元素開始(因為第一個元素已經是第一輪排好的“有序”元素),對比數組中前一個元素和後一個元素的大小,遍歷尋找數組中第二小的數字,並記錄它的下標。如果標記的下標不是第二個元素的下標,則交換兩個元素。

第三輪:從第三個元素開始(因為第一和第二個元素已經是第一、二輪排好的“有序”元素),對比數組中前一個元素和後一個元素的大小,遍歷尋找數組中第三小的數字,並記錄它的下標。如果標記的下標不是第三個元素的下標,則交換兩個元素。

第四輪:從第四個元素開始(因為第一、第二、第三個元素已經是第一、二、三輪排好的“有序”元素),對比數組中前一個元素和後一個元素的大小,遍歷尋找數組中第四小的數字,並記錄它的下標。如果標記的下標不是第四個元素的下標,則交換兩個元素。

第五輪:沒有第五輪(因為一共就五個數,排好四個數,自然就全部排好了,再多排一輪浪費了)

形象排序:

原始數據: |6 2 8 5 1
第一輪: 1 |2 8 5 6
第二輪: 1 2 |8 5 6
第三輪: 1 2 5 |8 6
第四輪: 1 2 5 6 |8
1
2
3
4
5
具體解釋:
先假設第一個數字6為最小的數字,那麽記錄它的下標(index=0)

第一輪中:
第一次:先比較6和2(即標記的數和後面一個元素比較),發現2更小,則記錄2的下標(index=1)
第二次:比較2和8(標記的數和後面一個元素比較),發現還是2小,則下標還是2的下標不變(index=1)
第三次:比較2和5(標記的數和更後面的數比較),發現還是2小,則下標還是2的下標不變(index=1)
第四次:比較2和1(標記的數和更後面的數比較),發現1比2小,則標記改為1的下標(index=4)
最後:index並不等於第一個元素的下標(0),則交換第一個元素和被標記的元素

第一個元素已經有序,則從第二個元素開始(並且把標記index初始化為1,代表第二個元素2)
第二輪中:
第一次:先比較2和8,還是2小,則下標還是2的下標不變(index=1)
第二次:比較2和5,還是2小,則下標還是2的下標不變(index=1)
第三次:比較2和6,還是2小,則下標還是2的下標不變(index=1)
最後:index等於第二個元素的下標(1),則不用交換第二個元素和被標記的元素

第一、二個元素(1和2)已經有序,則從第三個元素開始(並且把標記index初始化為2,代表第三個元素8)
第三輪中:
第一次:先比較8和5,發現5比8小,則標記改為5的下標(index=3)
第二次:比較5和6,還是5小,則下標還是5的下標不變(index=3)
最後:index並不等於第三個元素的下標(2),則交換第三個元素和被標記的元素

第一、二、三個元素(1和2和5)已經有序,則從第四個元素開始(並且把標記index初始化為3,代表第四個元素8)
第四輪中:
第一次:先比較8和6,發現6比8小,則標記改為6的下標(index=4)
最後:index並不等於第四個元素的下標(3),則交換第四個元素和被標記的元素

技術分享圖片
public static void SelectionSortAsc(int[] arr){
int min = 0;
for (int i = 0; i < arr.length - 1; i++) {
min = i;
for (int j = i + 1; j < arr.length ; j++) {
if(arr[j] < arr[min]){
min = j;
}
}
if(min != i){
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
}

public static void SelectionSortDesc(int[] arr){
int max = 0;
for (int i = 0; i < arr.length - 1; i++) {
max = i;
for (int j = i + 1; j < arr.length ; j++) {
if(arr[j] > arr[max]){
max = j;
}
}
if(max != i){
int tmp = arr[i];
arr[i] = arr[max];
arr[max] = tmp;
}
}
}
技術分享圖片

插入排序:設定一個初始已排序的集合(一般選擇一個元素),從剩余的集合中將各個元素以此插入到初始集合中的正確位置

原理:
插入排序法通過把數組中的元素插入到適當的位置來進行排序:

先假設第一個元素有序,則第一輪從第二個元素開始,作為待插入的數,往前依次比較,看往哪裏插
第二輪把下一個元素(第三個)插入到其對應於已排序元素的排序位置
對於數組中的每個元素重復2步驟。即把第四個元素插入到適當位置,然後是第5個元素,等等。
形象排序:

原始數據: 6| 2 8 5 1
第一輪: 2 6| 8 5 1
第二輪: 2 6 8| 5 1
第三輪: 2 5 6 8| 1
第四輪: 1 2 5 6 8|
1
2
3
4
5
具體解釋:

假設第一個元素6是有序的,並且定義待插入的數int inserter=a[i],和定義下標index=i-1,用此下標來讓插入點與對應數比較

因為第一個數假設是有序的,則從第二個數開始作為待插入的數(inserter=a[1])
第一輪中:
第一次:把inserter與第一個元素比較(即2與6),發現2比6小,則把第一個元素後挪一個位置(目前數字排列為 6 6| 8 5 1)
最後:把inserter中保留的待插入的數插入到相應位置(目前數字排列為 2 6| 8 5 1)

前面兩個元素已經有序,則第二輪把第三個元素插到有序元素中的適當位置,則實現前三個元素有序(目前數字排列為 2 6| 8 5 1)
第二輪中:(保存第三個元素inserter=a[2])
第一次:把inserter與第二個元素比較(即8與6),發現8比6大,則把第二個元素不做後挪(目前數字排列為 2 6 8| 5 1)
第二次:由於8比6大,所以肯定比2大,所以不需要再比了
最後:把inserter中保留的待插入的數插入到相應位置(對於本題,則還是原位置)(目前數字排列為 2 6 8| 5 1)

前面三個元素已經有序,則第三輪把第四個元素插到有序元素中的適當位置,則實現前四個元素有序(目前數字排列為 2 6 8| 5 1)
第三輪中:(保存第四個元素inserter=a[3])
第一次:把inserter與第三個元素比較(即5與8),發現5比8小,則把第三個元素後挪一個位置(目前數字排列為 2 6 8 8| 1)
第二次:把inserter與第二個元素比較(即5與6),發現5比6小,則把第二個元素後挪一個位置(目前數字排列為 2 6 6 8| 1)
第三次:把inserter與第一個元素比較(即5與2),發現5比2大,則把第一個元素不做後挪(目前數字排列為 2 6 6 8| 1)
最後:把inserter中保留的待插入的數插入到相應位置(目前數字排列為 2 5 6 8| 1)

前面四個元素已經有序,則第四輪把第五個元素插到有序元素中的適當位置,則實現前五個元素有序(目前數字排列為 2 5 6 8| 1)
第五輪中:(保存第五個元素inserter=a[4])
第一次:把inserter與第四個元素比較(即1與8),發現1比8小,則把第四個元素後挪一個位置(目前數字排列為 2 5 6 8 8|)
第二次:把inserter與第三個元素比較(即1與6),發現1比6小,則把第三個元素後挪一個位置(目前數字排列為 2 5 6 6 8|)
第三次:把inserter與第二個元素比較(即1與5),發現1比5小,則把第二個元素後挪一個位置(目前數字排列為 2 5 5 6 8|)
第四次:把inserter與第一個元素比較(即1與2),發現1比2小,則把第一個元素後挪一個位置(目前數字排列為 2 2 5 6 8|)
最後:把inserter中保留的待插入的數插入到相應位置(目前數字排列為 1 2 5 6 8|),完成排序

技術分享圖片
public static void insertionSort(int [] array){
        for(int i = 1; i < array.length; i++){
            int temp = array[i];//被標記的值或者說是當前需要插入的值
            int j = i;
            //如果輪循值大於被標記值則往後移
            while( j > 0 && temp < array[j - 1]){
                array[j] = array[j - 1];
                j-- ;
            }
            //將被標記值插入最終移出的空位置
            array[j] = temp;
        }
    }
技術分享圖片

快速排序:選取錨點,劃分小於,大於兩個集合,遞歸處理子集合

原理:
通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

先假設第一個元素為軸值,自右向左找一個比軸值小的數交換,再自左向右找一個比軸值大的數交換,再重復自右向左找一個比軸值小的數交換,自左向右找一個比軸值大的數交換,直到軸值左邊沒有比其大的數存在,右邊也沒有比其小的數存在,則第一輪結束。原來的一組數據被劃分為以軸值為界的兩組新數據
第二輪:取上一輪軸值左邊的一組新數據,重復1的操作;取上一輪軸值右邊的一組新數據,重復1的操作,則把最初的一組數據分成了四部分,這樣便產生一個遞歸的思想
一直重復操作,直到數據被分的不可再分為止。
形象排序:

原始數據: |6| 2 8 5 1
第一輪: |1| 2 5 | 6 |8|
第二輪: 1 ||2| 5 | 6 | 8
第三輪: 1 | 2 | 5 | 6 | 8
1
2
3
4
具體解釋:
第一輪中:(先假設第一個元素6為軸值)
第一次:自右向左找一個比軸值(即6)小的數交換,正巧右邊的第一個數就比6小,則交換6和1(目前數字排列為 1 2 8 5 6)
第二次:自左向右找一個比軸值(即6)大的數交換,左邊第一個數為1,不比6大,則找左邊第二個數;左邊第二個數為2,不必6大,找左邊第三個數;左邊第三個數為8,比6大,則交換6和8(目前數字排列為 1 2 6 5 8)
第三次:再自右向左找一個比軸值(即6)小的數交換,右邊第一個數為8,不比6小,則找右邊第二個數;右邊第二個數為5,比6小,則交換6和5(目前數字排列為 1 2 5 6 8)
第四次:再自左向右找一個比軸值(即6)大的數交換,左邊第一個數為1,不比6大,則找左邊第二個數;左邊第二個數為2,不必6大,則找左邊第三個數;左邊第三個數為5,不比6大,則找左邊第四個數,結果第四個書就是軸值本身,則一輪循環停止(目前數字排列為 1 2 5 6 8)
最後:這樣,第一輪就把最初的一組元素{ 6 2 8 5 1 }分為兩組元素{ 1 2 5 }和{ 8 }(6為軸值,經歷這幾次遍歷,便已經固定其正確位置了,以後不需要再考慮這個元素)

第二輪中:
先考慮第一輪軸值(即6)左邊的數據 { 1 2 5 }:
第二輪中左邊新數據的第一輪:(先假設新數據的第一個元素1為新的軸值)自右向左找一個比軸值(即1)小的數交換,右邊第一個數為5,不比1小,則找右邊第二個數;右邊第二個數為2,不比1小,則找右邊第三個數,結果右邊第三個數就是軸值本身,則循環停止(目前數字排列為 1 2 5 ),同樣的循環已經固定軸值(即1)的位置
同時,軸值1的左邊沒有數據,即分到了不可再分的地步,那麽遞歸結束,而軸值1的右邊還有數據 { 2 5 },則繼續確立新的軸值為2,再進行如上操作,直到分到不可以再分,則遞歸終止,最後可以確保第一輪的軸值(即6)左邊的新數據 { 1 2 5 }每個都被固定,則左邊數據的遞歸結束。
再考慮第一輪軸值(即6)右邊的數據 { 8 }:
已經被分到不可再分,則它的位置就已經被確定了,右邊數據的遞歸結束。
最終整組數據就排列完畢

技術分享圖片
public static void quikeSort(int[] arr, int low, int high) {
        int start = low;
        int end = high;
        int anchor = arr[low];

        while (end > start) {
            //比較 anchor---end
            while (end > start && arr[end] >= anchor) {  //從末尾向前尋找小於等於anchor的值
                end--;
            }

            //交換位置
            if (arr[end] <= anchor) {
                int temp = arr[end];
                arr[end] = arr[start];
                arr[start] = temp;
            }

            //比較start---anchor
            while (end > start && arr[start] <= anchor) {//從起始位置向後尋找大於等於anchor的值
                start++;
            }

            //交換位置
            if (arr[start] >= anchor) {
                int temp = arr[start];
                arr[start] = arr[end];
                arr[end] = temp;
            }
        }
        //anchor位置確定。左邊的元素值都小於anchor值,右邊的值都大於anchor值,遞歸排序左右兩側排序
        //左邊元素。low---anchor值索引-1
        if (start > low) {
            quikeSort(arr, low, start - 1);
        }

        //右邊元素。從anchor值索引+1---high
        if (end < high) {
            quikeSort(arr, end + 1, high);
        }
    }
技術分享圖片

歸並排序:中分兩個結合,分別歸並排序,然後合並兩個有序結合;遞歸進行

技術分享圖片
public static void mergeSort(int[] data) {
        sort(data, 0, data.length - 1);
    }

    public static void sort(int[] data, int left, int right) {
        if (left >= right)
            return;
        // 找出中間索引
        int center = (left + right) / 2;
        // 對左邊數組進行遞歸
        sort(data, left, center);
        // 對右邊數組進行遞歸
        sort(data, center + 1, right);
        // 合並
        merge(data, left, center, right);
    }

    /**
     * 將兩個數組進行歸並,歸並前面2個數組已有序,歸並後依然有序
     *
     * @param data
     *            數組對象
     * @param left
     *            左數組的第一個元素的索引
     * @param center
     *            左數組的最後一個元素的索引,center+1是右數組第一個元素的索引
     * @param right
     *            右數組最後一個元素的索引
     */
    public static void merge(int[] data, int left, int center, int right) {
        // 臨時數組
        int[] tmpArr = new int[data.length];
        // 右數組第一個元素索引
        int mid = center + 1;
        // third 記錄臨時數組的索引
        int third = left;
        // 緩存左數組第一個元素的索引
        int tmp = left;
        while (left <= center && mid <= right) {
            // 從兩個數組中取出最小的放入臨時數組
            if (data[left] <= data[mid]) {
                tmpArr[third++] = data[left++];
            } else {
                tmpArr[third++] = data[mid++];
            }
        }
        // 剩余部分依次放入臨時數組(實際上兩個while只會執行其中一個)
        while (mid <= right) {
            tmpArr[third++] = data[mid++];
        }
        while (left <= center) {
            tmpArr[third++] = data[left++];
        }
        // 將臨時數組中的內容拷貝回原數組中
        // (原left-right範圍的內容被復制回原數組)
        while (tmp <= right) {
            data[tmp] = tmpArr[tmp++];
        }
    }
技術分享圖片

基數排序:逐位排序

技術分享圖片
//LSD
    public static void radixLSDSort(int[] arr){
        //最高位數
        int maxBit = getMaxBit(arr);
        //十個bulk 分別存放 每個位數 數字 相應的元素 如個位為3 則放入bulk[3]
        Queue<Integer>[] bulk = new Queue[10];
        for (int i = 0; i < bulk.length; i++) {
            bulk[i] = new ArrayDeque();
        }
        //分別處理不同位數 存放 處理
        for (int i = 0; i < maxBit; i++) {
            for (int j = 0; j < arr.length; j++) {
                int ivalue = (int) (arr[j] % Math.pow(10, i + 1) / Math.pow(10, i)); //去相應位數上的數字作為 存入bulk index
                bulk[ivalue].offer(arr[j]);
            }

            //取出bulk中的元素 重新放入數組 並清除bulk中的元素。
            int arrIndex = 0;
            for (int j = 0; j < bulk.length; j++) {
                while (bulk[j].size()>0){
                    arr[arrIndex++] = bulk[j].poll();
                }
            }
        }
    }

    public static int getMaxBit(int[] arr){
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if(arr[i] > max){
                max = arr[i];
            }
        }

        int b = 0;
        while (max > 0){
            max /= 10;
            b++;
        }
        return b;
    }

    public static void radixMSDSort(int[] arr){
        msdSort(arr, 0, arr.length, getMaxBit(arr));
    }

    //MSD
    public static void msdSort(int[] arr, int left, int right, int maxBit){
        //十個bulk 分別存放 每個位數 數字 相應的元素 如個位為3 則放入bulk[3]
        Queue<Integer>[] bulk = new Queue[10];
        for (int i = 0; i < bulk.length; i++) {
            bulk[i] = new ArrayDeque();
        }
        //依據最高位分別放入不同的bulk
        for (int j = left; j < right; j++) {
            int ivalue = (int) (arr[j] % Math.pow(10, maxBit) / Math.pow(10, maxBit - 1)); //去相應位數上的數字作為 存入bulk index
            bulk[ivalue].offer(arr[j]);
        }

        //取出bulk中的元素 重新放入數組 並清除bulk中的元素。記錄bulk中queue大小大於1的數組索引 遞歸使用
        List<Integer> count = new ArrayList<Integer>();
        int arrIndex = left;
        for (int j = 0; j < bulk.length; j++) {
            int start = arrIndex;
            while (bulk[j].size()>0){
                arr[arrIndex++] = bulk[j].poll();
            }
            if(arrIndex - start > 1){
                count.add(start);
                count.add(arrIndex);
            }
        }
        //遞歸最小位判斷
        int nextBit = maxBit - 1;
        if(nextBit > 0) {
            for (int i = 1; i < count.size(); i += 2) {
                msdSort(arr, count.get(i - 1), count.get(i), nextBit);
            }
        }
    }
技術分享圖片

shell排序:插入排序+步長改進

技術分享圖片
public static void shellSort(int[] arr){
        int step = arr.length/2;
        while (step >= 1) { //步長為1時 包含所有數組序列
            for (int i = 0; i < step; i++) { //步長為n則數組將分為n組分別排序
                for (int j = step + i; j < arr.length; j += step) { //對跨步長每組元素進行插入排序
                    int temp = arr[j];
                    int preIndex = j - step;
                    while (preIndex > -1 && temp < arr[preIndex]) {
                        arr[preIndex + step] = arr[preIndex];
                        preIndex -= step;
                    }
                    arr[preIndex + step] = temp;
                    System.out.println(" middle: " + Arrays.toString(arr));
                }
            }
            step /= 2; //每次步長處理
        }
    }
 
/**
* 改進形式
* @param arr
*/
public static void shellSort1(int[] arr){
int step = arr.length/2;
while (step >= 1) { //步長為1時 包含所有數組序列
for (int i = step; i < arr.length; i += step) { //對跨步長每組元素進行插入排序
int temp = arr[i];
int j = i;
while (j > 0 && temp < arr[j - step]) {
arr[j] = arr[j - step];
j -= step;
}
arr[j] = temp;
}
System.out.println("step: " + step + " middle: " + Arrays.toString(arr));
step /= 2; //每次步長處理
}
}

基本排序算法(冒泡排序 選擇排序 插入排序 快速排序 歸並排序 基數排序 希爾排序)