常用算法-分治法

Posted 编程杂货档

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常用算法-分治法相关的知识,希望对你有一定的参考价值。

最近准备就业的时候,发现很多公司的面试和笔试都有考算法,于是就打算写一系列的算法文章,就当巩固一下。

    那就从最简单的分治法说起吧!



分治法的基本思想就是将规模较大较复杂的问题分解成规模较小较简单的子问题,通过合并子问题的解来解决原问题。



    分治,即分而治之,是指将一个大问题分解成一个个小问题,将小问题解决后合并起来得到原问题的解。


    这和古时候皇帝管理自己的领土的方法类似,皇帝不可能直接管理自己的每一寸领土(规模太大太复杂,真一个一个管可能没两天就会累死...),于是将领土分封给诸侯,诸侯怎么管理自己的土地诸侯自己想办法,皇帝只管管理诸侯。如果诸侯也觉得土地太大管理不过来,也会将自己的领土分封给下一级,让下一级管理,直到问题在管理者能力范围之内为止。


分治法有一个天造地设的搭档:递归。(

    递归近乎完美地阐述了分而治之的过程,可以说使用递归实现的就是分治。但分治法不一定需要通过递归实现。



以二分查找法为例:

二分查找也叫折半查找,是一种在有序数组中快速定位特定值的的方法。

二分查找的基本思想就是分治:将位置分为三个部分,在我左边的、在我右边的、在我中间的。

在我左边哪?我不知道,但另一个知道,我只要告诉知道的那个人,然后等他告诉我结果就好了。


递归实现代码如下:

#include<iostream>using namespace std;int a[] = {1,2,4,5,6,8,9,12,16,20};int binSearch(int l,int r,int n){ if(l >= r) return -1; //实在找不到了 int mid = (l+r)/2; if(a[mid] == n){ //刚好在手上,直接返回结果 return mid; }  else if(a[mid] < n){ //在右边,然后就不管了,让右边的去处理,我只要处理的结果    return binSearch(mid+1,r,n);//返回右边处理的结果 } else{ //在左边,让左边的去处理 return binSearch(l,mid-1,n);//返回左边处理d额结果 } return -1;}
int main(){ int n = 9;//需要查找的数 int l = 0;//最左边d额位置 int r = sizeof(a)/sizeof(int);// int pos; pos = binSearch(l,r,n); cout<<pos<<endl; return 0;}

前面说过分治法不一定需要通过递归实现,可以通过循环或其他方式实现,比如将二分查找写成下面这样:

#include<iostream>using namespace std;int a[] = {1,2,4,5,6,8,9,12,16,20};int main(){ int n = 9; int left = 0; int right = sizeof(a)/sizeof(int); int pos = -1; while(left < right){ int mid = left+(right-left)/2;//推荐这样写,这样与原来的等价而且可以防止溢出 if(a[mid] == n){ pos = mid; break; } if(a[mid] < n){ left = mid+1; }else{ right = mid-1; } } cout<<pos<<endl; return 0;}

其实这种写法只是本质上还是递归,只是人为优化了一下代码,效率上并不会有多大的提高。


分治法在实际中应用的非常广,比如dfs(深度优先遍历,以后专门写一篇介绍)、快速排序、斐波那契数、汉诺塔......



下面主要说汉诺塔。


汉诺塔是递归中最经典的题目,能理解汉诺塔问题基本上就能理解递归思想,也就能理解分治思想了。

    汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。(来源百度)

常用算法-分治法

如果使用分治思想应该怎么解决呢?如果使用分治思想的话我们只需要三步:

第1步:将n-1个盘子放在中转站。

常用算法-分治法



第2步:将最大的那个盘子放在终点站。


第3步:将放在中转站的n-1个盘子放到终点站。


(是不是和如何把大象放进冰箱的问题差不多?)


这3步有两个比较难理解的地方,其实也就一个:如何移动n-1个盘子?

移动n-1个盘子很简单,也只要三步:

第1步:将n-2个盘子放在中转站。

第2步:将最大的那个盘子放在终点站。

第3步:将放在中转站的n-2个盘子放到终点站。


这和如何年入百万的问题可不一样,这个是有终点的,当你重复这个动作到只剩下最后一个的时候,就可以只做一步了:将那一个盘子放进终点站。


在放盘子的过程中有一个需要注意的地方:只有终点站是不变的,起始站和中转站是不停的在变的。

比如在如何移动n个盘子时,A是起始站,B是中转站,C是终点站。因为是需要从A把所有的盘子移动到C。

而在移动n-1个盘子时,就是把B的盘子移动到C了,此时起始站时B,中转站是A(谁叫他空着呢),终点站是C。

这个的话只需要在移动n-1个盘子的时候把原先的起始站设置为中转站(原先的起始站都移空了,他不做中转站谁做?),原先的中转站设置为起点站就可以了(把移动盘子的任务都交给原先的中转站了,他不当下一步的起始站谁当?)。




那n-2的时候谁是中转站谁是起始站呢?管那么多干嘛,这些是n-1的时候管的,现在我只管n时候的就行。

(事实上在到n-1的时候也是把n-1的起始站作为中转站,中转站作为起始站,这样一直n-2,n-3...都是相同的处理方法,所以不用重新定义函数,用原先的那个函数就行,这就叫递归。

又不可能让他无限的进行下去,当盘子数只有一个的时候就可以处理了,这就是递归的出口。)


以上是关于常用算法-分治法的主要内容,如果未能解决你的问题,请参考以下文章

六中常用算法设计:穷举法分治法动态规划贪心法回溯法和分支限界法

「五大常用算法」一文搞懂分治算法

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

五大常用算法一文搞懂分治算法

五大常用算法策略

五大常用算法策略