排序演算法是對一組數進行順序排序或者逆序排序,而合併排序就是排序演算法的一種。合併排序用到了分治策略實現對元素進行排序。
合併排序的基本思想:把待排序的n個元素分解成n組,也就是每組乙個元素;之後對分好的組進行兩兩合併(無配對的則不操作),以此類推。
以序列為例,排序過程如下:
**合併排序又叫做2-路歸併排序,是因為它每次都是兩兩歸併。
/**
* 合併src[left:mid] src[mid+1:right] 到dest[left:right]
* @param src: 源陣列
* @param dest: 目的陣列
* @param left: 陣列起始索引
* @param mid: 源陣列的中間索引
* @param right: 目的陣列的結束索引
*/templatevoid merge(type src, type dest, int left, int mid, int right)
//把剩下的陣列元素拷貝到目的陣列
if (i > mid)
for (int q = j; q <= right; q++)
dest[k++] = src[q];
else
for (int q = i; q <= mid; q++)
dest[k++] = src[q];
}
merge函式用於把src[left:mid] 和src[mid + 1: right]歸併到dest的對應位置。其思路如下
設定兩個索引變數i和j儲存當前指向的src[left:mid] 和src[mid + 1: right]的索引;
每次從i 和j 所指向的src[i] src[j]中取乙個小值依次放到dest的對應位置,然後這個索引加一;
重複2步驟直至遍歷完短陣列;
把剩餘的值拼接到dest陣列中。
/**
* src[left:right]複製到dest[left:right]
* @param src: 源陣列
* @param dest: 目的陣列
* @param left: 起始索引
* @param right: 結束索引
*/templatevoid copy(type src, type dest, int left, int right)
}
copy的作用就是陣列複製。
/**
* 用於合併排序好的相鄰陣列段
*/templatevoid mergepass(type x, type y, int step, int n)
if (i + step < n)
merge(x, y, i, i + step - 1, n - 1);
else
for (int j = i; j < n ; j++)
y[j] = x[j];
}
mergepass函式主要用於合併排序好的相鄰陣列,
當step為1,時,此時陣列被分成了n份,mergepass的while就是把上面的單個元素兩兩合併;
當step為2,時,則對最多為2個元素的子陣列進行兩兩合併。
以此類推
在之後還有乙個判斷:i + step < n,這裡舉例子來說明:
比如此時進行到了第二次歸併,即step = 2, 元素如下:
x = ,
i = 0時,合併的是x[0: 1]和x[2:3],之後i = 0 + 2 * 2 = 4;
i = 4,時,得到i <= 7 - 2 * 2 = 3 不成立,所以不再迴圈。
雖然迴圈不成立,但是可以看到, 還是滿足i + step < n的,所以這裡又加了最後乙個歸併。
而當x = ,step=2的情況下則會直接進行複製。
從上面的舉例可以看出,賦予step遞增的值,則可以實現歸併排序。
templatevoid mergesort(type a, int n)
delete temp;
}
在mergesort函式中,呼叫mergepass來實現歸併操作。
接著測試一下:
int main() ;
int len = sizeof(a) / sizeof(a[0]);
//合併排序
mergesort(a, len);
for (int i = 0; i < len; i++)
cout << a[i] << " ";
cout << endl;
return 0;
}
自然合併排序是上述所說的合併排序演算法的乙個變形。之前說過,合併排序是從乙個元素開始兩兩歸併的,而當乙個組內只有乙個元素時,能夠保證它是排序好的。基於上面的原理,給定乙個陣列,一般都會存在多個長度大於1的已自然排好序的子陣列段。比如對於乙個陣列。自然排序的子陣列段為 ;第一次排序可以得到 ;第二次排序得到 ;第三次排序則可以得到遞增序列。
上面需要解決的問題就是如何儲存每次的子陣列段的開始和結束,因為子陣列段的大小不是固定的,所以還需要乙個陣列去維護子陣列的邊界。
templatevoid mergesort(type a, int len)
//回寫到陣列a
for (int i = 0; i < len; i++)
a[i] = temp[i];
number = get_order(a, len, indices);
} delete temp;
}
新的mergesort函式用到了子陣列段,其中維護了indices陣列來儲存邊界,
首先可以看到indices的大小為len + 1,這是最壞情況下的indices的最大值,當陣列完全逆序的時候,比如要把排序成時,我們可以一眼看出只需要完全翻轉就可以了,不過程式則不知道,按照之前的步驟來說,以上分成了 共5個,再加上一開始的-1,所以最大陣列長度設為了len + 1。
第乙個元素為-1,主要是為了應對之後的排序。以為例子。自然排序的子陣列段為 ;那麼indices的值為。那麼需要a[-1 + 1: 1]和a[1 + 1, 4]進行歸併,a[4 +1: 5]和a[5 + 1:6]歸併...
每一次的迴圈都會重新獲取排序好的子陣列段(其實可以從之前得到的)。
/**
* 獲取陣列a的自然排好序的子陣列段,返回結束索引
* @param a: 陣列
* @param len: 陣列a的長度
* @param indices: 排序好的結束索引陣列 需要確保長度足夠
* @return: 返回indices真正的長度
*/templateint get_order(type a, int len, vector& indices)
index++;
} indices[i++] = len - 1;
return i;
}
根據mergesort和get_order而得到的indices陣列來說,必定存在的就是-1和最後乙個元素的索引,所以當indices的實際個數為2的時候,表示排序結束。
本測試在leetcode上執行,題目為:排序陣列
普通歸併排序執行:
自然歸併排序執行:
可以看到,自然歸併排序的執行用時要小於普通歸併排序,不過記憶體消耗略微增加。
分治法 合併排序
合併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用 分治法 divide and conquer 的乙個非常典型的應用。合併排序法是將兩個 或兩個以上 有序表合併成乙個新的有序表,即把待排序序列分為若干個子串行,每個子串行是有序的。然後再把有序子串行合併為整體有序序列。將已有序的子串行...
分治法合併排序(C )
參考 include include include using namespace std 合併函式 void merge int arr,int p,int q,int r for int j 0 j len2 j l len1 r len2 int max 定義無窮大 int i 0,j 0 ...
基礎演算法 合併排序(分治法)
基礎演算法 合併排序演算法 分治法 的宗旨是將問題 分解 處理 歸併 將書中的偽 翻譯為c c 語言實現,大致可用兩個函式來解決問題,第乙個函式實現 治 也就是分解問題,處理問題 的步驟,另乙個函式通 過遞迴實現 分 合 的操作。實現 分 治 的函式實現如下 void merge int parra...