最近在看算法導論第三版,練習題2.3-2要求根據書中介紹,使用不帶哨兵的歸併排序算法。
歸併排序的基本思想可以理解爲:將數組二分,分成不能再分的單個元素爲止,然後將兩個單個的元素比較,按照大小順序合併,一層一層向上遞歸。
如果不使用哨兵,就需要人工去檢測邊界,根據邊界的變化處理合並時,可能出現的越界情況,算法性能分析參考算法導論。
歸併算法流程:(注意這裏使用的下標跟算法導論中原始的下標有出入,本作者自己覺着原書的寫法用起來不方便)
算法流程:
Merge_sort(A,p,q):
if(p<q):
r=floor(p+q);
Merge_sort(A,p,r);
Merge_sort(A,r+1,q);
Merge(A,p,r,q);
template<typename T>
void merge_sort(std::vector<T> & input,int p,int q)
{
if (p < q) //左邊界必須要於右邊界
{
int r = (int)floor((q + p) / 2);
merge_sort(input, p, r); //將數組分成兩個部分---前部分
merge_sort(input, r + 1, q); //將數組分成兩個部分---後部分
merge<T>(input, p, r, q); //合併
}
}
最關鍵的部分就是合併!
上圖爲算法導論帶哨兵的算法流程,我做的改進是在加入條件判斷。在合併的時候,由於分出左右數組,左右數組的元素個數是在合併之前知道的,所以如果有一個數組的元素都被合並進去了,則直接將另外一個數組的元素按現有順序放到新的數組中即可,知道填滿。
template<typename T>
void merge(std::vector<T> &input, int p, int r, int q)
{
int n1 = r - p+1; //計算合併的所有左數組元素個數
int n2 = q - r; //計算合併的右數組元素個數
std::vector<T> l_vector, r_vector;
for (int i = 0; i < n1+n2; i++) //拷貝數據
{
if (i < n1)
l_vector.push_back(input[p + i]);
else
r_vector.push_back(input[p + i]);
}
int i = 0;
int j = 0;
for (int k = 0; k <= q - p; k++)
{
if (i < n1 && j < n2) //如果左右數組都存在未被拷貝完的元素,選擇小的拷貝,並將相應的指針向後移動一個位置,直到結束
{
if (l_vector[i] < r_vector[j])
{
input[p+k] = l_vector[i];
i++;
}
else
{
input[p+k] = r_vector[j];
j++;
}
}
else if (i < n1 && j == n2) //這是右側數組元素拷貝完成而左側數組拷貝未完成,直接將左側數組元素按照順序向合併數組拷貝即可
{
input[p+k] = l_vector[i];
i++;
}
else if(i==n1 && j<n2) //這是左側數組元素拷貝完成而左側數組拷貝未完成,直接將右側數組元素按照順序向合併數組拷貝即可
{
input[p+k] = r_vector[j];
j++;
}
else if (n1 == 1 && n2 == 0) //處理邊界情況,即存在級數個數是一定會發生只有一個元素,此時左側有元素,右側無元素,直接將單個元素拷貝即可,不需要比較大小
{
input[p+k] = l_vector[0];
}
}
}
測試用例如下:
void test_merge_sort()
{
std::vector<int> v = { 4,3,1,1 };
merge_sort(v, 0, v.size()-1);
for (auto x : v)
std::cout << x << std::endl;
}