排序問題
算法問題的基礎問題之一,便是排序問題:
輸入:n個數的一個序列,<a1, a2,..., an>。
輸出:一個排列<a1',a2', ... , an'>,滿足a1' ≤ a2' ≤... ≤ an' 。(輸出亦可為降序,左邊給出的例子為升序)
一.算法描述
(1)分治法
歸並排序是使用到了分治方法(Divide and Conquer)。
Divide:將原問題分解為若干子問題,其中這些子問題的規模小於原問題的規模。
Conquer:遞歸地求解子問題,當子問題規模足夠小時直接求解。
Merge:將子問題的解合並得到原問題的解。
(2)歸並排序的分治思想
分解:分解待排序的n個元素序列為各具n/2個元素的子序列
解決:使用歸並排序遞歸地對子序列排序
合並:合並兩個已排序的子序列
(3)歸並排序
MergeSort:假設我們要對一個輸入規模為n的序列A[1,2, ... , n]進行排序,我們可以考慮將該序列一分為二,左邊為A_left[1, 2, ... , ⌈n/2⌉],右邊為A_right[⌈n/2⌉+1, ... , n]。然后分別對兩邊規模為n/2的子問題進行求解,並將兩個解合並。(⌈n/2⌉是n除以2的向上取整結果)
Merge:合並算法是將兩個有序的序列A和B合並為一個有序的序列,這個過程只需要我們每次都取出A和B隊列的對首加入一個新隊列的末尾即可,直至某一個隊列為空,將另一個隊列的剩余元素補到新隊列后面。
下面我們給出一個對序列[5, 2, 4, 6, 1, 3, 8, 7]使用歸並排序得到遞增序列的過程。在圖中白色的部分是未排序的序列,每一層將序列規模折半分成左右兩個子序列排序,直至問題規模將為1,子問題規模足夠小時可以直接求解,而規模為1的序列本身就是有序的,所有不需要進行任何操作。最后一步是將所有子問題的解合並,每次都將兩個有序序列合並為一個有序序列,直至只剩下一個有序序列時終止。
下圖則給出了一個合並算法的例子。對於A,B兩個排序好的序列,每次取出A、B之中的較小值放入歸並后的序列,直到B中元素被取完,直接將A中的剩余元素按順序尾插到Merge序列中,就能得到A與B的合並。
二.代碼實現
下面是歸並排序的C++實現:
#include<iostream> #include<cmath> using namespace std; /** * @brief 對兩個有序序列合並 * @param arr 原數組 * @param start 待合並的數組起始下標 * @param end 待合並的數組結尾下標 * @param result 合並后的數組 */ void Merge(int* arr, int start, int end, int* result){ int i,j = 0; int mid = (ceil(start + end)/2); int left_index = start; int right_index = mid + 1; int result_index = start; //左右都非空時,循環取左右中的最小元素尾插到result數組 while( left_index < mid + 1 && right_index < end + 1){ if(arr[left_index] <= arr[right_index]){ result[result_index] = arr[left_index]; left_index++; } else{ result[result_index] = arr[right_index]; right_index++; } result_index++; } //左右有一個為空,則將非空的數組元素放到result末尾 while(left_index < mid + 1){ result[result_index] = arr[left_index]; result_index++; left_index++; } while(right_index < end + 1){ result[result_index] = arr[right_index]; result_index++; right_index++; } } /** * @brief 歸並排序 * @param arr 待排序的數組 * @param start 待排序的數組起始下標 * @param end 待排序的數組結尾下標 * @param result 保存臨時結果的數組 */ void MergeSort(int* arr, int start, int end, int* result) { //規模為0時不需進行任何操作 if(0 == end - start){ return; } else{ //遞歸地對左右序列排序,合並到result中以后覆蓋arr中的對應位置元素 MergeSort(arr,start,ceil((start+end)/2), result); MergeSort(arr,ceil((start+end)/2)+1,end, result); Merge(arr,start,end,result); for(int i = start; i <= end; i++){ arr[i] = result[i]; } } } int main() { int arr[10] = {5,2,4,7,10,9,8,1,6,3}; int result[10]; MergeSort(arr,0,9,result); for(int i = 0; i < 10; i++){ cout << arr[i] <<' '; } }
三.算法分析
(1)時間復雜度
對於最好,最壞和平均情況,問題都要將規模分解到1為止,所以三者的時間復雜度相同。
T(n) = 2T(n/2) + θ(n) n > 1時
T(n) = θ(1) n = 1時
通過畫遞歸樹分析可知T(n) = cnlgn + cn, 時間復雜度即為θ(nlgn),當然也為o(nlgn)。
(2)穩定性
穩定性是指對於原有序列中的等值元素,是否在排序后不改變它們的相對順序關系。歸並時當左邊不大於右邊時先取左邊的元素,遵循這樣的規律時歸並排序就是穩定的。
(3)適合范圍
一般用於對總體無序,但是各子項相對有序的數列。
(4)算法改進
TimSort,結合了插入排序來優化,具體不詳細展開。