算法4:合并排序的套路 | 重用 | 分治

“重用与增量有序”的设计套路,前文已经介绍,这次介绍另一个招数--重用与分治的设计思想,这个套路在合并排序的算法设计中有所体现。

重用已经是一种被广泛使用的套路,小程之前介绍了重用的含义,而合并排序的重用,体现在对自身的反复调用。首先,认定合并排序算法就是让数列有序的,只要经过它处理,就一定会变得有序,这个信念很重要,做人要信。然后,把数列分为两部分,分别重用合并排序,重用完,这两部分就变得有序了。最后,把有序的两部分数列,合并起来,就解决了排序的问题。

也就是,合并排序本身就是一个标准作业,可以反复被重用。

选定标准作业,是很重要的,它会演化出不同的算法。比如,插入排序是不能以自身作为标准作业的,因为如果这样设计,它就不叫“插入排序”了,很可能变成了合并排序。

以上介绍了重用套路在合并排序算法中的体现。

另一个经典的套路,是分治。

分治,就是分而治之,你应该经常听到这样的说法:“没有什么是搞不定的!只要你把它分解得足够小,就能解决!”,换一个说法就是:没有什么是退一步不能解决的,如果有,那就退两步。

分是第一步,分出来解决后,还要把结果组合起来。

合并排序的分治套路,表现很明显:把数列分为两部分,重用自己令这两部分有序后,再组合起来。

而且,合并排序的分治,是很简单的分治,从中间分开再处理就可以了。当分到只有一个元素时,就不能再分,此时这一个元素是有序的。

至此,合并排序的两个重要的套路(重用与分治)就差不多介绍完毕了。

为了让读者更清晰地感受这两个套路,小程接下来从具体的排序实例来详细介绍。

合并排序,先是要分(一分为二),分到只有一个元素为止(一个元素时就是有序的)。然后是合,先是两个元素合在一起,之后是多个元素合在一起。

参考以下这个演示图,可以更好地理解合排的设计与实现:

上面这个图,注意不同颜色框的变化(分与合的变化)。

再参考另一个演示图(来自网络):

以上是算法套路,接下是代码实现,这两者是两个话题,之前已经解释过。

#include <stdio.h>
#include <stdlib.h>

void merge(int* arr, int f, int m, int l, int* tmparr) {
    int i=f, j=m+1;
    int k=0;
    while (i<=m && j<=l) {
        if (arr[i] > arr[j]) {
            tmparr[k++]=arr[j++];
        }
        else {
            tmparr[k++]=arr[i++];
        }
    }
    while (i<=m) {
        tmparr[k++]=arr[i++];
    }
    while (j<=l) {
        tmparr[k++]=arr[j++];
    }
    for (i=0; i<k; i++) {
        arr[f++]=tmparr[i];
    }
}

void _sort_merge(int* arr, int f, int l, int* tmparr) {
    if (f < l) {
        int m=(f+l)>>1;
        _sort_merge(arr, f, m, tmparr);
        _sort_merge(arr, m+1, l, tmparr);
        merge(arr, f, m, l, tmparr);
    }
}

void sort_merge(int* arr, int size) {
    int* tmparr=(int*)malloc(sizeof(int) * size);
    _sort_merge(arr, 0, size-1, tmparr);
    free(tmparr);
}

int main(int argc, char *argv[])
{
    int arr[] = {4, 2, 5, 1, 6, 6, 8, 9, 8, 3};
    int size=sizeof arr/sizeof *arr;
    for (int i = 0; i < size; i ++) {
        printf("%d, ", arr[i]);
    }
    sort_merge(arr, size);
    printf("\nafter_sort:\n");
    for (int i = 0; i < size; i ++) {
        printf("%d, ", arr[i]);
    }
    printf("\n");
    return 0;
}

另外,在分治的套路中,可以混搭增量有序的套路,也就是,合并排序在划分到数量较小时,可以使用插入排序来完成排序(因为在数量较小时,插入排序更快一些),这种套路混搭,打出来的就是组合拳



以下这部分,介绍了合排与快排的对比,你可以忽略掉,因为时间复杂度或速度方面,跟套路并无直接关系。

小白:只要分成两部分,再递归调用自己来排好序,再合并在一起即可。小程,你之前讲的递归又发挥作用了!

小程:不要把递归实现跟设计思想混在一起。设计上,是一直分下去,再合起来。但实现时,不一定要用递归,比如可以把分出来的部分,用数组保存起来,再对这个数组内的部分作细分...,用迭代也能实现。只不过,递归是很自然的实现选择,而且简洁。但是,你要有“空手套白狼”的勇气才敢于用上递归实现。

小白:就是先假设我的函数已经实现排序了,再在函数里面调用自己,对某部分作排序了。东风吹战鼓擂,这个世界谁怕谁?

合并排序的分(O(lgn))与合(O(n)),整体的时间复杂度是O(nlgn),而且是稳定排序。

小白:那岂不是比快排还要快,因为快排有可能变为n的平方?

小程:不是。快排有可能变成n的平方,这种极端的情况是低概率的,而且,可以先打乱再来快排,从而避免去到O(n^2),或者去到极低概率。在都为O(nlogn)时,快排的系数比合排更小,所以速度更快。另外,合排需要额外的空间来保存合并的结果,而快排不需要。

小白:这个...,看来quicksort是实至名归!

在工程中,快排的效果比合排更优,但注意高层次的设计思想,也就是套路,是一样的。



总结一下,本文介绍了合并排序的套路,即重用与分治,这两种思想都是经典的套路,重用能简化问题的思考,而分治能把问题变小,能熟练掌握这两种套路就具备了很厉害的功力。



原文地址:https://www.cnblogs.com/freeself/p/10812375.html

时间: 2024-10-28 11:03:06

算法4:合并排序的套路 | 重用 | 分治的相关文章

第二章 算法入门 合并排序

在第二章中难的算法不多,接下来我会把稍微复杂一点的算法整理一下 #include <iostream> using namespace std; void mergeSort(int *A,int left,int mid,int right) { int *L=new int[mid-left+1]; int *R=new int[right-mid+1]; int i,j; for(i=0;i<mid-left+1;i++) { L[i]=A[left+i]; } for (j=0;

Java学习笔记——排序算法之进阶排序(堆排序与分治并归排序)

春蚕到死丝方尽,蜡炬成灰泪始干 --无题 这里介绍两个比较难的算法: 1.堆排序 2.分治并归排序 先说堆. 这里请大家先自行了解完全二叉树的数据结构. 堆是完全二叉树.大顶堆是在堆中,任意双亲值都大于(或等于)其孩子值,就称其为大顶堆. 堆排序的步骤: 1.把数组想象成一个堆.数组的index+1就是其对应在堆中的序号 2.调堆中各值的顺序,得到大顶堆 3.将堆首位值与堆末尾值交换,最大值排序完毕 4.将堆得大小减1,重复步骤2和步骤3,直到堆中只剩下一个元素.排序完毕 上代码: 1 publ

算法之合并排序(mergeSort)

合并排序算法在结构上是递归的,采用分治策略:就是将原有的问题划分为 n 个规模较小但结构与原问题相似的子问题,递归地解决这些子问题,然后合并其结果,就得到原问题的解. 合并排序的模式一般如下: 1.分解:将 n 个元素分解为各含 n/2 个元素的两个序列: 2.解决:用分治排序法对两个子序列递归地排序: 3.合并:合并两个已排好序的子序列得到排序结果. 在对子序列递归的过程中,当子序列元素数为1时,递归结束. 合并排序算法的时间复杂度为O(nlgn) 1 void merge(int* a, i

算法之合并排序

上篇文章讲到插入排序算法,是一个标准的增量方法:在排好的子数组后,将元素插入,形成新的数组.今天要介绍的是一种新方法:分治法. 分治法,将原问题划分成n个规模较小而结构与原问题相似的子问题:递归地解决这些子问题,然后再合并其结果,就能得到原问题的解.在每一层递归上都会有三个步骤: 分解:将原问题分解成一系列子问题: 解决:递归地解决各子问题,若子问题足够小,则直接求解: 合并:将子问题的结果合并成原问题的解. 合并排序算法完全依照了上述模式,直观的操作如下: 分解:将n个元素分成各含n/2个元素

【算法】合并排序

#include <STDIO.H> #include <STDLIB.H> void mergePass(int *ar,int *pr,int s,int size); void merge(int *ar,int *pr,int l,int m,int r); void mergeSort(int *ar,int size) // 合并排序 { int *pr=(int *)malloc(sizeof(int)*size); //动态开辟要排序数组大小的一个新数组 int s

算法实验:分治法合并排序(C++)

这篇文章分两部分来写,第一部分写代码的实现过程,第二部分把实验报告从头到尾呈现出来. 我习惯调试使用的编译器是DEV C++,不是vs系列的,可能头文件上有点区别.但是下面的报告是我放到vs里面测试过的,可以直接用,不影响. 第一部分:(解析) 题目:随机产生一个整型数组,然后用合并排序将该数组做升序排列,要求输出排序前和排序后的数组. 题目分析: 需要随机产生一个整数数组: 采用的算法是合并排序,也就是用归并排序: 输出排序后的数组. 随机产生一个整数数组:这个问题首先想到的是用rand()函

分治——合并排序

分治策略中有一个经典的算法就是合并排序,这个算法的精髓也是分治二字,分而治之.将一个大规模的问题分割成若干个同样的小问题,小问题的规模很小,很容易解决,解决了小的问题后再对这些小问题的结果进行合并得到大规模问题的解答. 合并排序便是分治策略中比较经典的算法,首先是合并,两个排列有序的数列经过合并后成为有序的数组:代码如下: void _merge(int *A,int left,int middle,int right) { int i=left,j=middle+1,k=0; int *C;

分治法-合并排序和快速排序

分治法是按照以下方案工作的: 将问题的实例划分为同一个问题的几个较小的实例,最好拥有同样的规模 对这些较小的实例求解(一般使用递归方法,但在问题规模足够小的时候,有时会利用另一种算法以提高效率) 如果必要的话,合并较小问题的解,以得到原始问题的解 分治法的流程: 4.1 合并排序 合并排序是成功应用分治技术的一个完美例子(书上说的). 对于一个需要排序的数组,合并排序把它一分为二,并对每个子数组递归排序,然后把这两个排好序的子数组合并为一个有序数组. 代码实现: /** * 合并排序 * @au

算法导论三剑客之 分治算法 合并排序

1 #include "iostream" 2 #include "windows.h" 3 #define MAX 0x7fffffff 4 using namespace std; 5 6 void merge(int s,int q,int e,int A[]){ 7 int i,j,k; 8 int B[100],C[100]; 9 for(i=s;i<=q;i++) 10 B[i-s+1]=A[i]; 11 for(j=q+1;j<=e;j++