分治法与归并排序

分治策略

  解决一个给定问题,算法需要一次或多次地递归调用自身来解决相关的子问题,这种算法通常采用分治策略。分治模式在每一层递归上都有三个步骤:

  〉〉分解:将原问题分解成一系列子问题

  〉〉解决:递归地求解各子问题。若子问题足够小,则直接求解

  〉〉合并:将子问题的结果合并成原问题的解。

归并排序(合并排序)

  归并排序的关键在于归并两个相邻的子序列,使其变成一个排序好的新序列。如果这个新序列就是原来需要进行排序的数组,那么排序完成。所以,我们需要将原序列递归地分成若干子序列,直道最小的子序列只有一个元素,然后将子序列依次归并,就可以得到排序好的原序列。我们要解决的第一个问题就是:假设有子序列A[p...q]和子序列[q + 1...r]是已经排序好的子序列,如何按顺序将它们归并到一个子序列中去呢?

归并两个子序列

  我们可以用扑克牌做模拟。将两堆数量相近的扑克牌按顺序叠好,最小的牌放在最上面,牌面朝上。

  〉〉第一步:拿出两张中较小的一张,放在桌上。

  〉〉第二步:分别比较所拿的牌和两个堆上面的牌,重复第一步,跟在前一张牌的后面。直到一个堆拿完。

  〉〉第三步:将剩余的堆从上往下一次放在桌上,跟在前一张牌的后面。

  由此可见,按问题要求(加橙色的),我们可以设计如下代码:

void merge(int ar[], int p, int q, int r, int temp[]){
        //将ar[p...q]和ar[q+1...r]合并到temp[p...r],temp由外部分配内存
    int i = p, j = q + 1, k = 0;
    while(i <= q && j <= r){
        if(ar[i] < ar[j])
            temp[k++] = ar[i++];
        else
            temp[k++] = ar[j++];
    }
    while(i <= q) //如果ar[p..q]有剩
        temp[k++] = ar[i++];
    while(j <= r) //如果ar[q+1..r]有剩
        temp[k++] = ar[j++];

    for(k = 0; k <= (r - p); k++) //将合并后的子序列赋值给原序列ar[p...r]
        ar[p + k] = temp[k];
}

  对于归并排序,我们要解决的第二个问题就是:原序列如何成为有序序列?

利用分治策略使原序列有序

  我们可以通过将原序列二分为两个子序列,再将两个子序列二分为另外的四个子序列,直到不能再分解为止。然后分别合并相邻的两个子序列,直到不能合并为止。这里引用网络上的一张图修改后来说明这个原理。

  前面提到过,解决一个给定问题,算法需要一次或多次地递归调用自身来解决相关的子问题,这种算法通常采用分治策略。所以,我们可以利用分治策略通过通过递归调用一个函数来解决。我们可以这样写代码:

void mergesort(int ar[], int head, int end, int temp[]){
    if(head < end){ //条件:直到不能分割为止
        int middle = (head + end) / 2; //找到二分原序列的点
        mergesort(ar, head, middle, temp); //左子序列排序
        mergesort(ar, middle + 1, end, temp); //右子序列排序
        merge(ar, head, middle, end, temp); //合并这两个子序列
    }
}

  分析上面的代码,我们可以将上图标上执行顺序(建议在新窗口打开下图),来验证该算法的正确性。同时,你也可以通过类似“循环不变式”的方法证明:

完整代码

  到这里,归并排序的所有问题都解决完了。我们给出完整的不带注释(分配内存后的空注释是标志,提醒一定要及时释放内存)的代码。

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <conio.h>
 4 #include <time.h>
 5
 6 void mergesort(int [], int, int, int []);
 7 void merge(int [], int, int, int, int[]);
 8
 9 int main(int argc, char * argv[]){
10     int * ar, * temp;
11     int n = 10, i;
12
13     if( !(ar = (int *)malloc( (size_t)n * sizeof(int) ) ) )//
14         exit(EXIT_FAILURE);
15     if( !(temp = (int *)malloc( (size_t)n * sizeof(int) ) ) )//
16         exit(EXIT_FAILURE);
17
18     srand( (unsigned int)time(NULL) );
19     for(i = 0; i < n; i++){
20         ar[i] = rand() % 1000;
21         printf("%-4d",ar[i]);
22     }
23
24     mergesort(ar, 0, n - 1, temp);
25
26     printf("\n归并排序后:\n");
27     for(i = 0; i < n; i++)
28         printf("%-4d",ar[i]);
29
30     free(temp);
31     free(ar);
32
33     _getch();
34     return 0;
35 }
36
37 void mergesort(int ar[], int head, int end, int temp[]){
38     if(head < end){
39         int middle = (head + end) / 2;
40         mergesort(ar, head, middle, temp);
41         mergesort(ar, middle + 1, end, temp);
42         merge(ar, head, middle, end, temp);
43     }
44 }
45
46 void merge(int ar[], int p, int q, int r, int temp[]){
47     int i = p, j = q + 1, k = 0;
48     while(i <= q && j <= r){
49         if(ar[i] < ar[j])
50             temp[k++] = ar[i++];
51         else
52             temp[k++] = ar[j++];
53     }
54     while(i <= q)
55         temp[k++] = ar[i++];
56     while(j <= r)
57         temp[k++] = ar[j++];
58
59     for(k = 0; k <= (r - p); k++)
60         ar[p + k] = temp[k];
61 }

MergeSort

时间: 2024-10-13 23:55:16

分治法与归并排序的相关文章

算法设计《分治法》归并排序(三)实例分析之逆序对数

问题定义: 假设A[1...n]是一个有n个不同数的数组.若i<j且A[i]>A[j]则称(A[i], A[j])为数组A的一个逆序对. 例如数组<2, 3, 8, 6, 1>有(2, 1),(3, 1),(8, 6),(8, 1)和(6,1)5个逆序对. 对于这个问题,直观上进行求解的话,使用暴力求解的方式的话,对于每个数num,都遍历数组中num后的所有数的话,则时间复杂度为O(n^2). 实现代码如下: 另一种方式,便是使用分治法,首先将整个数组分成两部分,然后,分别求解两个

算法导论第2章 分治法与归并排序, 二分查找法

分治策略:将原问题划分成n个规模较小而结构与原问题相似的子问题,然后递归地解决这些子问题,最后再合并其结果,就可以得到原问题的解. 它需要三个步骤: 分解:将原问题分解成一系列的子问题. 解决:递归地解决各个子问题.若子问题足够小,则直接求解. 合并:将子问题的结果合并成原问题的解. 通过分治策略和分治步骤,可以简单地默出归并算法. 分解:将n个元素分成各自包含n/2个元素的子序列 解决:用归并排序法递归地对两个子序列进行排序. 合并:合并两个以排序的子序列,得到排序结果: void merge

算法笔记_065:分治法求逆序对(Java)

目录 1 问题描述 2 解决方案 2.1 蛮力法 2.2 分治法(归并排序)   1 问题描述 给定一个随机数数组,求取这个数组中的逆序对总个数.要求时间效率尽可能高. 那么,何为逆序对? 引用自百度百科: 设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同. 如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数. 例如,数组(3,1,4,5,

归并排序(分治法)

横向想了一下这几个经典的排序算法,个人感觉快排应该是速度最快了,首先快排在空间复杂度的角度应该开销比归并要小很多,因为归并需要申请新的临时空间,时间复杂度上虽说都是N*log(n).但是同一个数量级上归并有很多的数组复制操作,感觉如果数据很大的话应该比快排所消耗的时间多很多(但是都是在一个数量级上,比如100 和900的区别).本来想等考完试有时间去做一下试验,但是今天发现有博主已经做了,嘻嘻,那我就直接转载过来了.(这个博主安利下,他的文章深入浅出,对新手很友好) 贴试验之前,先整理一下归并排

分治法排序之归并排序

使用分治法的两路合并排序算法: 将待排序的元素序列一分为二,得到长度基本相等的两个子序列,分别排序. 如果子序列较长,还可继续细分,直到子序列的长度不超过1为止. 当分解所得的子序列已排列有序时,将两个有序子序列合并成一个有序子序列,得到原问题的解. 合并方法: 比较两序列中的最小值,输出其中较小者,然后重复此过程,直到其中一个队列为空时, 如果另一个队列还有元素没有输出,则将剩余元素依次输出. #include<stdio.h>#define N 100int merge(int *a, i

分治法 ----归并排序

分治法的思想就是把一个难以解决的大问题分解成很多个小规模的问题--分而治之,说实话我不明白和dp的区别 /* Name: Copyright: Author: 流照君 Date: 2019/9/13 11:03:29 Description: */ #include <iostream> #include<string> #include <algorithm> #include <vector> #define inf 10010 using namesp

专题:分治法

分治法(Divide and Conquer) 作为五大算法之一的分治法,可算是最早接触的一种算法.分治法,与其说是一种算法,不如将其称为策略来的更贴切一些.算法的思想就是将大问题分成小问题,并解决小问题之后合并起来生成大问题的解. 分治法的精髓: 分--将问题分解为规模更小的子问题: 治--将这些规模更小的子问题逐个击破: 合--将已解决的子问题合并,最终得出“母”问题的解: 分治法的作用,自然是让程序更加快速地处理问题.比如一个n的问题分解成两个n/2的问题,并由两个人来完成,效率就会快一些

分治法(一)

这篇文章将讨论: 1) 分治策略的思想和理论 2) 几个分治策略的例子:合并排序,快速排序,折半查找,二叉遍历树及其相关特性. 说明:这几个例子在前面都写过了,这里又拿出来,从算法设计的策略的角度把它们放在一起来比较,看看分治是如何实现滴.由于内容太多,我将再花一篇文章来写4个之前没有写过的分治算法:1,大整数乘法   2,矩阵乘法的分治策略   3,最近点对  4,凸包问题,请见下一篇. 好了,切入正题. --------------------------------------------

分治法

分治法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同.递归的解这些子问题,然后将各子问题的解合并得到原问题的解. 分治法所能解决的问题一般具有以下几个特征: 1) 该问题的规模缩小到一定的程度就可以容易地解决 2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质. 3) 利用该问题分解出的子问题的解可以合并为该问题的解: 4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题. 分治法的基本步骤:分治法在