LeetCode入門指南 之 排序

WINLSR發表於2021-08-08

912. 排序陣列

給你一個整數陣列 nums,請你將該陣列升序排列。

歸併排序

public class Sort {
    //歸併排序
    public static int[] MergeSort(int[] arr) {
        int[] temp = new int[arr.length];
        mergeSort(arr, 0, arr.length - 1, temp);
        return arr;
    }

    private static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left >= right) {
            return;
        }

        int mid = (left + right) / 2;
        mergeSort(arr, left, mid, temp);
        mergeSort(arr, mid + 1, right, temp);

        merge(arr, left, mid, right, temp);
    }

    private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left, j = mid + 1;
        int k = 0;
        while(i <= mid && j <= right) {
            if (arr[i] < arr[j]) {		//改成 <= 就是穩定的
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }

        //如果左邊還有剩餘
        while(i <= mid) {
            temp[k++] = arr[i++];
        }

        //如果右邊還有剩餘
        while(j <= right) {
            temp[k++] = arr[j++];
        }

        //將temp中的資料放回arr
        k = 0;
        for (int m = left; m <= right; m++) {
            arr[m] = temp[k++];
        }
    }
}

穩定性:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序後的序列中,r[i]仍在r[j]之前,則稱這種排序演算法是穩定的。穩定性的好處:排序演算法如果是穩定的,那麼從一個鍵上排序,然後再從另一個鍵上排序,第一個鍵排序的結果可以為第二個鍵排序所用。推薦閱讀:堆排序為什麼不穩定_為什麼要區分穩定和不穩定排序

時間複雜度:平均 O(nlogn) 最優 O(nlogn) 最差 O(nlogn)

空間複雜度:O(n) = max(O(logn)——遞迴棧深度, O(n)——臨時陣列)

是否穩定性演算法: 是/否,取決於merge函式的實現。看merge是否會導致兩個值相同的元素髮生前後順序的改變,現在的實現是不穩定的。(如:[ 1, 2, 3, 2] 。第一次合併:1和2、3和2合併有[1, 2, 2, 3];第二次合併:1, 2 和 2, 3合併得[1, 2, 2, 3],但已經交換了兩個2的先後)

快速排序

public class Sort {
    //快速排序
    public static int[] QuickSort(int[] arr) {
        quickSort(arr, 0, arr.length - 1);
        return arr;
    }

    private static void quickSort(int[] arr, int left, int right) {
        if (left >= right) {
            return;
        }

        int mid = partition(arr, left, right);
        quickSort(arr, left, mid - 1);
        quickSort(arr, mid + 1, right);
    }

    private static Random random = new Random(47);
    
    private static int partition(int[] arr, int left, int right) {
        int rand = left + random.nextInt(right - left + 1);
        swap(arr, left, rand);
        int pivot = arr[left];
        
        while (left < right) {
            // 右邊找一個 小於等於 pivot 的元素
            while (left < right && arr[right] > pivot) {
                right--;
            }
            
            // 找到了就將右邊的元素放到左邊
            if (left < right) {
                swap(arr, left, right);
                left++;
            }
            
            // 左邊找一個 大於 pivot 的元素
            while (left < right && arr[left] <= pivot) {
                left++;
            }
            
            if (left < right) {
                swap(arr, left, right);
                right--;
            }
        }
        
        //跳出迴圈時 left = right
        arr[left] = pivot;
        return left;
    }
    
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
  • 歸併排序是標準的分治法模板
  • 快速排序只有分沒有治,因為在分之前就已經做好了要做的事

時間複雜度:平均 O(nlogn) 最優 O(nlogn) 最差O(n2)

空間複雜度:O(logn) —— 遞迴棧深度

是否穩定排序演算法:否 (如:[ 4, 2, 3, 2] -> [2, 2, 3, 4])這裡的 4 - 2 交換,導致原有的 2、2排序被打亂。

堆排序

堆:一種滿足堆積性質的完全二叉樹,需要滿足如下性質:

  1. 堆中的某個節點的值總是大於等於或小於等於其父節點的值;

  2. 堆總是一顆完全二叉樹;

public class Sort {
     //堆排序
     public static void HeapSort(int[] arr) {
         buildMaxHeap(arr);
         for (int i = arr.length - 1; i > 0; i--) {
             swap(arr, 0, i);       //將堆頂元素和最後一個葉子結點交換,最大值放到陣列尾部
             heapify(arr, 0, i);    //對前i個元素構建新的大頂堆
         }
     }

     //構建大頂堆(從第一個非葉子結點從右至左,從下至上)
     private static void buildMaxHeap(int[] arr) {
         for (int i = arr.length / 2 - 1; i >= 0; i--) {
            heapify(arr, i, arr.length);
         }
     }

     //以i為根的堆調整(大頂堆)
     private static void heapify(int[]arr, int i, int length) {
        int left = 2*i + 1;
        int right = 2*i + 2;

        int largest = i;  //假設根節點i為最大值

        // 左子結點存在且大於根節點
        if (left < length && arr[left] > arr[largest]) {
            largest = left;
        }

        // 右子結點存在且大於根節點
        if (right < length && arr[right] > arr[largest]) {
            largest = right;
        }

        if (largest != i) {         //說明最大值為其子結點
            swap(arr, largest, i);
            heapify(arr, largest, length);  //交換過後再調整其子結點為根的堆
        }
     }

     private static void swap(int[]arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
     }
}

時間複雜度:平均 O(nlogn) 最優 O(nlogn) 最差 O(nlogn)

空間複雜度:O(logn) - 遞迴堆疊

是否穩定排序演算法:否。比如:3 27 36 27(小頂堆),如果堆頂3先輸出,則,第三層的27(最後一個27)跑到堆頂,然後堆穩定,繼續輸出堆頂,是剛才那個27,這樣說明後面的27先於第二個位置的27輸出,不穩定。

排序結果測試類

public class Test {
    private static final int num = 8000000;
    private static int[] arr = new int[num];

    private static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH::mm:ss");

    private static void generateArr() {
        //給相同的種子使得每次生成的偽隨機數序列相同
        Random random = new Random(47);
        for (int i = 0; i < num; i++) {
            arr[i] = random.nextInt(num);
        }
    }

    private static boolean isOrdered(int[] arr) {
        boolean order = true;
        for (int i = 0; i < arr.length - 1; i++) {
            if (arr[i] > arr[i + 1]) {
                order = false;
                break;
            }
        }
        return order;
    }

    public static void test(SortI sort) {
        generateArr();
        Date startDate = new Date();
        System.out.println("start : " + formatter.format(startDate));

        sort.sort(arr);

        Date endDate = new Date();
        System.out.println("end : " + formatter.format(endDate));
        System.out.println("排序正確性:" + isOrdered(arr));
    }
    
    public static void main(String[] args) {
        Test.test(Sort::MergeSort);
        Test.test(Sort::QuickSort);
        Test.test(Sort::HeapSort);
    }

}

interface SortI {
    void sort(int[] arr);
}

相關文章