歸並排序的理解和實現(Java)


歸並排序介紹

歸並排序(Merge Sort)就是利用歸並的思想實現的排序方法。它的原理是假設初始序列含有fn個記錄,則可以看成是n個有序的子序列,每個子序列的長度為1,然后兩兩歸並,得到[ n 2 \frac{n}{2} ]([x]表示不小於x的最小整數)個長度為2或1的有序子序列;在兩兩歸並,…,如此重復,知道得到一個長度為n的有序序列為止,這種排序方法稱為2路歸並排序。

根據具體的實現,歸並排序包括"從上往下"和"從下往上"2種方式。
下面的圖片很清晰的反映了"從下往上"和"從上往下"的歸並排序的區別。
在這里插入圖片描述

從上往下

代碼實現:

	/** * 從上到下 * @param elem * @param start * @param end */
	public void mergeSortUp2Down(int[] elem, int start, int end) {
		if(elem == null || start >= end) {
			return;
		}
		
		int mid = (start + end) / 2;
		
		mergeSortUp2Down(elem, start, mid);
		mergeSortUp2Down(elem, mid + 1, end);
		
		merge(elem, start, mid, end);
	}
	
	public void merge(int[] elem, int start, int mid, int end) {
		int[] temp = new int[end - start + 1];
		int i = start;
		int j = mid + 1;
		int k = 0;
		while(i <= mid && j <= end) {
			if(elem[i] < elem[j]) {
				temp[k++] = elem[i++];
			}
			else {
				temp[k++] = elem[j++];
			}
		}
		
		while(i <= mid) {
			temp[k++] = elem[i++];
		}
		
		while(j <= end) {
			temp[k++] = elem[j++];
		}
		
		for (i = 0; i < k; i++) {
			elem[start + i] = temp[i];
		}
		temp = null;	
	}

從上往下的思路如圖所示:
在這里插入圖片描述

從下往上

代碼實現:

	/** * 從下到上 * @param elem */
	public void mergeSortDown2Up(int[] elem) {
		
		if(elem == null) return;
		
		for (int i = 1; i < elem.length; i *= 2) {
			mergeGroups(elem, elem.length, i);
		}
	}
	
	public void mergeGroups(int[] elem, int len, int gap) {
		int i;
		for (i = 0; i + 2 * gap -1 < len; i += (2 * gap)) {
			merge(elem, i, i + gap -1, i + 2 * gap -1);
		}
		
		if(i + gap -1 < len - 1) {
			merge(elem, i, i + gap - 1, len - 1);
		}
	}

在這里插入圖片描述

歸並排序的復雜度分析

歸並排序的時間復雜度是O(n log \log n)。
假設被排序的數列中有n個元素。遍歷一趟的時間復雜度是O(n),需要遍歷多少次呢?歸並排序的形式就是一棵二叉樹,它需要遍歷的次數就是二叉樹的深度,而根據完全二叉樹的性質可以得知可以得出它的時間復雜度是O(n log \log n)。

由於歸並怕徐在歸並過過程中需要與原始記錄序列同樣數量的存儲空間存放歸並結果以及遞歸時深度為 l o g 2 log_2 n的棧空間,所以空間復雜度為O(n + log \log n)

歸並排序是穩定的算法,它滿足穩定算法的定義。

歸並排序的非遞歸實現

非遞歸的思想和遞歸一樣,均為先分解后合並,非遞歸的重點在於如何確定並合理的分解待排序數組。
對於非遞歸來講,切分的不向遞歸從大到小,非遞歸實際上從一開始構建算法的時候都從小到大。
第一次切分排序就確定最小單位為1個數字,將2個數字組合為一組。
在這里插入圖片描述
第二次切分排序確定為2個數字,將4個數字組合為一組。

第三次切分排序確定為4個數字,將8(7)個數字組合為一組。
在這里插入圖片描述
也就是說非遞歸歸並排序中分解的依據為:從切分的長度為1開始,一次歸並變回原來的2倍。每完成一次歸並則 gap = gap * 2。

	/** * 非遞歸 * @param elem */
	public void mergeSortNon(int[] elem) {
		int gap = 1;
		while(gap <= elem.length) {
			for (int i = 0; i + gap < elem.length; i += (gap * 2)) {
				int start = i, mid = i + gap -1, end = i + 2 * gap -1;
				if(end > elem.length - 1) {
					end = elem.length - 1;
				}
				merge(elem, start, mid, end);
			}
			gap *= 2;
		}
	}

完整代碼:

public class Test {

	/** * 從上到下 * @param elem * @param start * @param end */
	public void mergeSortUp2Down(int[] elem, int start, int end) {
		if(elem == null || start >= end) {
			return;
		}
		
		int mid = (start + end) / 2;
		
		mergeSortUp2Down(elem, start, mid);
		mergeSortUp2Down(elem, mid + 1, end);
		
		merge(elem, start, mid, end);
	}
	
	
	/** * 從下到上 * @param elem */
	public void mergeSortDown2Up(int[] elem) {
		
		if(elem == null) return;
		
		for (int i = 1; i < elem.length; i *= 2) {
			mergeGroups(elem, elem.length, i);
		}
	}
	
	public void mergeGroups(int[] elem, int len, int gap) {
		int i;
		for (i = 0; i + 2 * gap -1 < len; i += (2 * gap)) {
			merge(elem, i, i + gap -1, i + 2 * gap -1);
		}
		
		if(i + gap -1 < len - 1) {
			merge(elem, i, i + gap - 1, len - 1);
		}
	}
	
	
	/** * 非遞歸 * @param elem */
	public void mergeSortNon(int[] elem) {
		int gap = 1;
		while(gap <= elem.length) {
			for (int i = 0; i + gap < elem.length; i += (gap * 2)) {
				int start = i, mid = i + gap -1, end = i + 2 * gap -1;
				if(end > elem.length - 1) {
					end = elem.length - 1;
				}
				merge(elem, start, mid, end);
			}
			gap *= 2;
		}
	}
	
	public void merge(int[] elem, int start, int mid, int end) {
		int[] temp = new int[end - start + 1];
		int i = start;
		int j = mid + 1;
		int k = 0;
		while(i <= mid && j <= end) {
			if(elem[i] < elem[j]) {
				temp[k++] = elem[i++];
			}
			else {
				temp[k++] = elem[j++];
			}
		}
		
		while(i <= mid) {
			temp[k++] = elem[i++];
		}
		
		while(j <= end) {
			temp[k++] = elem[j++];
		}
		
		for (i = 0; i < k; i++) {
			elem[start + i] = temp[i];
		}
		temp = null;	
	}
	
	public static void main(String[] args) {
		Test t = new Test();
		int[] elem = {80,30,60,40,20,10,50,70};
		
		t.mergeSortUp2Down(elem, 0, elem.length - 1); //從上到下
		
// t.mergeSortDown2Up(elem); //從下到上
		
// t.mergeSortNon(elem); //非遞歸
		
		for (int i = 0; i < elem.length; i++) {
			System.out.print(elem[i] + ", ");
		}
	}
}

參考:
https://www.cnblogs.com/skywang12345/p/3602369.html

https://www.cnblogs.com/yulinfeng/p/7078661.html?utm_source=itdadao&utm_medium=referral
《大話數據結構》

補充說明

最近在做一道《劍指Offer》上的一道算法的時候,用到了歸並排序,因此加深了我對歸並排序的核心排序(merge())方法加深了理解:
在這里插入圖片描述
其實核心的排序就是設立三個指針P1, P2, P3。P3指針所指的數組就是兩個數組合並且排序后的數組。每次比較P1和P2指針所指的元素:

  • 如果P1<P2,那么將P1所指的元素賦給P3所指的數組節點,並將P1和P3的指針后移一位,P2指針不動。
  • 如果P1>P2,那么將P2所指的元素賦給P3所指的數組節點,並將P2和P3的指針后移一位,P1指針不動。
  • 當P1或P2其中某一個指針指到最后了,那么也就是說兩個數組比較排序已經完成,但是其中一個數組還“剩下”一部分元素(注意剩下的這些元素是有序的且剩下元素中最小的元素也大於新數組的最大元素),將剩下這部分的元素放在新數組后面。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM