微信公衆號:超悅編程
點擊上方關注,瞭解更多算法、數據結構和計算機基礎知識的內容
問題或建議,請公衆號留言git
本文介紹了歸併排序的基本思想,遞歸方法的通常寫法,最後一步步手寫歸併排序,並對其性能進行了分析。
github
基本思想
歸併排序是創建在歸併操做上的一種有效的排序算法,該算法是採用分治法的一個很是典型的應用。即先使每一個子序列有序,再將已有序的子序列合併,獲得徹底有序的序列。這裏給出一種遞歸形式的歸併排序實現。web
遞歸方法的通常寫法
遞歸方法的書寫主要有三步:算法
明確遞歸方法的功能邊界;編程
獲得遞歸的遞推關係;數組
給定遞歸的終止條件。微信
遞歸方法都可按照這三步進行,切忌不要陷入遞歸實現的細節中。下面以歸併排序算法的書寫爲例,來談一下遞歸方法的具體寫法。數據結構
手寫歸併排序
首先,明確遞歸方法的功能,這裏咱們定義方法的功能爲,給定一個數組及左右邊界,方法完成數組邊界內元素的排序,以下:函數
private static void mergeSort(int[] arr,int left,int right);
先假設咱們已經有了這麼一個方法,不用管具體的實現。
接着,尋找遞推關係,什麼是遞推關係呢?就是如何由子問題的求解,來獲得原問題的求解,仍是舉例說明,有以下的數組
性能
咱們將其拆分爲左右兩部分,以下
遞推關係就是,假如左右兩部分都已經有序了,如何使整個數組有序?這個問題其實就是給定了一個數組,數組的左半部分有序,右半部分也有序,如何使整個數組有序?
首先,定義兩個指針,分別指向左側部分起始位置和右側部分起始位置,同時建立一個輔助數組和指向其初始位置的輔助指針
接着比較,左指針和右指針所對應的元素的大小,較小的元素填充至輔助數組,同時其對應的指針和輔助指針均加1,以下:
依次進行,直至某左指針指向中間位置或者右指針指向數組的末尾,此時要將將剩餘的元素填充至輔助數組。全部的元素填充完成後,再將輔助數組中的元素填充回原數組便可。具體的代碼以下:
/**
*
* @param arr 要合併的數組
* @param left 左邊界
* @param mid 中間的分界
* @param right 右邊界
*/
private static void merge(int[] arr,int left,int mid,int right){
int[] helpArr = new int[right - left + 1];//首先定義一個輔助數組
int lPoint = left;//左指針
int rPoint = mid + 1;//右指針
int i = 0;//輔助指針
while(lPoint <= mid && rPoint <= right){//比較並填充輔助數組
if(arr[lPoint] <= arr[rPoint])
helpArr[i++] = arr[lPoint++];
else
helpArr[i++] = arr[rPoint++];
}
while(lPoint <= mid){//將剩餘元素填充至輔助數組
helpArr[i++] = arr[lPoint++];
}
while(rPoint <= right){
helpArr[i++] = arr[rPoint++];
}
for(int j = 0;j < helpArr.length;j ++){//將輔助數組中的元素回填至原數組
arr[left + j] = helpArr[j];
}
}
最後,肯定終止條件,通常是數組爲空或者數組中只有一個元素,返回便可。
如今咱們能夠寫出整個歸併排序的代碼了,以下:
private static void mergeSort(int[] arr,int left,int right){
if(arr == null || right == left)//終止條件
return ;
int mid = left + (right - left) / 2;//肯定分割的邊界
mergeSort(arr,left,mid);//對左半部分調用遞歸方法,使其有序
mergeSort(arr,mid + 1,right);//對右半部分調用遞歸方法,使其有序
merge(arr,left,mid,right);//合併左右兩部分,使整個數組有序
}
爲了保證形式的統一,再對函數進行一下封裝,以下,這就是咱們的歸併排序了。
/**
* 歸併排序算法
* @param arr
*/
public static void mergeSort(int[] arr){
mergeSort(arr,0,arr.length - 1);//調用寫好的遞歸版歸併排序方法
}
至此,咱們便完成了歸併排序算法的代碼實現。
性能分析
在分析歸併排序算法性能以前,先介紹幾個基礎的概念。
時間複雜度:一個算法執行所消耗的時間;
空間複雜度:運行完一個算法所需的內存大小;
原地排序:在排序過程當中不申請多餘的存儲空間,只利用原來存儲待排數據的存儲空間進行比較和交換的數據排序。
非原地排序:須要利用額外的數組來輔助排序。
穩定排序:若是 a 本來在 b 的前面,且 a == b,排序以後 a 仍然在 b 的前面,則爲穩定排序。
非穩定排序:若是 a 本來在 b 的前面,且 a == b,排序以後 a 可能不在 b 的前面,則爲非穩定排序。
下面咱們分析下歸併排序算法的性能。
首先是時間複雜度。歸併排序算法在排序時首先將問題進行分解,而後解決子問題,再合併,因此總時間=分解時間+解決子問題時間+合併時間。分解時間就是把一個數組分解爲左右兩部分,時間爲一常數,即O(1);解決子問題時間是兩個遞歸方法,把一個規模爲n的問題分紅兩個規模分別爲n/2的子問題,時間爲2T(n/2);合併時間複雜度爲O(n)。因此總時間T(n)=2T(n/2)+O(n)。這個遞歸問題的時間複雜度能夠用下面的公式來計算
這個公式可針對形如:T(n) = aT(n/b) + f(n)的遞歸方程進行時間複雜度求解。帶入可知,歸併排序的時間複雜度爲 O(nlogn)。此外在最壞、最佳、平均狀況下歸併排序時間複雜度均爲O(nlogn)。
空間複雜度分析:在排序過程當中使用了一個與原數組等長的輔助數組,估空間複雜度爲O(n)。
穩定性分析:由排序過程能夠知道,歸併排序是一種穩定排序。
是否原地排序:排序過程當中用到了輔助數組,因此是非原地排序。
本文源碼已同步至github,地址:https://github.com/zhanglianchao/AllForJava/tree/master/src/algorithm/sort
以爲文章有用的話,在看+關注唄,好讓更多的人看到這篇文章。
更多關於算法、數據結構和計算機基礎知識的內容,歡迎掃碼關注個人原創公衆號「超悅編程 」。
本文分享自微信公衆號 - 超悅編程(gh_ca6d8e9bfd68)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。