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

歸併排序介紹

歸併排序(Merge Sort)就是利用歸併的思想實現的排序方法。它的原理是假設初始序列含有fn個記錄,則能夠當作是n個有序的子序列,每一個子序列的長度爲1,而後兩兩歸併,獲得[n2\frac{n}{2}2n]([x]表示不小於x的最小整數)個長度爲2或1的有序子序列;在兩兩歸併,…,如此重複,知道獲得一個長度爲n的有序序列爲止,這種排序方法稱爲2路歸併排序。html

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

從上往下

代碼實現:算法

/** * 從上到下 * @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(nlog⁡\loglogn)。
假設被排序的數列中有n個元素。遍歷一趟的時間複雜度是O(n),須要遍歷多少次呢?歸併排序的形式就是一棵二叉樹,它須要遍歷的次數就是二叉樹的深度,而根據徹底二叉樹的性質能夠得知能夠得出它的時間複雜度是O(nlog⁡\loglogn)。spa

因爲歸併怕徐在歸併過過程當中須要與原始記錄序列一樣數量的存儲空間存放歸併結果以及遞歸時深度爲log2log_2log2n的棧空間,因此空間複雜度爲O(n + log⁡\loglogn)指針

歸併排序是穩定的算法,它知足穩定算法的定義。code

歸併排序的非遞歸實現

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

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

/** * 非遞歸 * @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;
		}
	}

完整代碼:blog

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] + ", ");
		}
	}
}

參考:
http://www.javashuo.com/article/p-mcvhjvfq-bv.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其中某一個指針指到最後了,那麼也就是說兩個數組比較排序已經完成,可是其中一個數組還「剩下」一部分元素(注意剩下的這些元素是有序的且剩下元素中最小的元素也大於新數組的最大元素),將剩下這部分的元素放在新數組後面。
相關文章
相關標籤/搜索