归并排序(MergeSort)

    归并排序是数据结构中常用的一种排序算法,它原理上简单,易于理解。它基于分治法策略:先做划分,再做排序。归并排序的思想来源于一个朴实的想法,即,部分有序的序列要比完全无序的序列更便于排序。我们先给出算法的伪码:

 

dataList mergeSort(dataList& L) {
    if(Length(L) <= 1) {
        return L;
    }
    dataList L1 = L的左半部分;
    dataList L2 = L的右半部分;
    return merge(mergerSort(L1), mergeSort(L2));
}

    算法的伪码也很容易理解,首先对于一个初始的序列L,我们先将其不断二分,得到一个个单元素序列,然后依次对每个序列使用merger函数做归并,下面给出算法的过程示意:

21 25 49 25* 16 08 31 41 \to 21 25 49 25*  16 08 31 41 \to \cdots \to21 25 49 25* 16 08 31 41

\to 21 25 25* 49 08 16 31 41 \to 21 25 25* 49 08 16 31 41 \to 08 16 21 25 25* 31 41 49

 从上面简单的二路归并例子我们可以看到归并排序的一般步骤,并且知道归并排序是一种稳定的排序算法。下面给出归并排序的算法。首先我们给出归并步骤的算法:

template<class T>
void merge(dataList<T>L1, dataList<T>L2, int left, int right, int mid) {
    for(int k=left; k<=right; k++) {
        L2[k] = L1[k];
    }
    int s1 = left, s2 = mid + 1, t = left;
    while(s1 <= mid && s2 <=right) {
        if(L2[s1]<=L2[s2]) L1[t++] = L2[s1++];
        else L1[t++]=L2[s2++];
    }
    while(s1<=left) L1[t++] = L2[s1++];
    while(s2<=right) L1[t++] = L2[s2++];
}

有了归并步骤的算法之后,我们就可以完成一个简单的二路归并算法:

template<class T>
void mergeSort(dataList<T>L, dataList<T>L2, int left, int right) {
    //初始情况下,这里的L2是一个空序列
    if(left > right) return;
    int mid = (left + right) / 2;
    mergeSort(L, L2, left, mid);
    mergeSort(L, L2, mid+1, right);
    merge(L, L2, left, right, mid);
}

    可以看到,归并算法需要一个辅助数组,其实他就是一个典型的空间换时间的排序算法,空间消耗为O(n),时间消耗一般为:T(n)=cn+2T(n/2)。综合来看,其时间复杂度是O(nlogn)


    下面介绍一种简单的,对二路归并做改进的方法:

    我们看到,归并步骤时,我们需要判断指针s1和s2的位置是否超出了所在数组的限制。我们也发现,真正做归并排序的时候我并没有对数组做物理上的划分,仅仅是设置不同的位置记号来区分我们需要的子序列。其实我们可以有有一个取巧的办法,就是往L2中复制数组的时候,可以一个由left往mid复制,一个由right往mid复制。这样可以设置s1=left,s2=right,当s1==s2的时候,排序结束,具体的,我们来看相应的代码:

template<T>
void improvedmerge(dataList<T>L1, dataList<T>L2, int left, int right, int mid) {
	int s1=left, s2=right, t=left, k;
	for(k=left; k<=mid; k++) {
		L2[k]=L1[k];
	}
	for(k=mid+1; k<=right; k++) {
		L2[right+mid+1-k] = L1[k];
	}
	while(t<=right) {
		if(L2[s1]<=L2[s2]) L1[t++]=L2[s1++];
		else L1[t++]=L2[s2--];
	}
}

同时,我们可以设置阈值,当子序列长度小于固定值K的时候,我们使用插排,这样可以提升实际运行时候的代码运行速度,但是从数量级角度而言,算法的时间复杂度是不变的。


当然了,我们也很容易想到,既然有二路归并,自然也会有三路归并、四路归并。他们计算机中的磁盘读写中经常会用到,有兴趣可以参考算法导论和操作系统。

 

Reference:
数据结构(用面向对象方法与C++语言描述).殷人昆.清华大学出版社

算法导论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值