1. 程式人生 > >歸併排序中的分治與遞迴

歸併排序中的分治與遞迴

在電腦科學中,分治與遞迴是兩個很容易混淆的概念。我覺得很有必要搞清楚二者之間的關係。我的理解,分治是一種思想,遞迴是一種手段。下面是百科裡面對分治和遞迴的定義:

【分治演算法】的基本思想是將一個規模為N的問題分解為K個規模較小的子問題,這些子問題相互獨立且與原問題性質相同。求出子問題的解,就可得到原問題的解。即一種分目標完成程式演算法,簡單問題可用二分法完成。

程式呼叫自身的程式設計技巧稱為【遞迴】。遞迴做為一種演算法在程式設計語言中廣泛應用。 一個過程或函式在其定義或說明中有直接或間接呼叫自身的一種方法,它通常把一個大型複雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解,遞迴策略只需少量的程式就可描述出解題過程所需要的多次重複計算,大大地減少了程式的程式碼量。

在眾多的排序演算法中,有一種演算法能夠直觀明瞭地體現分治和遞迴的關係,那就是【歸併排序】。

歸併排序的基本思路:將一個數組分成兩部分,兩個部分各自排序,然後將兩個部分歸併,得到最終結果。

聽起來好像還差了點什麼,這裡缺的就是分治思想:一個規模為N的問題,把它分解成2個規模為N/2的問題,它們的規模依舊很大,所以要把它們一直分解下去,直到計算機能夠很輕易地解決它們。當歸並排序中的子陣列長度為2的時候(即子陣列中只有2個元素),歸併就成為了一種排序。接下來將歸併後的最小子陣列作為待歸併的一部分返回給更大規模的子陣列,以此類推,就完成了歸併排序中的分治。

基於上述的分治思想,應該沒有比遞迴更好的解決辦法了。先看一個例子:

初始化逆序陣列a:16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

假設每次歸併陣列的下界為lo,上界為hi,定義對稱點mid=lo+(hi-lo)/2。

下面是利用遞迴的歸併過程(紅色部分為每次歸併的陣列):

lo=0, mid=0, hi=1
15 16 14 13 12 11 10 9 8 7 6 5 4 3 2 1 

lo=2, mid=2, hi=3
15 16 13 14 12 11 10 9 8 7 6 5 4 3 2 1 

lo=0, mid=1, hi=3
13 14 15 16 12 11 10 9 8 7 6 5 4 3 2 1 

lo=4, mid=4, hi=5
13 14 15 16 11 12 10 9 8 7 6 5 4 3 2 1 

lo=6, mid=6, hi=7
13 14 15 16 11 12 9 10 8 7 6 5 4 3 2 1 

lo=4, mid=5, hi=7
13 14 15 16 9 10 11 12 8 7 6 5 4 3 2 1 

lo=0, mid=3, hi=7
9 10 11 12 13 14 15 16 8 7 6 5 4 3 2 1 

lo=8, mid=8, hi=9
9 10 11 12 13 14 15 16 7 8 6 5 4 3 2 1 

lo=10, mid=10, hi=11
9 10 11 12 13 14 15 16 7 8 5 6 4 3 2 1 

lo=8, mid=9, hi=11
9 10 11 12 13 14 15 16 5 6 7 8 4 3 2 1 

lo=12, mid=12, hi=13
9 10 11 12 13 14 15 16 5 6 7 8 3 4 2 1 

lo=14, mid=14, hi=15
9 10 11 12 13 14 15 16 5 6 7 8 3 4 1 2 

lo=12, mid=13, hi=15
9 10 11 12 13 14 15 16 5 6 7 8 1 2 3 4 

lo=8, mid=11, hi=15
9 10 11 12 13 14 15 16 1 2 3 4 5 6 7 8 

lo=0, mid=7, hi=15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 

實現程式碼:

public class merge_sort {
	private static int[] aux;//輔助陣列

	public static void sort(int[] a){
		aux=new int[a.length];
		sort(a, 0, a.length-1);
	}//呼叫排序演算法
	
	private static void sort(int[] a, int lo, int hi){
		if (hi<=lo)
			return;//當前排序結束條件

		int mid=lo+(hi-lo)/2;
		sort(a, lo, mid);//s1:排序前半部分
		sort(a, mid+1, hi);//s2:排序後半部分
		merge(a, lo, mid, hi);//s1與s2執行完後歸併兩部分
	}
	
	public static void merge(int[] a, int lo, int mid, int hi){
		int i=lo, j=mid+1;
		for (int k=lo; k<=hi; k++)
			aux[k]=a[k];
		
		for (int k=lo; k<=hi; k++)
			if (i>mid)
				a[k]=aux[j++];
			else if (j>hi)
				a[k]=aux[i++];
			else if (aux[j]<aux[i])
				a[k]=aux[j++];
			else
				a[k]=aux[i++];
	}//歸併演算法
}

歸併過程:

歸併遞迴樹(縮排量代表深度,執行順序從上到下)

            merge(a, 0, 0, 1)

            merge(a, 2, 2, 3)

        merge(a, 0, 1, 3)

            merge(a, 4, 4, 5)

            merge(a, 6, 6, 7)

        merge(a, 4, 5, 7)

    merge(a, 0, 3, 7)

            merge(a, 8, 8, 9)

            merge(a, 10, 10, 11)

        merge(a, 8, 9, 11)

            merge(a, 12, 12, 13)

            merge(a, 14, 14, 15)

        merge(a, 12, 13, 15)

    merge(a, 8, 11, 15)

merge(a, 0, 7, 15)

BY DXH924

2018.10.28