java從《《遞迴函式》》到《《歸併排序》》再到《《最小和問題(歸併排序的應用)》》:
阿新 • • 發佈:2018-11-16
一:我們首先來研究一下遞迴函式(使用遞迴函式求陣列最大值):
我們開始把陣列分為兩半,分別找出最大值,那麼這個最大值就是最後的最大值:同時我們左右兩邊繼續細分,停止條件就是細分到單個數值為止。
package chapter1; //使用遞迴求出一個數組中的最小值 public class FindMax { public static void main(String[] args) { int[] arr = { 1, 2, 5, 3, 2 }; int result = findMax(arr, 0, arr.length - 1); System.out.println(result); } public static int findMax(int[] arr, int start, int len) { if (start == len) {//首先是找到終止條件 return arr[start]; } int mid = (len + start) / 2; int LMax = findMax(arr, start, mid);//左邊出去,像一個樹形結構一樣不斷細分出去 int RMax = findMax(arr, mid + 1, len);//右邊出去,像一個樹形結構一樣不斷細分出去 //經歷過了上邊的程式碼,當走當了了if裡面的時候,才會結束那個細分,然後我們的細分結構了全部完成了, //分成了很多細分支,那麼接下來就是對每個細分部分進行我們想要的操作,就是下邊的return語句。 //每一個細分都有這個語句,那麼我們從細到粗進行return語句操作 return Math.max(LMax, RMax); } }
控制檯列印:
二:下邊來分析我們歸併排序:
歸併排序可以分為兩個部分,一個就是遞迴(就是不斷的細分)
// 首先是遞迴操作,也就是我們的大的一個函式 public static void mergeSort(int[] arr, int start, int end) { // 首先是終止條件 if (start == end) { return; } int mid = start + ((end - start) >> 1); mergeSort(arr, start, mid);// 左邊細分出去 mergeSort(arr, mid + 1, end);// 右邊細分出去 // 下邊就是每一個細分需要進行的哪些操作,就是將細分的部分排序,然後替換陣列中原有的部分 merge(arr, start, end); }
第二個就是合併。
那麼我們合併的時候就是假設是在處理陣列的兩個部分,同時這兩個部分是分別有序的,然後我們的任務就是吧兩個有序的數組合成一個有序的陣列,比如,1,3,5和2,4,6合併完就是1,2,3,4,5,6。
public static void merge(int[] arr, int start, int end) { // TODO Auto-generated method stub int i = 0;//help陣列的下標 int[] help = new int[end - start + 1]; int p1 = start; int mid = start + ((end - start) >> 1); int p2 = mid + 1; // 下邊就是p1,p2往後移動,然後一個一個比較(這裡我們是把一個數組分兩邊,假設兩邊都分別排好序了,這裡就是將兩個已經排好序的進行合併一起 while (p1 <= mid && p2 <= end) { help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } // 如果跳出了while迴圈就說明兩邊至少有一邊越界了,那麼我們就把還沒越界的那邊就新增到help後邊即可 while (p1 <= mid) { help[i++] = arr[p1++]; } while (p2 <= end) { help[i++] = arr[p2++]; } // 經歷過了上邊幾個while迴圈過後,我們的help就是我們已經排序過後的陣列,那麼我去原陣列替換這部分即可(用有序的help去替換無序的原始部分) for (int j = 0; j < help.length; j++) { arr[start + j] = help[j]; } }
完整測試程式碼如下:
package chapter1;
//歸併排序
public class MergeSort {
public static void main(String[] args) {
System.out.println("排序前");
int[] arr = { 1, 2, 6, 5, 3 };
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
mergeSort(arr, 0, arr.length - 1);
System.out.println();
System.out.println("排序後");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
}
// 首先是遞迴操作,也就是我們的大的一個函式
public static void mergeSort(int[] arr, int start, int end) {
// 首先是終止條件
if (start == end) {
return;
}
int mid = start + ((end - start) >> 1);
mergeSort(arr, start, mid);// 左邊細分出去
mergeSort(arr, mid + 1, end);// 右邊細分出去
// 下邊就是每一個細分需要進行的哪些操作,就是將細分的部分排序,然後替換陣列中原有的部分
merge(arr, start, end);
}
public static void merge(int[] arr, int start, int end) {
// TODO Auto-generated method stub
int i = 0;
int[] help = new int[end - start + 1];
int p1 = start;
int mid = start + ((end - start) >> 1);
int p2 = mid + 1;
// 下邊就是p1,p2往後移動,然後一個一個比較(這裡我們是把一個數組分兩邊,假設兩邊都分別排好序了,這裡就是將兩個已經排好序的進行合併一起
while (p1 <= mid && p2 <= end) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
// 如果跳出了while迴圈就說明兩邊至少有一邊越界了,那麼我們就把還沒越界的那邊就新增到help後邊即可
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= end) {
help[i++] = arr[p2++];
}
// 經歷過了上邊幾個while迴圈過後,我們的help就是我們已經排序過後的陣列,那麼我去原陣列替換這部分即可(用有序的help去替換無序的原始部分)
for (int j = 0; j < help.length; j++) {
arr[start + j] = help[j];
}
}
}
控制檯:
三:歸併排序的應用:
比如我們針對第一個數,後邊如果有k個數比第一個數大,小和需要計算k次
法一:使用複雜度n*n方式,就是兩個for迴圈來求解
package chapter1;
public class MinSum2 {
public static void main(String[] args) {
int[] arr = { 1, 3, 4, 2, 5 };
int result = 0;
// 搞兩個for迴圈
for (int i = 0; i < arr.length - 1; i++) {
int n = 0;// 記錄前邊有幾個數比當前i位置數字要大
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] < arr[j]) {
n++;
}
}
result += n * arr[i];
}
System.out.println(result);
}
}
控制檯如下:
法二:使用歸併排序方式:複雜度n*log(n)
比如我們最開始細分到最底端,然後每次合併其實都是有計算我們的小和,只是開始是計算我們小組內的,然後合併大範圍的時候,其實我們的小組內的小和已經求過了,下邊只是求解合併時候右邊小組對於左邊小組會產生的小和。所以每次都是批次處理,而不是隻處理一個數
package chapter1;
import java.util.concurrent.SynchronousQueue;
public class MinSum {
public static void main(String[] args) {
int[] arr = { 1, 3, 4, 2, 5 };
int result = mergeSort(arr, 0, arr.length - 1);// (注意)這裡很容易搞成arr.length
System.out.println(result);
}
public static int mergeSort(int[] arr, int start, int end) {
// 結束條件
if (start == end) {
return 0;// 這裡就是細分到每一個數據的時候,最小和是0
}
int mid = start + ((end - start) >> 1);
return mergeSort(arr, start, mid) + mergeSort(arr, mid + 1, end) + merge(arr, start, end);
}
private static int merge(int[] arr, int start, int end) {
// TODO Auto-generated method stub
int mid = start + ((end - start) >> 1);
// 首先定義兩個指標
int p1 = start;
int p2 = mid + 1;
// 定義一個help陣列以及一個下標i
int[] help = new int[end - start + 1];
int i = 0;
int result = 0;
while (p1 <= mid && p2 <= end) {// 如果都沒越界,就一直迴圈
result += arr[p1] < arr[p2] ? arr[p1] * (end - p2 + 1) : 0;
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= end) {
help[i++] = arr[p2++];
}
return result;
}
}
控制檯: