在实际应用中,不同问题的解题思想也往往不同。如果找不到一个合适的思路,那么求解过程可能就变得复杂,甚至无法求解得到结果。选择合理的思想,可以帮助用户理清问题的头绪,更快地解决问题。算法就是起到了这个作用。
常见的算法
穷举算法(Exhaustive Attackmethod)是最简单的一种算法,其依赖于计算机的强大计算能力来穷尽每一种可能性,从而达到求解问题的目的。穷举算法效率不高,但是适应于一些没有规律可循的场合。
1.2 思想
穷举算法基本思想穷举算法的基本思想就是从所有可能的情况中搜索正确的答案,其执行步骤如下:
(1)对于一种可能的情况,计算其结果。
(2)判断结果是否符合要求,如果不满足则执行第(1)步来搜索下一个可能的情况;如果符合要求,则表示寻找到一个正确答案。
在使用穷举法时,需要明确问题的答案的范围,这样才可以在指定的范围内搜索答案。指定范围之后,就可以使用循环语句和条件语句逐步验证候选答案的正确性,从而得到需要的正确答案。
1.3 示例
穷举算法举例鸡兔同笼问题最早记载于1500年前的《孙子兵法》,这是一个非常有名的问题。鸡兔同笼的原文如下:
今有鸡兔同笼,上有三十五头,下有九十四足,问鸡兔各几只?
这个问题的大致意思是:在一个笼子里关着若干只鸡和若干只兔,从上面数共有35个头,从下面数共有94只脚。问笼中鸡和兔的数量各是多少?
1.4 实现代码:
#include <iostream> using namespace std; int qiongju(int head,int foot ,int *chicken,int *rabbit) { int re,i,j; re = 0; for (i=0; i<=head; i++) { j = head -i; if (i * 2 + j * 4 == foot) { re = 1; *chicken = i; *rabbit = j; } } return re; } //鸡兔同笼(穷举算法) int main(int argc, const char * argv[]) { int chicken,rabbit,head,foot; int re; std::cout<<"穷举法求解鸡兔同笼问题\n"; std::cout<<"输入头数:"; scanf("%d",&head); std::cout<<"输入脚数:"; scanf("%d",&foot); re = qiongju(head,foot,&chicken,&rabbit); if (re == 1) { printf("鸡有:%d只,兔子有:%d只。\n",chicken,rabbit); } else { printf("无法求解!\n"); } // std::cout << "Hello, World!\n"; return 0; }
2.1 概念
通过已知条件,利用特定关系逐步递推,最终得到结果为止,核心就是不断的利用现有信息推导出新的东西。
2.2 分类
当然递推中有两种,“顺推”和“逆推“
顺推:从条件推出结果。
逆推:从结果推出条件。
呵呵,是不是觉的有一种policeman破案的感觉。
2.3 举例
<1> 顺推的例子
上过大学的应该都知道著名的“斐波那契”数列吧,说的是繁殖兔子的问题,题目我就大概说一下。
如果1对兔子每月能生1对小兔子,而每对小兔在它出生后的第3个月就可以生1对小兔子,如果从1对初生的小兔子开始,1年后能
繁殖多少兔子?
思路:其实这个问题我们可以将兔子划分为“1月大的兔子“,”2月大的兔子“,”3月大的兔子“。
① 初始时: 一对1月大小兔子,总数为1对。
② 第一个月: 1月大的小兔子变成2月大的兔子,总数还是1对。
③ 第二个月: 2月大的小兔子变成3月大的兔子,繁殖了一对小兔子,总数为2对。
④ 第三个月: 3月大的兔子tmd有生了一对小兔子,上个月1月大的小兔子变成了2月大的兔子,总数为3对。
...... ......
F0=1
F1=1
F2=F0+F1
F3=F1+F2
......
Fn=Fn-2+Fn-1
#include <iostream> using namespace std; int Fibonacci(int n) { int t1,t2; if (n==1 || n==2) { return 1; } else { t1 = Fibonacci(n-1); t2 = Fibonacci(n-2); return t1+t2; } } //兔子产仔问题 int main(int argc, const char * argv[]) { int n,num; std::cout<< "递推算法求解兔子产仔问题!\n"; std::cout<<"请先输入时间:"; scanf("%d",&n); num = Fibonacci(n); printf("经过%d月的时间,共能繁殖成%d对兔子!\n",n,num); // std::cout << "Hello, World!\n"; return 0; }执行结果如图:
3.1 概念
递归,说白了就是直接或者间接的调用自己的一种算法。它是把求解问题转化为规模较小的子问题,然后通过多次递归一直到可以得出结果
的最小解,然后通过最小解逐层向上返回调用,最终得到整个问题的解。总之递归可以概括为一句话就是:“能进则进,不进则退”。
3.2 三要素
<1> 递归中每次循环都必须使问题规模有所缩小。
<2> 递归操作的每两步都是有紧密的联系,如在“递归”的“归操作时”,前一次的输出就是后一次的输入。
<3> 当子问题的规模足够小时,必须能够直接求出该规模问题的解,其实也就是必须要有结束递归的条件。
3.3 注意
<1> 前面也说了,递归必须要有一个递归出口。
<2> 深层次的递归会涉及到频繁进栈出栈和分配内存空间,所以运行效率比较低,当问题规模较大时,不推荐使用。
<3> 在递归过程中,每次调用中的参数,方法返回点,局部变量都是存放在堆栈中的,如果当问题规模非常大时,容易造成堆栈溢出。
3.4 举二个例子
<1> 相信大家在初中的时候都学过阶乘吧,比如:5!=5*4*3*2*1
思路:根据上面的阶乘特征很容易我们就可以推导出n!=n*(n-1)*(n-2)....*2*1,
那么进一步其实就是: n!=n*(n-1)! ,
(n-1)!=(n-1)*(n-2)!。
显然他是满足递归的三要素,当n的规模不大时,我们可以用递归拿下。
long fact(int n) { if (n <= 1) { return 1; } else { return n*fact(n-1); } } //计算n的阶乘 int main(int argc, const char * argv[]) { int i; std::cout<<"请输入要求阶乘的一个整数:"; scanf("%d",&i); printf("%d的阶乘结果为:%ld\n",i,fact(i)); // std::cout << "Hello, World!\n"; return 0; }
本篇为了避免太长,再续篇。