有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次或多次地递归调用起自身来解决相关的子问题 。这些算法通常采用分治策略:将原有问题划分成n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
分治模式在每一层递归上都有三个步骤:
分治(Divide):将原问题分解成一系列子问题;
解决(Conquer):递归地解各子问题。若子问题足够小,则直接求解;
合并(Combine):将子问题的解合并成原问题的解
合并排序(Mergesort)算法完全依照了上述模式,直观地操作如下:
分解:将n个元素分成各含n/2个元素的子序列;
解决:用合并排序法对两个字序列递归地排序;
合并:合并两个已排序的子序列。
在对子序列排序时,其长度为1时递归结束。单个元素被视为已排序的。
合并排序的关键步骤在于合并两个已排序的子序列。为此,引入一个辅助过程MERGE(A, p, q, r),其中A是个数组,p、q、r是下标,满足p <= q < r。该过程假设子数组A[p .. q]和A[q+1 .. r]都已排好序,并将它们合并成一个已排好序的子数组代替当前子数组A[p..r]。
合并排序的算法如下:
MERGE-SORT(A, p, r)
//如果只有一个元素,即p = r,则递归结束,否则继续分治
if p < r
then q = (p + r) / 2 向下取整
//以q为界,将数组分解成两个子数组,对子数组进行排序,然后再将已排序的子数组合并
MERGE-SORT(A, p, q)
MERGE-SORT(A, q + 1, r)
MERGE(A, p, q, r)
辅助过程MERGE的伪代码如下:
MERGE(A, p, q, r)
//子序列A[p .. q]的长度
n1 = q - p + 1
//子序列A[q+1 .. r]的长度
n2 = r -q
//创建临时数组L和R
create arrays L[1 .. n1+1] and R[1 .. n2+1]
//将子序列A[p .. q] 和 A[q+1 .. r]分别备份到数组L和R中。
for i = 1 to n1
L[i] = A[p + i - 1]
for j = 1 to n2
R[j] = A[q + j]
//放置哨兵
L[n1 + 1] = ∞
R[n2 + 1] = ∞
//合并已排序子数组,使得A[p .. r]有序,参考下面的示意图
i = 1
j = 1
for k = p to r
if L[i] <= R[j]
then A[k] = L[i]
i++
else A[k] = R[j]
j++
合并已排序子数组的示意图(展示了当k = p + 4时,各个下标的取值。此时,应该挑选L[i]覆盖A[k],然后k和i向后移动):
说明:
1、数组A中绿色部分,表示子数组A[p .. r]排序后的最终值,灰色部分表示将被覆盖的值。数组L和R中,绿色部分表示已被复制会A中的元素,白色部分表示有待被复制回A的值。
2、在合并过程中,如果不设置哨兵元素的话,在对L和R中的元素进行比较时,需要判断下标是否越界(或则比较是否已到数组末尾)。
如果加入哨兵元素,则可简化判断。比如,示意图中数组R将先被合并完成,j指向∞,此后L中所有的元素(除了哨兵),都比R当前的元素(哨兵)小,直接将L中的元素覆盖A中对应的元素即可。
总结:合并算法采用分治策略,时间复杂度为o(nlgn)。
今天抽点时间,写了一个归并排序的模板,适用于内置类型和用户自定义类型,但要求用户自定义类型重载“<”和“>”操作以及输出操作符。代码如下:
- #include <iostream>
- using namespace std;
- template<typename T>
- bool ElemAsc(const T &left, const T &right)
- {
- return left < right;
- }
- template<typename T>
- bool ElemDesc(const T &left, const T &right)
- {
- return right < left;
- }
- template<typename T, class CompareFunc>
- void Merge(T *pArray, int iLow, int iMid, int iHigh, CompareFunc func)
- {
- //左边子数组的长度
- int iLeftLen = iMid - iLow + 1;
- T *pLeft = new T[iLeftLen];
- //备份
- memcpy(pLeft, pArray + iLow, iLeftLen * sizeof(T));
- //右边子数组的长度
- int iRightLen = iHigh - iMid;
- T *pRight = new T[iRightLen];
- //备份
- memcpy(pRight, pArray + iMid + 1, iRightLen * sizeof(T));
- //归并(这里没法放置哨兵,因为无法知道T类型的极值,所以这里增加了对子数组是否越界的处理)
- int iLeftIndex = 0, iRightIndex = 0;
- for(int iFinalIndex = iLow; iFinalIndex <= iHigh; iFinalIndex++)
- {
- //特殊情况1:右边子数组已经合并完成
- if(iRightIndex == iRightLen)
- {
- pArray[iFinalIndex] = pLeft[iLeftIndex++];
- continue;
- }
- //特殊情况2:左边子数组合并完成
- if(iLeftIndex == iLeftLen)
- {
- pArray[iFinalIndex] = pRight[iRightIndex++];
- continue;
- }
- //调用比较函数
- if(func(pLeft[iLeftIndex], pRight[iRightIndex]))
- {
- pArray[iFinalIndex] = pLeft[iLeftIndex++];
- }
- else
- {
- pArray[iFinalIndex] = pRight[iRightIndex++];
- }
- }
- }
- template<typename T, class CompareFunc>
- void MergeSort(T *pArray, int iLow, int iHigh, CompareFunc func)
- {
- //如果只有一个元素,直接退出
- if(iLow == iHigh)
- {
- return;
- }
- //递归
- int iMid = (iLow + iHigh) / 2;
- MergeSort(pArray, iLow, iMid, func);
- MergeSort(pArray, iMid + 1, iHigh, func);
- //合并
- Merge(pArray, iLow, iMid, iHigh, func);
- }
- template<typename T>
- void PrintArray(const T *pArray, int iSize)
- {
- for(int i = 0; i < iSize; i++)
- {
- cout << pArray[i] << "--";
- }
- cout << endl;
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- int arrTest[10] = {5, 2, 4, 8, 1,10, 9, 6, 3, 7};
- cout<<"Original Array:" << endl;
- PrintArray(arrTest, 10);
- //升序排列
- MergeSort(arrTest, 0, 9, ElemAsc<int>);
- cout<<"Sort Asc:"<<endl;
- PrintArray(arrTest, 10);
- //降序排列
- MergeSort(arrTest, 0, 9, ElemDesc<int>);
- cout<<"Sort Desc:"<<endl;
- PrintArray(arrTest, 10);
- return 0;
- }