分治算法,顾名思义就是“分而治之”,即把规模较大的复杂问题拆分为若干规模较小的类似子问题,并逐个解决,最后再将各个子问题的解决结果合并,得到原始问题的结果的方法。这个技巧是很多高效算法的基础,例如快速排序算法、归并排序算法、快速傅里叶变换等等。

五大常用算法之分治算法

分治算法的通俗理解

一般来说,规模小的问题比规模大的问题解决起来简单,例如数组排序问题,只有 2 个元素的数组处理起来要比有 2000 个元素的数组简单。这是分治算法的基本立足点,也是使用条件之一,总结一下就是,对于一个规模较大的问题,可以拆分成若干不相关的子问题,并且子问题解决起来更加简单。

分治算法在我们日常生活中无处不在,例如国家依次划分为省市县镇村来管理,本质上也是因为解决“子问题”(管理村)要简单许多。

有这样一个非常经典的问题:生产线生产了 99 个工件,其中 1 个工件是劣品,比其他 98 个工件质量轻一些,如果用天平称,至少多少次一定能找到这个劣品工件?

要解决这个问题,最简单粗暴的方法是逐个比较,如果第 x 个工件比第 y 个工件轻,第 x 个工件就是劣品。那么 99 个工件至少需要比较 50 次才能确保一定找到劣品。

逐个比较

能够发现,使用逐个比较的方法的时间开销与工件总数有关,如果工件总数很少,比如只有 2 个,那么只需要比较一次就可以找到劣品了。因此该问题满足使用“分治算法”的必要条件之一:规模较小的子问题解决起来更简单。现在尝试使用分治算法解决该问题:

  1. 将 99 个工件等分为 3 份,每份 33 个工件;
  2. 比较第 1、2 份,如果天平平衡,那么劣品必定在第 3 份中,否则劣品在轻的那一份中;
  3. 将劣品所在的那一份再等分为 3 份,每份 11 个工件;
  4. 重复第 2 步;
  5. 将劣品所在那一份再分为 3 份,每份分别为 3、3、2 个工件;
  6. 重复第 2 步;
  7. 不管劣品所在那一份为 3 个工件还是 2 个工件,只需再比较一次,就可找到劣品。

可见,使用分治算法只需要比较 4 次就可以找到劣品,这远低于逐个比较法的 50 次。不过也应该注意到,使用分治法找劣品时,每次拆分子问题时,各个子问题是互不干扰的——例如其中一份的 33 个工件质量不会影响其他份的 33 个工件质量。

归并排序法

从前面这个简单的实例可以看出,分治法有时的确能够极大的提升解决问题的效率,事实上,在C语言程序开发中,许多优秀的算法本质上都是基于分治思想的,一个非常典型的例子就是归并排序法,它在处理数组排序时有着不错的效率。

在处理数组排序时,如果数组中只有 1 个元素,那么该数组本身就可看作是排好序的数组。如果数组中有 2 个元素,那么最多只需比较一次就可以得到排好序的数组。这其实是归并排序法的核心,也即规模越小的数组,对其排序越简单。下图是一个长度为 5 的数组,为了排序,先把它拆分到不能继续拆为止。

拆分到不能继续拆

显然,“拆分到不能继续拆”后,子问题已经变成了只有 1 个元素的数组,这样的数组已经是“排好序”的数组了。按照我们前面讨论的,现在需要做的就是把这些已经排好序的子数组合并,问题转化为:按照顺序合并有序数组,如下图所示:

按照顺序合并有序数组

归并排序的C语言实现

根据前面的分析,使用C语言实现归并排序法需要实现两个子模块:拆分和合并。拆分数组有多种方法,通常采用二等分法,设数组头的索引为 start,数组尾的索引为 end,mid=(start+end)/2,每次平均拆分,都会将数组分为 start 到 mid,和 mid+1 到 end 两个子数组,再对这两个子数组执行同样的拆分,一直到不能拆分为止。所谓“不能拆分”,其实就是数组中只有一个元素,也即 start 等于 end,这一过程非常适合使用递归实现。我们先确定递归的停止条件:

void divide(int *arr, int start, int end){    if (start >= end)        return;}

否则,我们将继续拆分子数组(start, mid)和(mid+1, end),这一过程的C语言代码如下:

void divide(int *arr, int start, int end){    if (start >= end)    return;    int mid = (start+end)/2;    divide(arr, start, mid);    divide(arr, mid+1, end);}

关于 divide() 递归函数的理解,我之前的这篇文章详细讨论过。

搞定拆分模块后,再来看看合并模块。按照前面讨论的,拆分到“不能拆为止”的都可认为是已经排好序的子数组,所以合并模块要按照顺序(本例从小到大)将子数组合并:

int merge(int *arr, int start, int mid, int end){    int ln = mid-start +1;    int rn = end - mid;    int left[ln], right[rn];        for (int i=0; i

C语言代码1

我们先将要合并的拆分后的两个子数组分别保存在 left 和 right 数组里,应注意,这里使用了C语言的变长数组语法,因此在编译时要指定-std=c99选项。接着,我们逐个比较 left 和 right 中的元素,按照顺序填入 arr,这一过程的C语言代码如下所示:

int merge(int *arr, int start, int mid, int end){...    int i=0, j=0, k=0;    for (k = start; i < ln && j < rn; ++k) {        if (left[i] < right[j]) {            arr[k] = left[i];            ++i;        } else {            arr[k] = right[j];            ++j;        }    }

C语言代码2

执行完毕后,left 和 right 中可能还有剩余元素,这些剩余元素必定是需要放在 arr 后部分的,因此C语言代码可以如下写:

int merge(int *arr, int start, int mid, int end){...    if (i < ln)        for (; i < ln; i++) {            arr[k] = left[i];            ++k;        }    if (j < rn)        for (; j < rn; ++j) {            arr[k] = right[j];            ++k;        }}

C语言代码3

到这里,merge() 函数就完成了,将之与 divide() 函数组合起来,也即:

void divide(int *arr, int start, int end){    if (start >= end)    return;    int mid = (start+end)/2;    divide(arr, start, mid);    divide(arr, mid+1, end);    merge(arr, start, mid, end);}

C语言代码4

现在 divide() 函数便可对输入的数组 arr 排序了。

测试归并排序法

这里使用 8 个元素的数组做测试:

int main(){    int arr[8] = {1, 3, 2, 5, 8, 7, 6, 4};    divide(arr, 0, 7);    for (int i=0; i<8; i++)        printf("%d ", arr[i]);    printf("");    return 0;}

C语言代码5

编译并执行这段C语言代码,得到如下输出:

# gcc t.c -std=c99# ./a.out 1 2 3 4 5 6 7 8

归并排序算法的时间复杂度

在计算过程中,累加和比较的过程是关键操作,一个长度为 n 的数组在递归的每一层都会进行 n 次操作,分治法的递归层级在 logN 级别,所以整体的时间复杂度是 O(nlogn)。

归并排序法是一个效率不错的排序算法,可能时间复杂度看起来不是特别直观,我们将之与简单粗暴的“冒泡排序算法”对比,在我的机器上分别对不同规模的数组排序,得到如下结果:

排序效率对比

冒泡排序算法是比较简单的算法,在处理规模较小的数组时和归并排序法的效率不相上下,但是在处理规模较大的数组时,冒泡排序算法的效率逐渐远离归并排序了,且数组的规模越大,二者的效率差异越大,在处理 100 万个数组元素时,归并排序算法仅消耗 230 毫秒就处理完毕了,而冒泡排序算法在执行 2 分钟后仍然还没有完成,我没有耐心等下去,提前结束它了。

aes算法实现c语言_以C语言实现归并排序为例,谈谈五大常用算法之一的“分治法”...相关推荐

  1. mysql区间算法_「五大常用算法」一文图解分治算法和思想

    前言 分治算法(divide and conquer)是五大常用算法(分治算法.动态规划算法.贪心算法.回溯法.分治界限法)之一,很多人在平时学习中可能只是知道分治算法,但是可能并没有系统的学习分治算 ...

  2. (转)五大常用算法:分治、动态规划、贪心、回溯和分支界定

    分治算法 一.基本概念 在计算机科学中,分治法是一种很重要的算法.字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题-- ...

  3. 算法设计之五大常用算法设计方法总结

    算法设计之五大常用算法设计方法总结 一.[分治法] 在计算机科学中,分治法是一种很重要的算法.字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再 ...

  4. 「五大常用算法」一文图解分治算法和思想

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招! 个人原创100W+访问量博客:点击前往,查看更多 前言 分 ...

  5. 五大常用算法:分治、动态规划、贪心、回溯和分支界定

    算法系列之十六:使用穷举法解猜结果游戏--http://blog.csdn.net/orbit/article/details/7607685 ---------------例子: 麻将PC上发送操作 ...

  6. 五大常用算法一(回溯,随机化,动态规划)

    五大常用算法一(回溯,随机化,动态规划) 回溯算法 回溯法: 也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并以此慢慢地扩大问题规模,迭代地逼近最终问题 ...

  7. 五大常用算法之一——分治算法

    分治算法(Divide and Conquer) 分治算法,简单理解就是把一个大规模的问题分成相同或者相似的子问题,直到最后子问题能很容易的求解,原问题的解通过子问题的解合并得到. 根据这个描述,我们 ...

  8. 计算机 五大算法类型,计算机算法设计五大常用算法的分析及实例.docx

    标准化管理处编码[BBX968T-XBB8968-NNJ668-MM9N] 标准化管理处编码[BBX968T-XBB8968-NNJ668-MM9N] 计算机算法设计五大常用算法的分析及实例 摘要 算 ...

  9. java沙漏_(java)五大常用算法

    算法一:分治法 基本概念1.把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题--直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并.2.分治策略是对于一个规模 ...

最新文章

  1. matlab contour光滑,使用Matplotlib在Contour Plot中平滑数据
  2. 5.QML动画——分组动画
  3. 项目托管至GitHub上
  4. 页面适配的小栗子 - github
  5. java分数怎么保存到文件_Java如何将控制台上的结果保存到文件
  6. 【数据结构与算法】之深入解析“字符串转换整数 (atoi)”的求解思路和算法示例
  7. cmd mysql log_如何使用mysqlbinlog工具?
  8. mongodb地理空间索引原理阅读摘要
  9. PCM(脉冲编码调制)、iLBC编解码、opus(声音编码格式)、VP8视频压缩格式、H.264数字视频压缩格式
  10. electron-vue使用electron-updater实现自动更新
  11. 10亿级流数据交互查询,为什么抛弃MySQL选择VoltDB?
  12. paip. C#.NET循环获取不同随机数的方法根据时间
  13. 交换机的基本交换配置
  14. 发字的楷书写法图片_硬笔书法笔顺正确写法,手写示范动态图
  15. 【Unity3D】使用 FBX 格式的外部模型 ② ( FBX 模型与默认 3D 模型的区别 | FBX 模型贴图查找路径 | FBX 模型可设置多个材质 )
  16. 100部经典中国电影,你看过几部?
  17. win10修改DVD驱动器盘符(磁盘管理无DVD驱动器)
  18. keep-alive的作用是什么? 使用它的目的是什么?
  19. P1395 会议 题解
  20. java 编辑PDF 文件,或者填充数据

热门文章

  1. DDD理论学习系列(2)-- 领域
  2. 用 docker secrets 保存 appsettings.Production.json
  3. Visual Studio 2017 针对移动开发的新特性介绍
  4. 浅析如何在Nancy中生成API文档
  5. ASP.NET Core MVC 配置全局路由前缀
  6. 提交Form表单,submit之前做js判断处理
  7. C# 的扩展方法在 LINQ 中实现数组排序
  8. linux网络编程之地址结构sockaddr和sockaddr_in的区别
  9. linux之telnet命令使用
  10. Andriod之import android.support.v4或者import android.support.v7提示导入错误解决办法