歸併排序核心思想:
合併兩個有序數組爲一個有序數組是很輕鬆的。
步驟:
- 將一個數組不斷的從中間分成兩個數組
- 對分後的兩個數組不斷重複步驟一,直到數組的長度爲1
- 對分成的兩個數組進行合併,此時就是分成的兩個數組都已經是有序的
這段時間複習了一下數據結構的知識,對歸併排序進行了重新的編寫,採用的java語言,但通過測試20萬個隨機數據發現,歸併排序的效率和選擇排序的效率竟然差不多,對此我陷入了深深的懷疑,究竟是哪個地方出錯了呢?
在此,先貼出原代碼:
public int[] mergeSort(int[] array) {
//採取先分後治的思想
if(array.length > 1){
//分
int[] left = mergeSort(leftArray(array));
int[] right = mergeSort(rightArray(array));
//合
array = mergeArray(left,right);
}
return array;
}
//獲取數組的左半邊
private int[] leftArray(int[] a){
int[] b = new int[(a.length + 1)/2];
for (int i = 0; i < b.length; i++) {
b[i] = a[i];
}
return b;
}
//獲取數組的右半邊
private int[] rightArray(int[] a){
int[] b = new int[a.length/2];
int lenA = (a.length + 1) / 2;
for (int i = 0; i < b.length; i++) {
b[i] = a[i + lenA];
}
return b;
}
//合併兩有序數組
private int[] mergeArray(int[] left, int[] right){
int[] a = new int[left.length + right.length];
int indexL = 0;
int indexR = 0;
int lenL = left.length;
int lenR = right.length;
for (int i = 0; i < a.length; i++) {
a[i] = left[indexL] < right[indexR] ? left[indexL++] : right[indexR++];
if(indexL == lenL || indexR == lenR){
//滿足條件跳出循環
break;
}
}
if(indexL == lenL){
//將右側數組依次裝進合併數組內
for (int i = indexL + indexR; i < a.length; i++) {
a[i] = right[indexR++];
}
}else if(indexL == indexR){
//將左側數組依次裝進合併數組內
for (int i = indexL + indexR; i < a.length; i++) {
a[i] = left[indexL++];
}
}
return a;
}
經過思索可能造成速度緩慢的因素(打死我都不認爲是歸併的思想出了問題),在共產黨的帶領下,我揪出了那個問題的兇手,沒錯,兇手只有一個,那就是不斷的申請新數組,開闢空間是很耗時的,難怪效率那麼慢了,原來大部分的時間都用來開礦了!慚愧,慚愧,這就是當代程序員濫用空間的血淋淋的例子,懷念遠古時期,前輩們用個變量都要思考着個變量是否必要使用,生怕造成一點空間的浪費,額,扯遠了。。。
知道了問題的原因,那就要着手去解決這個問題,既然開闢空間耗時,那就不開闢空間了,儘量用原來的數組空間,頂多加個臨時數組,那這樣問題能得到解決嗎?說實話,我現在也不知道運行起來效率能提升多少,但提升是肯定的,期待中…
修改後的代碼,如下:
public int[] mergeSort(int[] array) {
if(array == null || array.length == 0){
return null;
}
//該temp爲了減少doMerge中的創建臨時數組造成的性能下降而提前設置的
int[] temp = new int[array.length];
merge(array, 0, array.length - 1, temp);
return array;
}
private void merge(int[] array, int start, int end, int[] temp){
if(start < end){
//分
int mid = (start + end) >> 1;
merge(array, start, mid, temp);
merge(array, mid + 1, end, temp);
//治
doMerge(array,start,mid,end, temp);
}
}
private void doMerge(int[] array, int start, int mid, int end, int[] temp){
int left = start;
int right = mid + 1;
int index = start;
while(left <= mid && right <= end){
temp[index++] = array[left] > array[right] ? array[left++] : array[right++];
}
//將剩餘一邊的數組全部依次導入
while(left <= mid){
temp[index++] = array[left++];
}
while (right <= end){
temp[index++] = array[right++];
}
//將臨時數組導入array
for (int i = start; i <= end; i++) {
array[i] = temp[i];
}
}
話不多說,有什麼問題跑起來再說,噹噹噹,結果如下:
OMG!怎麼會這樣,啊啊啊啊啊啊啊啊!
我一定是看錯了,不行我要去看眼科,什麼情況啊,我改動了那麼大,怎麼會一點變化都沒有,而且跟選擇排序時間基本一樣。
我好像,發現了什麼,跟選擇排序的時間基本一樣=.=
呵呵,歸併排序調用了選擇排序
辣眼睛,=.=
改回來吧,看看改後的歸併排序效率有多高
再對比下,改之前的歸併排序時間
唔,證明我的改動還是相當讚的,20完個數據快了80%,現在加大數據量,看看效果如何。
先看老版的一億個隨機數的排序時間:
在看新版的一億個隨機數的排序時間
唔,大體這樣了,超大數據的時候快了25%吧,還是沒達到我的預期,我覺得起碼要快兩倍呢,桑心