排序算法之二——合并排序

有很多算法在结构上是递归的:为了解决一个给定的问题,算法要一次或多次地递归调用起自身来解决相关的子问题 。这些算法通常采用分治策略:将原有问题划分成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;
}

运行结果如下:


 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值