歸併排序解決小和問題 (圖解詳細流程)
阿新 • • 發佈:2019-01-11
在一個數組中,每一個數左邊比當前數小的數累加起來,叫做這個陣列的小和。求一個數組的小和。
例子:
[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次,第二次遍歷2次,第n次遍歷n次,是一個等差數列,但是時間複雜度為O(N^2)
程式碼
public static int smallSum(int[] arr) { //宣告累加變數 int res=0; for (int i = 1; i < arr.length; i++) { //遍歷索引小於i的元素,並進行判斷 for (int j = 0; j < i; j++) { if(arr[j]<arr[i]){ res+=arr[j]; } } } return res; }
歸併排序解決
通過歸併排序可以將時間複雜度控制到O(N*logN)
圖解流程
下圖為歸併排序的過程,將資料分解後,再進行合併
原理:歸併的每一次合併都是將兩個有序組合併為一個有序組,合併好後的有序組,再和另外的有序組繼續合併,最終可以得到一個完整的有序陣列
累加的原理
這裡主要就是利用合併的過程中,兩個有序組都是有序的進行判斷累加,我們以上圖的資料為3,5組和資料為8,9組合並的過程為例,來計算累加的結果
從上面的圖可以看出,如果p1索引的值小於p2索引的值,那麼這一次排序的過程可以計算右側陣列比3大的數有2個(因為每一組都是有序的),然後索引p1向右移動
從上面的圖可以看出,p1索引的值小於p2索引的值,那麼這一次排序過程可以計算出右邊比5大的數有2個
總結:上面兩個有序組合併為一個有序組時,累加的小和的值為: 3*2+5*2=16
程式碼
public static int smallSum(int[] arr) { if (arr == null || arr.length < 2) { return 0; } return mergeSort(arr,0,arr.length-1); } public static int mergeSort(int[] arr, int L, int R) { if (L == R) { return 0; } int mid = (L + R) >>>1;//這裡是防止資料溢位 return mergeSort(arr, L, mid) + mergeSort(arr, mid + 1, R) + merge(arr, L, mid, R); } //合併的過程 public static int merge(int[] arr, int L, int mid, int R) { //準備一個臨時陣列,長度和傳進來的arr一樣 int[] temp = new int[R - L + 1]; int p1 = L; int p2 = mid + 1; //臨時陣列temp的索引起始變數 int i = 0; //小和結果的變數 int result = 0; //合併陣列的迴圈,並計算小和 while (p1 <= mid && p2 <= R) { if (arr[p1] < arr[p2]) { //計算小和的累加結果,(R-p2+1)為比數arr[p1]大的數量 result += (R - p2 + 1) * arr[p1]; temp[i] = arr[p1]; p1++; i++; } else { temp[i] = arr[p2]; p2++; i++; } } while (p1 <= mid) { temp[i] = arr[p1]; i++; p1++; } while (p2 <= R) { temp[i] = arr[p2]; i++; p2++; } //這裡是將臨時陣列temp的元素重新賦值給傳入進來的arr for (int j = 0; j < temp.length; j++) { arr[L + j] = temp[j]; } return result; }