1. 程式人生 > >java從《《遞迴函式》》到《《歸併排序》》再到《《最小和問題(歸併排序的應用)》》:

java從《《遞迴函式》》到《《歸併排序》》再到《《最小和問題(歸併排序的應用)》》:

一:我們首先來研究一下遞迴函式(使用遞迴函式求陣列最大值):

我們開始把陣列分為兩半,分別找出最大值,那麼這個最大值就是最後的最大值:同時我們左右兩邊繼續細分,停止條件就是細分到單個數值為止。

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;
	}
}

控制檯: