1. 程式人生 > >插入排序、歸併排序以及小和問題

插入排序、歸併排序以及小和問題

一.插入排序

插入排序的思想了類似於向一個有序的數組裡面插入一個數。最初我們將一個無序的陣列0~N-1的0~0部分視作有序,然後取1位置的數與0位置上的數進行比較,比0位置上數小就進行交換,比0位置上數大就不變,從而使0~1部分排成有序,以此類推。也就是說,取有序部分後邊的數插入到那個有序的部分,將這個數和它前面的數依次進行比較,比前面的數小就進行交換,比前面的數大就已經找到了這個數的位置,直到最後位置的數也找到了正確的位置排序就結束。
這裡寫圖片描述
程式碼實現:

    /*
     * 插入排序
     * 時間複雜度:
     * 是和資料的狀況有關的演算法流程,一律按照最差狀況計算
     * 最優:有序序列:O(N)
     * 最差:逆序序列:O(N^2)
     * 所以時間複雜度為:O(N^2)
     */
public static void sort(int[] arr) { if(arr == null || arr.length < 2) return; //i位置上的數就是這次排序要排的數 for(int i = 1;i < arr.length;i++) { for(int j = i-1;j >= 0 && arr[j] > arr[j+1];j--) { swap(arr, j, j+1); } } } //進行交換的方法
public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }

二.歸併排序

該演算法是採用分治法的一個非常典型的應用,將已有序的兩個子序列合併為一個有序的序列。
我們使用一個輔助陣列,大小是兩個子序列大小的和,分別定義兩個記錄兩個子序列當前小標的變數,和一個記錄輔助陣列的下標的變數,取兩個子序列中的數進行比較,較小的數拷貝進輔助陣列中,並且將下標後移一位,較大的數所在序列記錄下標的變數不變,輔助陣列下標也後移一位。。。以此類推,如果有一個數組已經全部放入輔助陣列中,就將另一個數組中還沒有放入輔助陣列的數一次全部放入。
這裡寫圖片描述


程式碼實現:

    /*
     * 歸併排序:
     * 時間複雜度:O(N*logN),額外空間複雜度O(N)
     */
    //將陣列排成兩個有序的陣列,使用插排遞迴實現
    public static void sortProcess(int[] arr, int L, int R) {
        if(L == R)
            return;
        //使用右移的位運算取mid
        int mid = L + ((L - R) >> 1);
        sortProcess(arr, L, mid);
        sortProcess(arr, mid+1, R);
        merge(arr, L, mid, R);
    }

    //歸併排序
    public static void merge(int[] arr, int L, int mid, int R) {
        //輔助陣列
        int[] help = new int[R-L+1];
        int i = 0;
        int p1 = L;
        int p2 = mid + 1;

        //兩個有序部分的陣列還有數沒有排的時候
        while(p1 <= mid && p2 <= R) {
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }

        //當其中一個數組的數已經全部放進去,將另外一個數組的沒有放進去的部分放進去
        while(p1 <= mid) {
            help[i++] = arr[p1++];
        }
        while(p2 <= R) {
            help[i++] = arr[p2++];
        }

        //將輔助陣列中的數全部放進原陣列中
        for(i = 0;i < arr.length;i++) {
            arr[L + i] = help[i];
        }
    }

三.歸併排序的應用——小和問題

題目描述:
  在一個數組中,每一個數左邊比當前數小的數累加起來,叫做這個數的小和。求一個數組的小和。

例子:
  陣列:[1, 3, 4, 2, 5]
  1左邊比1小的數:沒有;
  3左邊比3小的數:1;
  4左邊比4小的數:1, 3;
  2左邊比2小的數:1;
  5左邊比5小的數:1, 3, 4, 2;
所以小和為:1 + 1 + 3 + 1 + 1 + 3 + 4 + 2 = 16

分析:
我們可以把小和問題轉化成:求每個數右邊比自己大的個數 * 自己這個數的大小,比如題中:1的右邊比1大的數一共有四個,一共計算了四次1;3的右邊有兩個比3大的數,一共計算了兩次3;4的右邊有一個比4大的數,一共計算了一次4;2的右邊有一個比2大的數,一共計算了一次2;5的右邊沒有比它大的數。
那麼我們該如何將這個問題轉化成歸併呢?
這裡寫圖片描述
首先使用分治的思想將陣列分為上圖所示,先定義一個小和值sum = 0,然後在歸併的時候,是從最下層進行歸併的:
所以最先歸併的是1和3,1<3,sum += 1 * 1,第一個1是指較小的數為1,第二個1是指,有1個數比1大;
第二次歸併的是[1, 3]和4,1<4,sum += 1 * 1;3 < 4,sum += 3 * 1;
第三次歸併的是2和5,2 < 5,sum += 2 * 1;
第四次歸併的是[1, 3, 4]和[2, 5],1 < 2,sum += 1 * 2;2 < 3,sum += 2 * 2;3 < 5,sum += 3 * 1;4 < 5,sum += 4 * 1
所以,小和問題其實就是一個歸併排序的應用
部分程式碼如下:

    //只是在原來歸併的基礎上添加了一個變數res存放小和的值,並在這個while語句中計算小和值
    int res = 0;
    while(p1 <= mid && p2 <= r) {
            res += arr[p1] < arr[p2] ? arr[p1] * (r - p2 + 1) : 0;
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }