1.基本思想
歸併排序(Merge Sort)的核心是分治,將一個複雜問題分解成多個相同或相似的子問題,然後把子問題分解成更小的問題,知道子問題可以簡單地求解,最原始問題的解就是子問題解的合併。
一開始先將數組從中間分成兩個子數組,一直遞歸把子數組劃分成更小的子數組,直到子數組裏只包含一個元素,這時纔開始排序。排序的方法就是按照大小的順序合併兩個元素,接着依次按照遞歸返回的順序,不斷合併排好序的子數組,直到最後把整個數組的順序排好。
例題:利用歸併排序算法對數組 [2, 1, 7, 9, 5, 8] 進行排序。
2.C++代碼實現
編譯環境:win10系統,vs2013
#include <iostream>
#include <vector>
using namespace std;
//合併函數
void merge(vector<int> &arr, int l, int m, int r) {
vector<int> arr_copy(arr);
//定義一個變量k,表示從什麼位置開始改變原來的數組
//i表示左邊部分的起始位置的下標,j表示右邊部分的起始位置的下標
int k = l, i = l, j = m + 1;
while (k <= r) {
if (i > m) {
arr[k++] = arr_copy[j++];
}
else if (j > r) {
arr[k++] = arr_copy[i++];
}
else if (arr_copy[i] > arr_copy[j]) {
arr[k++] = arr_copy[j++];
}
else {
arr[k++] = arr_copy[i++];
}
}
}
//歸併排序
void mergeSort(vector<int> &arr, int l, int r) {
//判斷是否只剩下一個元素
if (l >= r) return;
//將數組從中間分成兩部分呢
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
//將排好序的左右兩半合併
merge(arr, l, m, r);
}
//打印vector
void printVector(vector<int> &v) {
for (auto &i : v)
cout << i << " ";
cout << endl;
}
int main()
{
vector<int> vec = { 2, 1, 7, 9, 5, 8 };
cout << "給定數組爲:";
printVector(vec);
mergeSort(vec, 0, vec.size() - 1);
cout << "歸併排序後:";
printVector(vec);
system("pause");
return 0;
}
merge函數
中,While 語句中,while的結束條件爲k<=l
,一共可能會出現四種情況。
- 左半邊的數都處理完畢,只剩下右半邊的數,只需要將右半邊的數逐個拷貝過去。
- 右半邊的數都處理完畢,只剩下左半邊的數,只需要將左半邊的數逐個拷貝過去就好。
- 右邊的數小於左邊的數,將右邊的數拷貝到合適的位置,j 指針往前移動一位。
- 左邊的數小於右邊的數,將左邊的數拷貝到合適的位置,i 指針往前移動一位。
輸出
給定數組爲:2 1 7 9 5 8
歸併排序後:1 2 5 7 8 9
請按任意鍵繼續. . .
3.算法分析
空間複雜度:O(n)
由於合併n個元素需要額外分配一個大小爲n的數組,合併完成後釋放,所以算法的空間複雜度爲O(n)。歸併排序也是穩定的排序算法
。
時間複雜度:O(nlogn)
歸併排序是一種遞歸算法,時間複雜度可以表示爲以下遞歸關係:T(n) = 2×T(n/2) + O(n)
公式解釋:
舉例:數組的元素個數是 n
,時間複雜度是T(n)
的函數。
解法:把這個規模爲n的問題分解爲兩個規模爲n/2
的子問題,每個子問題的時間複雜度爲T(n/2)
,則兩個子問題的複雜度之和爲2xT(n/2)
。當兩個子數組都排好序了,需要將它們合併,一共有n
個元素,需要進行n-1
次的比較,所以合併的複雜度爲O(n)
。因此遞歸複雜度的公式爲:T(n) = 2×T(n/2) + O(n)
對於公式的求解,是將規模爲n
的問題分解爲規模爲n/2
的問題,一直分解爲規模爲1。如果n爲2,需要分1次,如果n爲4,需要分2次,以此類推,如果規模就爲n,則需要分logn
次。而在每一次的合併中,所涉及到的元素就是數組中的所有元素,因此每次合併的複雜度都是O(n)
。所有整體的複雜度爲O(nlogn)
。