歸併排序是乙個非常常用的排序演算法,基礎是對乙個陣列的兩個已排序子陣列的排序。
用歸併排序對n個元素的陣列進行排序時,當n為2的冪時,元素比較次數在(n log n)/2到n log n - n +1之間,元素被賦值次數為2n log n。時間複雜度o(n log 2n),速度僅次於快排,比較穩定。
現在分塊來解釋演算法實現過程:
一、演算法思想
歸併排序的演算法思想基於對乙個陣列的兩個已排序子陣列的排序–merge。歸併排序先將陣列進行分割,直到每個子陣列只有乙個元素,這樣就可以將相鄰的兩個子陣列看成是兩個已排序的陣列,構成merge演算法的先決條件,就可以用merge演算法進行排序,構成乙個長度翻倍的子陣列。對整個陣列進行一次小長度的merge演算法後,可以構成乙個長度翻倍的merge演算法的條件而進行merge演算法,最終對整個陣列實現排序。
二、基礎–merge
(一)merge演算法的前提:乙個陣列可以劃分為兩個已排序的子陣列,如1 4 7 8 2 5 10
,此陣列可以劃分為兩個已排序的子陣列:1 4 7 8
和2 5 10
。
(二)merge演算法的思想:
1、定義乙個臨時陣列,長度為兩個子陣列的長度之和。int b = new int[high - low]
2、從兩個子陣列的頭部開始進行比較,將較小的元素放入臨時陣列
int s = 0,t = 0,i = 0;
int l1 = mid - low;//第一部分的長度
int l2 = high - mid;//第二部分的長度
//從兩個陣列的頭開始比較,將較小值填入臨時陣列
while(s < l1 && t < l2)
else
i ++;
}
直到某乙個陣列全部放入臨時陣列,然後將另乙個陣列的剩餘部分放入臨時陣列,構成乙個長度與原陣列相同,且已排序的陣列
//某一陣列全部填入臨時陣列之後,將另乙個陣列的餘下部分填入臨時陣列
if(s != l1)
}else
}
因兩個子陣列是已排序的,所以各選乙個進行比較之後就可確定更小元素在排好序的陣列中的位置,而無需考慮其他的問題。
三、分割陣列
歸併排序的第一步是對陣列進行分割,只有分割到滿足merge演算法的前提,才能使演算法正確執行。
為了構成merge演算法的前提,所以必須分割到最小限度。
我用乙個step來表示分割後的子陣列長度,step是乙個2的整數次冪,如:step:1->2->4->8->..
當step為1的子陣列排序完之後,就自然構成了多個滿足merge演算法前提的step為2的子陣列,如此迭代即可完成演算法。
//因歸併排序的第一步是劃分,步長一步步翻倍
//因待排序的陣列的長度可能是奇數,而步長總是2的整數倍,故將step的上限定為陣列長度的一半並向上取整,即c.length/2 + 1
while(step
<= c.length/2 + 1)
//若不是上述情況,則正常分割
merge(c,i,i+step,i+step * 2);
}step = step * 2;//步長翻倍增長
}
此處有乙個非常關鍵的問題,就是step的界限控制,及傳遞給merge演算法的引數控制問題。
(一)step的界限控制
step是用來控制分割的關鍵引數,因原陣列的長度可能為奇數,而step總是2的整數次冪,所以若不進行區別控制,將會導致最後結果為乙個可以分割成兩個已排序的子陣列的新陣列,而沒有進行最後的一步歸併排序,原因就在於step的界限控制。
這裡我將step的界限控制在step <= c.length/2 + 1
即step的上限定為陣列長度的一半並向上取整,這樣即使存在奇數的原陣列長度,也可進行完全的歸併排序。
例子:
1、原陣列:5 2 4
,陣列長度為3,是奇數,且3/2 + 1 = 2,即須排序2次。
驗證:第一次排序:step = 1
,5 2
=>2 5
=>2 5 4
;第二次排序:step = 2
,2 5 4
=>2 4 5
,排序2次,結果正確。
若不向上取整,則3/2 = 1,只排序1次,所以結果必然是錯誤的。
2、原陣列:5 2 4 6
,陣列長度為4,是偶數,且4/2+1 = 3,而step為2的整數次冪,所以排序次數為2次。
驗證:第一次排序:step = 1
,5 2 4 6
=>2 5 4 6
;第二次排序:step = 2
,2 5 4 6
=>2 4 5 6
,排序2次,結果正確。
在對所有元素進行一次歸併後,需要將step翻倍,即step = step * 2
(二)傳遞給merge演算法的引數控制
1、引數解釋
merge演算法的引數可以根據需求設定,此處我設定為4個引數merge(int a,int low,int mid,int high)
,a為原陣列,low為在陣列a中須排序的部分的最小位置,mid為兩個已排序的子陣列的分割,high為在陣列a中須排序的部分的最大位置。如2 5 4 6
,在次數組中,low為0,mid為2,high為4。所以明顯的,第乙個子陣列的長度為mid-low = 2
,第二個子陣列的長度為high-mid = 2
,結果正確。
2、引數控制
因為原陣列的長度可能為奇數,而step為2的冪,所以會存在第一次排序時,最後乙個子陣列沒有歸併物件,在之後的排序中,兩邊陣列的長度不等的情況,若不加區別控制,則會造成陣列越界的問題。
例子:
原陣列:2 5 4
過程:
一次merge:step=1,第一部分2 5
構成可用merge(c,i,i+step,i+step * 2)
進行排序的陣列,即merge(c,0,1,2)
,結果正確且未陣列越界;第二部分4
,沒有歸併物件,若用merge(c,i,i+step,i+step * 2)
即merge(c,2,3,4)
,明顯陣列越界。
所以需做如下控制:
if((i + step * 2) > c.length)
//若不是上述情況,則正常分割
merge(c,i,i+step,i+step * 2);
在此處,我加上了區別控制。
當發現i + step * 2
,即引數high超過了原陣列的長度,則表明最後乙個子陣列不能滿足兩個子陣列長度相等的情況,故不能用普遍的引數merge(c,i,i+step,i+step * 2)
來處理,而需要將最後乙個引數改為c.length
,確保在後續操作時,不出現陣列越界的情況。
merge演算法並不需要兩個子陣列的長度相等,所以這樣不會造成演算法的失敗。
四、具體演算法
附上我寫的歸併排序演算法
public
class bottomupsort
else
i ++;
}//某一陣列全部填入臨時陣列之後,將另乙個陣列的餘下部分填入臨時陣列
if(s != l1)
}else
}//將原陣列被排序部分用臨時陣列替換
for(i = 0;i < high - low;i++)
}void combine(int step)
//若不是上述情況,則正常分割
merge(c,i,i+step,i+step * 2);
}step = step * 2;//步長翻倍增長}}
public
static
void
main(string args);
system.out.print("data: ");
for(int i = 0;i < b.c.length;i++)
//進行歸併排序
b.combine(1);
system.out.print("\nafter sort,result: ");
for(int i = 0;i < b.c.length;i++)
}}
八大排序之歸併排序
歸併排序 merge sort 是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法 divide and conquer 的乙個非常典型的應用。我們可以這樣簡單的理解 將乙個陣列分為兩個陣列arr1 arr2,假如這兩個陣列有序,再將這兩個陣列歸併為乙個陣列,並確保歸併後的陣列也有序。關...
八大排序之歸併排序
todo 歸併排序 public class mergesort 測試時間複雜度 o n log n int arr newint 80000 int temp newint arr.length for int i 0 i arr.length i long start time system.c...
八大排序 歸併排序
歸併排序 1.申請空間,使其大小為兩個已經排序的序列之和,用來存放合併後的序列。2.設定兩個指標指向兩序列的開始。3.比較兩個指標所指的元素,選擇小的放的合併空間,移動指標。4.重複3直到某個指標到達序列尾。5.將剩餘的元素拷到合併空間。實現 void merge sort int arr,int ...