算法的基本思想
(一)穷举算法思想
从所有可能情况中搜索正确答案
- 对于一种可能情况,计算其结果。
- 判断结果是否满足,如不能满足者执行第一步来搜索下一个可能的情况;如满足则表示选找到一个正确答案。
实例:鸡兔同笼问题
(二)递推算法思想
递推算法是一种理性思维模式的代表,其根据已有的数据和关系,逐步推到而得到结果。对推算法的执行过程:
-
根据已知结果和关系,求解中间结果。
-
判定是否但到要求,若没有继续执行第一步,若有则表示找到一个正确结果
递推算法往往需要知道答案和问题之间的实际逻辑关系。再许多数学问题中往往都有着明确的计算公式可以遵循,因此可以采用递推算法来实现。实例:斐波那契数列:兔子产仔问题
(三)递归算法思想
递归算法即在程序中不反复调用自身达到解决问题的方法,是一个方法在其方法体内调用自身方法调用方式。在递归中主方法又是被调方法。执行递归将反复调用其自身。每调用一层就进入新的一层。
递归调用分为两种情况:
- 直接递归,即在方法中调用方法本身
- 间接递归,即间接地调用一个方法
编写递归方法时,必须使用if语句强制在未执行递归前返回。
实例:阶乘问题
(四)分治算法思想
分治算法就是把一个复杂问题分为规模较小的,计算简单的小问题,然后综合小问题得到最后答案的思想。分治算法执行过程如下:
-
对于一个规模为N的问题,若给问题比较容易解决,则直接解决;否则执行下面的步骤。
-
将该问题分解为M个规模较小的问题,这些问题相互独立,并且与原问题相互独立。
-
递归这些小问题
-
然后,将各个小问题合并得到完问题的解
实例:一个袋子里有三十个硬币,其中有一枚假币,并且假币和真币一某一样,肉眼很难分辨,目前只知道假币比真币轻一点,请问如何区分假币?
算法分析
-
首先为每个硬币编号,然后然后将所有硬币等分为两份,放在天平两边。
-
再将较轻的那一份等分为两份重复上述方法
-
直到剩下两个硬币,较轻的一个就是假币
算法实现
public int FalseCoin(int coin[],int low,int high)
{
int i,sum1,sum2,sum3;
int re = 0;
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) //n是偶数
{
for(i=low;i<=low+(high-low)/2;i++)
{
sum1= sum1+coin[i];
}
for(i=low+low+(high-low)/2;i<=high;i++)
{
sum2= sum2+coin[i];
}
if(sum1>sum2)
{
re=FalseCoin(coin,low+(high-low)/2,high);
return re;
}
else if(sum1<sum2)
{
re=FalseCoin(coin,low,low+(high-low)/2);
return re;
}
}
else
{
for(i=low;i<=low+(high-low)/2-1;i++)
{
sum1= sum1+coin[i];
}
for(i=low+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
{
re=low+(high-low)/2+1;
return re;
}
}
return re;
}
(五)概率算法思想
概率算法依照概率统计的思路来求解问题,往往不能得到问题的精确解,但却在数值计算领域得到了广泛的应用。因为很多数学问题,往往没有或者很难计算解析解,这时便需要通过数值计算来求解近似值。
概率算法执行的基本过程如下:
(1)将问题转化为相应的几何图形S, S 的面积是容易计算的,问题的结果往往对应几何图形中某一部分S1 的面积。
(2)然后,向几何图形中随机撒点。
(3)统计几何图形S 和 S1 中的点数。根 据 S 的面积和S1 面积的关系以及各图形中的点数来计算得到结果。
(4) 判断上述结果是否在需要的精度之内,如果未达到精度则执行步骤(2)。如果达到精度,则输出近似结果。
概率算法大致分为如下4 种形式。
• 数值概率算法。
• 蒙 特 卡 罗 (MonteCarlo)算法。
• 拉 斯 维 加 斯 (Las Vegas)算法。
• 舍 伍 德 (Sherwood)算法
实例【蒙特卡罗PI概率算法问题】
在边长为1的正方形内,以1为半径画一个1/4圆。落入院内的概率为PI/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;//在0-1之间产生一个随机数x
y=(double)rand()/RAND_MAX;//在0-1之间产生一个随机数y
if((x*x+y*y)<=1){//判断点是否在圆内
sum++;//计数
}
}
PI=4.0*sum/n;//计算PI
return PI;
}
int main()
{
int n=500000;
double PI;
printf("蒙特卡罗概率PI=%f\n", MontePI(n));
return 0;
}
(六)回溯算法思想
概念
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
基本思想
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。
若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
用回溯法解题的一般步骤:
(1)针对所给问题,确定问题的解空间:
首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。
(2)确定结点的扩展搜索规则
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
(七)动态规划算法思想
动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
实现步骤
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。
(八)分支限界算法思想
基本描述
类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
- 分支搜索算法
所谓“分支”就是采用广度优先的策略,依次搜索E-结点的所有分支,也就是所有相邻结点,抛弃不满足约束条件的结点,其余结点加入活结点表。然后从表中选择一个结点作为下一个E-结点,继续搜索。
选择下一个E-结点的方式不同,则会有几种不同的分支搜索方式。
1)FIFO搜索
2)LIFO搜索
3)优先队列式搜索
- 分支限界搜索算法
分支限界法的一般过程
由于求解目标不同,导致分支限界法与回溯法在解空间树T上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T。
分支限界法的搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展对点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。在搜索问题的解空间树时,分支限界法与回溯法对当前扩展结点所使用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被子加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所求的解或活结点表为空时为止。
- 回溯法和分支限界法的一些区别
回溯法和分支限界法的一些区别:
方法对解空间树的搜索方式 存储结点的常用数据结构 结点存储特性常用应用
回溯法深度优先搜索堆栈活结点的所有可行子结点被遍历后才被从栈中弹出找出满足约束条件的所有解
分支限界法广度优先或最小消耗优先搜索队列、优先队列每个结点只有一次成为活结点的机会找出满足约束条件的一个解或特定意义下的最优解
(九)贪心算法
基本概念:
所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。
贪心算法的基本思路:
1.建立数学模型来描述问题。
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解。
4.把子问题的解局部最优解合成原来解问题的一个解。
贪心算法适用的问题
贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。
贪心算法的实现框架
从问题的某一初始解出发;
while (能朝给定总目标前进一步)
{
利用可行的决策,求出可行解的一个解元素;
}
由所有解元素组合成问题的一个可行解;
贪心策略的选择
因为用贪心算法只能通过解局部最优解的策略来达到全局最优解,因此,一定要注意判断问题是否适合采用贪心算法策略,找到的解是否一定是问题的最优解。
例题分析
下面是一个可以试用贪心算法解的题目,贪心解的确不错,可惜不是最优解。
[背包问题]有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
物品 A B C D E F G
重量 35 30 60 50 40 10 25
价值 10 40 30 50 35 40 30
分析:
目标函数: ∑pi最大
约束条件是装入的物品总重量不超过背包容量:∑wi<=M( M=150)
(1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?
(2)每次挑选所占重量最小的物品装入是否能得到最优解?
(3)每次选取单位重量价值最大的物品,成为解本题的策略。
值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。
贪心算法还是很常见的算法之一,这是由于它简单易行,构造贪心策略不是很困难。
可惜的是,它需要证明后才能真正运用到题目的算法中。
一般来说,贪心算法的证明围绕着:整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。
对于例题中的3种贪心策略,都是无法成立(无法被证明)的,解释如下:
(1)贪心策略:选取价值最大者。反例:
W=30
物品:A B C
重量:28 12 12
价值:30 20 20
根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。
(2)贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。
(3)贪心策略:选取单位重量价值最大的物品。反例:
W=30
物品:A B C
重量:28 20 10
价值:28 20 10
根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。