4.1 思想
有时候我们处理一个复杂的问题,可能此问题求解步骤非常杂,也可能是数据非常多,导致我们当时很难求出或者无法求出,古语有云:
步步为营,各个击破,这个思想在算法中称为分治思想,就是我们可以将该问题分解成若干个子问题,然后我们逐一解决子问题,最后将子问题
的答案组合成整个问题的答案。
4.2 条件
当然各个思想都有它的使用领域,所以玩这场分治游戏就要遵守它的游戏规则。
① 求解问题确实能够分解成若干个规模较小的子问题,并且这些子问题最后能够实现接近或者是O(1)时间求解。
② 各个子问题之间不能有依赖关系,并且这些子问题确实能够通过组合得到整个问题的解。
4.3 步骤
通过上面对分治的说明,可以看到分治分三步走:
① 分解: 将问题分解成若干了小问题。
② 求解: O(1)的时间解决该子问题。
③ 合并: 子问题逐一合并构成整个问题的解。
4.4 示例
[找出伪币]
给你一个装有1 6个硬币的袋子。1 6个硬币中有一个是伪造的,并且那个伪造的硬币比真的硬币要轻一些。你的任务是找出这个伪造的硬币。为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,利用这台仪器,可以知道两组硬币的重量是否相同。
方法1:
比较硬币1与硬币2的重量。假如硬币1比硬币2轻,则硬币1是伪造的;假如硬币2比硬币1轻,则硬币2是伪造的。这样就完成了任务。假如两硬币重量相等,则比较硬币3和
硬币4。同样,假如有一个硬币轻一些,则寻找伪币的任务完成。假如两硬币重量相等,则继续比较硬币5和硬币6。按照这种方式,可以最多通过8次比较来判断伪币的存在并找出
这一伪币。
方法2:
利用分而治之方法,假如把10硬币的例子看成一个大的问题。
第一步,把这一问题分成两个小问题。随机选择5个硬币作为第一组称为A组,剩下的5个硬币作为第二组称为B组。这样,就把10个硬币的问题分成两个5硬币的问题来解决。
第二步,判断A和B组中是否有伪币。可以利用仪器来比较A组硬币和B组硬币的重量。假如两组硬币重量相等,则可以判断伪币不存在。假如两组硬币重量不相等,则存在伪币,并且可以判断它位于较轻的那一组硬币中。
最后,在第三步中,用第二步的结果得出原先1 0个硬币问题的答案。
若仅仅判断硬币是否存在,则第三步非常简单。
无论A组还是B组中有伪币,都可以推断这1 0个硬币中存在伪币。因此,仅仅通过一次重量的比较,就可以判断伪币是否存在。
4.5 实例代码:
#include <iostream> #define MAXNUM 30 int FalseCoin(int coin[],int low,int high) { int i,sum1,sum2,sum3; int re; sum1 = sum2 = sum3 = 0; if (low + 1 == high) { if (coin[low] < coin[high]) { re = low + 1; return re; } else { re = high + 1; return re; } } if ((high - low +1)%2 == 0) { for (i = low; i <= low + (high - low)/2; i++) { sum1 = sum1 + coin[i]; } for (i = low + (high - low)/2 + 1; i <= high; i++) { sum2 = sum2 + coin[i]; } if (sum1 > sum2) { re = FalseCoin(coin, low + (high- low)/2 + 1, high); return re; } else if(sum1 < sum2) { re = FalseCoin(coin, low, low + (high- low)/2 ); return re; } else { } } else { for (i = low; i <= low + (high - low)/2 -1; i++) { sum1 = sum1 + coin[i]; } for (i = low + (high - low)/2 + 1; i <= high; i++) { sum2 = sum2 + coin[i]; } sum3 = coin[low + (high - low)/2]; if (sum1 > sum2) { re = FalseCoin(coin, low + (high- low)/2 + 1, high); return re; } else if(sum1 < sum2) { re = FalseCoin(coin, low, low + (high- low)/2 - 1); return re; } else { } if (sum1 + sum3 == sum2 + sum3) { re = low + (high - low)/2 + 1; return re; } } } int main(int argc, const char * argv[]) { int coin[MAXNUM]; int i,n; int weizhi; std::cout<<"分治算法求解假银币问题:"; std::cout<<"请输入银币总的个数:"; scanf("%d",&n); for (i =0; i<n; i++) { scanf("%d",&coin[i]); } weizhi = FalseCoin(coin, 0, n-1); printf("在上述%d个银币中,第%d个银币是假的!\n",n,weizhi); // std::cout << "Hello, World!\n"; return 0; }
5.1 思想:
这里主要讲一下“数值概率算法”,该算法常用于解决数值计算问题,并且往往只能求得问题的近似解,同一个问题同样的概率算法
求解两次可能得到的结果大不一样,不过没关系,这种“近似解”会随时间的增加而越接近问题的解。
5.2 特征
现实生活中,有很多问题我们其实都得不到正确答案,只能得到近似解,比如“抛硬币”求出正面向上的概率,”抛骰子“出现1点的
概率,再如:求“无理数π”的值,计算"“定积分”等等。针对这样如上的情况,使用概率算法求解是再好不过的了。
5.3 举例
《蒙特卡洛π算法》
蒙特卡洛π算法计算圆周率的原理一般称为"投点法"或"撒豆子法",就是作一个正方形及其内切圆,向正方形所在的区域内随机投点,那么根据古典概型的特征可知:落在圆内的点数与落在正方形内的点数之比在概率上等于圆与正方形的面积之比。
假设内切圆的半径为R,投在正方形内的点共有N个,如图下,其中落在内切圆内的点有M个,则有如下关系式成立:
(πR²)/(4R²)=M/N 化简可得:π=4M/N
其中,N是人为给定的变量,是已知的。因此,算法的任务就是数出落在圆内的点的个数。
5.4 代码:
double MontePI(int n) { double PI; double x,y; int i,sum; sum = 0; srand(time(NULL)); for (i = 1; i<n; i++) { x = (double)rand()/RAND_MAX; y = (double)rand()/RAND_MAX; if ((x*x + y*y) <= 1) { sum++; } } PI = 4.0*sum/n; return PI; } int main(int argc, const char * argv[]) { int n; double PI; std::cout<<"蒙特卡罗概率算法计算\n"<<"输入点的数量"; scanf("%d",&n); PI = MontePI(n); printf("PI=%f\n",PI); // std::cout << "Hello, World!\n"; return 0; }运行结果如图:
参考书籍:《C/C++常用算法手册》