常見的排序方法及其java實現

常見排序方法及其時間複雜度

1. 冒泡排序     n^2
2. 選擇排序     n^2        (尋找局部最小數)
3. 直接插入排序  n^2  (有序數組)
4. 希爾排序   nlogn       (二分+直接插入+遞歸)
5. 快速排序   nlogn        (最右基準,大放右,小左)
6. 歸併排序   nlogn     (拆分+合併)
7. 基數排序    n+r,r爲最大數最高位的位數   (按位數比較)
8. 堆排序       nlogn   (平衡二叉樹)
9. 桶排序       n+r,(這裏的r受桶的數量影響)  (範圍+拆分比較)
10. 計數排序    n+r,r爲桶的數量   (範圍+個數)

1、2、3、4、5、6、8屬於比較排序,不受數據影響。(即數據可以是小數)
7、9、10屬於非比較排序,對數據規模和數據分佈有一定的要求。

場景分析
1.當輸入規模比較小,推薦使用直接插入排序
3.當輸入規模比較大時
對性能要求嚴苛:快速排序
對空間有要求:堆排序
對穩定性有要求(有大量重複的數):歸併排序
如果數據都是範圍類整形:計數排序

穩定,指的是一個序列中的相同值,它排序後,它的相同值的順序不會改變。

常見排序的java代碼實現

import java.util.ArrayList;
import java.util.ListIterator;

public class SortedUtils {


    /**
     * 001冒泡排序
     * 1000萬的數據排序需要55小時
     * 時間複雜度爲 n^2
     * @param arr
     * @return
     */
    public static int[] bubbleSort(int[] arr){
        int temp = 0;
        boolean flag = false; //表示是否已經按順序排列好了
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j+1]){
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    flag = true;
                }
            }
            if (!flag){
                break;
            }else {
                flag = false;
            }
        }
        return arr;
    }

    /**
     * 002選擇排序
     * 1000萬的數據排序需要12個小時
     * 時間複雜度爲 n^2
     * 原數組: 4 1 2 5 9 3
     * 第一步:將最小的數放在位置1
     * 第二步:將剩下最小的放位置2
     * 依次類推
     *
     * @param arr
     * @return
     */
    public static int[] selectSort(int[] arr){
        for (int i = 0; i < arr.length-1; i++) {
            int minIndex = i; // 初始最小值下表(假定每一輪的第一個值爲最小值)
            int min = arr[i]; // 初始最小值
            for (int j = i; j < arr.length-1; j++) {
                if (min > arr[j+1]){
                    minIndex = j+1; // 改變最小值索引
                    min = arr[j+1];
                }
            }
            if (minIndex != i){ // 最小值已經改變,將最小值與第一個位置進行交換
                arr[minIndex] = arr[i];
                arr[i] = min;
            }
        }
        return arr;
    }

    /***
     * 003直接插入排序
     * 1000萬的數據排序需要3.2個小時
     * 時間複雜度爲 n^2
     *
     * 通過一個有序數組來存放 4 1 2 5 9 3
     * 第一步:4
     * 第二步:1 4
     * 第三步:1 2 4
     * ....
     * 第六步:1 2 3 3 5 9
     * @param arr
     * @return
     */
    public static int[] insertSort(int[] arr){
        for (int i = 1; i < arr.length; i++) { // 從一開始,前面的第一個數爲有序數,後面的數與前面的有序數進行比較
            int insertValue = arr[i]; // 待插入的數
            int insertIndex = i - 1; // 待插入數前面一個數的索引
            while (insertIndex >= 0 && insertValue < arr[insertIndex]){
                arr[insertIndex+1] = arr[insertIndex]; // 將比要插入數大的向後移動
                insertIndex--; // 索引向前移動
            }
            arr[insertIndex+1] = insertValue; // 將要插入的數放在找到的位置
        }
        return arr;
    }

    /***
     * 004 希爾排序 (是對直接插入排序的一種改進)
     * 1000萬的數據排序需要3.067秒
     * 時間複雜度爲  nlogn (涉及二分法)
     * 第一步:將數據一分二 (0 2 4 一組 1 3 5 一組 間隔),分別對其進行直接排序 6/2=3
     * 第二步:再次一分爲二,在分別直接排序  3/2=1
     * 第三步:進行微調
     *
     * @param arr
     * @return
     */
    public int[] shellSort2(int[] arr){
        for (int gap = arr.length/2; gap >0 ; gap /= 2) { // 每次步長減半
            for (int i = gap; i < arr.length; i++) { // 從第gap個元素,逐個對其所在的組進行直接插入排序
                int insertIndex = i;
                int insertValue = arr[insertIndex];
                while (insertIndex - gap >= 0 && insertValue < arr[insertIndex - gap]){
                    arr[insertIndex] = arr[insertIndex - gap];
                    insertIndex -= gap;
                }
                arr[insertIndex] = insertValue;
            }
        }
        return arr;
    }

    /***
     * 005 快速排序
     * 1000萬的數據排序需要1.55秒
     * 時間複雜度爲  nlogn (涉及二分法)
     * 第一步:選擇最右的數 作爲基準 比它的放右邊,比它小的放左邊
     * 第二步:左右兩組數據 ,再次選擇最右數爲基準,進行比較
     * 依次類推,知道左右兩邊的數組長度都爲1
     *
     * @param arr
     * @param left
     * @param right
     */
    public void quickSort(int[] arr, int left, int right){
        if (left >= right){
            return ;
        }
        int l = left;
        int r = right;
        int temp = arr[right];
        int t = 0;// 作爲交換變量
        while (l < r){
            while (arr[l] <= temp && l < r){ // 從左邊向右尋找大於等於temp的數
                l++;
            }
            while (arr[r] >= temp && l < r){ // 從右向左尋找小於等於temp的數
                r--;
            }
            if (l < r){ //交換
                t = arr[l];
                arr[l] = arr[r];
                arr[r] = t;
            }
        }
        arr[right] = arr[l];
        arr[l] = temp;
        quickSort(arr, left, r-1);//向左遞歸
        quickSort(arr, l+1, right);// 向右遞歸
    }


    /***
     * 006 歸併排序
     * 1000萬的數據排序需要1.75秒
     * 時間複雜度爲  nlogn (涉及二分法)
     * 第一步:將數組不斷拆分,知道每個數組大小都爲2,比較大小
     * 第二步:將拆分後的數組兩兩合併並比較大小
     * 依次類推,直到所有的數組都合併
     *
     * @param arr
     * @param left
     * @param right
     */
    public void mergeSort(int[] arr, int[] temp, int left, int right){
        if (left < right){
            int mid = (left + right) / 2;
            // 向左遞歸分解
            mergeSort(arr, temp, left, mid);
            // 向右遞歸分解
            mergeSort(arr, temp, mid + 1, right);
            merge(arr, temp, left, right, mid);
        }
    }

    public void merge(int[] arr, int[] temp, int left, int right, int mid){
        int i = left;
        int j = mid + 1;
        int t = 0;
        // 1.比較兩個兩部分每一個的大小,知道有一部分數據全部加入temp
        while (i <= mid && j <= right){
            if (arr[i] < arr[j]){
                temp[t] = arr[i];
                i++;
            }else {
                temp[t] = arr[j];
                j++;
            }
            t++;
        }
        // 2.將剩餘的那一部分的全部加入temp
        while (i <= mid){ // 若前面的部分剩餘,將前面那部分剩餘的全部加入
            temp[t] = arr[i];
            i++;
            t++;
        }
        while (j <= right){ // 若後面的部分剩餘,則將後面的的部分全部加入
            temp[t] = arr[j];
            j++;
            t++;
        }

        // 3.將temp的數全部拷貝到arr中
        t = 0;
        int tempLeft = left;
        while (tempLeft <= right){
            arr[tempLeft] = temp[t];
            t++;
            tempLeft++;
        }
    }

    /**
     * 007 基數排序
     * 1000萬的數據排序需要0.695秒
     * 時間複雜度爲  n+r  (r爲是最高位的位數)
     * 第一步:根據個位排序
     * 第二步:根據十位排序
     * 依次類推,直到根據最大位進行排序
     *
     * @param arr
     */
    public void radixSort(int[] arr){
        int max = arr[0];
        for (int i = 0; i < arr.length; i++) { // 找到最大值,根據最大值位數確定排序的次數
            if (arr[i] > max){
                max = arr[i];
            }
        }
        int maxLength = (max+"").length();//最大值的長度
        // 初始化一個二維數據,二維數組的沒一個數組都是一個桶,因爲無法確定每一個桶會裝多少個數據,所以設置爲arr.lenth
        int[][] bucket = new int[10][arr.length];
        // 初始化一個一維數組用於保存桶保存的數的個數
        int [] bucketElementCounts = new int[10];
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) { // i控制循環次數,n控制每次取得的位數
            for (int j = 0; j < arr.length; j++) { // 循環每個數,按照規則放入桶內
                int digitalOfElement = arr[j] / n % 10; // 每次取模得到相應位數的值
                bucket[digitalOfElement][bucketElementCounts[digitalOfElement]] = arr[j];
                bucketElementCounts[digitalOfElement]++;
            }
            int index = 0;//存放數據的索引
            for (int k = 0; k < bucket.length; k++) { // 循環所有桶
                if (bucketElementCounts[k] != 0){ // 不爲空的桶
                    for (int m = 0; m < bucketElementCounts[k]; m++) { // 將桶的元素放入數組
                        arr[index++] = bucket[k][m];
                    }
                }
                bucketElementCounts[k] = 0;//清空桶
            }
        }
    }


    /**
     * 008 堆排序
     * 1000萬的數據排序需要2.695秒
     * 時間複雜度爲  nlogn
     *
     * @param arr
     */
    public void heapSort(int[] arr){
        int temp = 0;
        // 第一次調整:將最大是數調整爲根節點
        // arr.length/2 - 1 :爲第一個非葉子節點
        for (int i = arr.length/2 - 1; i >= 0; i--) {
            adjustHeap(arr, i, arr.length);
        }

        for (int i = arr.length-1 ; i > 0; i--) {
            temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;
            // 爲什麼這裏的的第二個參數寫死是0 ?
            // 因爲經過第一次調整之後,再經過交換,就只有很根節點不是一個大頂堆,
            // 所以只需要進行簡單的與根節點交換就好,而不需要在進行整體的一個大頂堆的構造
            adjustHeap(arr, 0, i);
        }
    }
    public void adjustHeap(int[] arr, int i, int length){
        int temp = arr[i];
        // k = i * 2 + 1:表示i這個節點的左子樹
        for (int k = i * 2 + 1; k < arr.length; k = k * 2 + 1) {
            if (k + 1 < length && arr[k] < arr[k+1]){ // 比較左右子節點的大小,將k指向較大的拿一個節點
                k++;
            }
            if (arr[k] > temp){ // 子節點大於父節點
                arr[i] = arr[k];//交換數據第一步:子節點的值給父節點
                i = k ;//將i指向調整後的子節點,用於循環結束後交換數據
            }else {
                break;
            }
        }
        arr[i] = temp;//交換數據第二步
    }



    /**
     * 009 桶排序
     * 1000萬的數據排序需要16小時
     * 時間複雜度爲 O(N+N*logN-N*logM)  桶範圍越大時間複雜度越大
     * 假設數據是均勻分佈的 把0—100放在數組A 把 100-200放數組B 分別對其排序
     * @param arr
     */
    public void bucketSort(int[] arr){
        int min = arr[0];
        int max = arr[0];
        for (int i = 0; i < arr.length; i++) { // 找出最大值與最小值
            min = Math.min(min,arr[i]);
            max = Math.max(max,arr[i]);
        }
        int bucketNumber = (max - min)/arr.length + 1; // 桶的數量
        ArrayList<ArrayList<Integer>> buckets = new ArrayList<>(); // 存儲每一個桶
        ArrayList<Integer> bucket = null;
        for (int i = 0; i < bucketNumber; i++) { // 將沒一個桶放入buckets中
            bucket = new ArrayList<Integer>();
            buckets.add(bucket);
        }

        for (int item : arr) { // 循環數據插入到桶
            ArrayList<Integer> bc = buckets.get((item - min) / arr.length); // (item - min) / arr.length 表示桶的位置
            insert(bc, item); // 插入桶(桶內排序)
        }

        int index = 0; // 索引數組
        for (ArrayList<Integer> bc : buckets) { // 循環每一個桶
            for (Integer item : bc) { // 循環桶內的數據
                arr[index++] = item; // 依次將桶內的數據取出
            }
        }
    }

    public void insert(ArrayList<Integer> bc, int item){
        ListIterator<Integer> itl = bc.listIterator();
        boolean flag = true;
        while (itl.hasNext()){
            if (item <= itl.next()){
                itl.previous(); // 將迭代器的位置偏移到上一位
                itl.add(item); // 將數據插入到迭代器當前的位置上
                flag = false;
                break;
            }
        }
        if (flag){ // 否在九八數據插插入到末端
            bc.add(item);
        }
    }


    /**
     * 010 計數排序 (在確定都是整數的情況下 一個數一個桶 )
     * 1000萬的數據排序需要0.241秒
     * 時間複雜度爲  n+k(k爲桶的個數)
     * 假設數據是均勻分佈的 把0—100放在數組A 把 100-200放數組B 分別對其排序
     * @param arr
     */
    public void countSort(int[] arr){
        int min = arr[0];
        int max = arr[0];
        for (int i = 0; i < arr.length; i++) { // 找出最大值與最小值
            if (arr[i] > max){
                max = arr[i];
            }else if (arr[i] < min){
                min = arr[i];
            }
        }

        int[] counts = new int[max - min + 1];// 計數空間

        for (int i = 0; i < arr.length; i++) { // 將每一個數減去最小值統計到臨時數組中
            counts[arr[i] - min] += 1; // arr[i]-min對應計數空間的下表
        }
        for (int i = 0,index =0; i < counts.length; i++) {
            int item = counts[i];//每一個數對應的統計量
            while (item-- != 0){ // 直到每一個數的統計量歸零
                arr[index++] = i + min; // 將原來的數展開放到原數組中,展開就是按照大小排列
            }
        }
    }



}

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