最优子结构性质:
当一个问题大的最优解包含着它的子问题的最优解时,称该问题具有最优子结构性质。
由于(b[1],b[2],……b[n])是这段路程加油次数最少的一个满足贪心选择性质的最优解,则易知若在第一个加油站加油时,b[1]=1,则(b[2],b[3],……b[n])是从a[2]到a[n]这段路程上加油次数最少且这段路程上的加油站个数为(a[2],a[3],……a[n])的最优解,即每次汽车中剩下的油不能在行驶到下一个加油站时我们才在这个加油站加一次油,每个过程从加油开始行驶到再次加油满足贪心且每一次加油后相当于与起点具有相同的条件,每个过程都是相同且独立,也就是说加油次数最少具有最优子结构性质。
贪心算法时间复杂度分析
由于若想知道该在哪个加油站加油就必须遍历所有的加油站,且不需要重复遍历,所以时间复杂度为O(n)。
2.回溯算法解决方案
回溯算法的基本思想
确定了解空间的组织结构后,回溯法就是从开始节点(根结点)出发,以深度优先的方式搜索整个解空间。
这个开始节点就成为一个活结点,同时也成为当前的扩展结点。
在当前的扩展结点处,搜索向纵深方向移至一个新结点。
这个新结点就成为一个新的或节点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向移动,则当前的扩展结点就成为死结点。
换句话说,这个节点,这个结点不再是一个活结点。
此时,应往回(回溯)移动至最近一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法即以这种工作方式递归的在解空间中搜索,直到找到所要求的解或解空间中以无活结点为止。
回溯算法的解题基本步骤
(1)首先要为问题定义一个解空间,这个节点包含问题的解(可能是最优解)。
在汽车加油问题中,我们可以定义一个包含从始点到终点的所有加油站的解的空间。
(2)组织解空间以便被顺利的搜索,同时搜索方法又能够避免移动到不可能产生解的子空间。
(3)定义了解空间的组织方法,这个空间即可按深度优先地方法从开始节点开始搜索。
在搜索过程中适用剪枝去掉不是最优的解。
[问题分析]
在该问题中,解空间用一棵树来表示,最优解,即数的深度值最小的情况。
开始起点为a[0],开始节点既是一个活节点又是一个扩展节点,从扩展节点可以通到一个新节点,如果能从当前的扩展节点移动到一个新节点,如果这个节点在该节点的上一层没有出现,那么这个新节点就将成为一个活节点和新的扩展节点,继续向前行驶,如果这个节点在上一层出现过,那么这个节点就是一个死结点,需要结束此过程,回到它的上一个节点继续寻找直到终点为止。
或者我们可以这样想这个问题,我们想要加油的次数最少,因为路程一定,要使加油次数最少,即在整个过程中每次加油时汽车里剩下的油之和最少。
问题分析的两种思路都可以用回溯算法来解决。
加油站回溯算法设计(C#):
usingSystem;
Namespacejiayouzhan
{
/**////
///Class1的摘要说明。
///
ClassClass1
{
/**////
///应用程序的主入口点。
///
///
staticdoublesum=7;//加油站总数
staticdoublecsum=0;//当前加油总数
staticdoublebestsum=6;//最优解
staticdoubles=100;//总路程
staticdoublecs=0;//当前走过的路程
staticdoublen=30;//满油能走的路程
staticdoublecn=30;//当前能走的路程
staticdouble[]a={0,20,10,10,10,20,10,20};//加油站分布
staticdouble[]b={0,0,0,0,0,0,0,0};
staticdoublep=0;//记录临时CN
Privatestaticvoidjiayou(inti)
{
if(i>sum&&(cn>=s-cs))
{
if(bestsum>csum)
{
bestsum=csum;
Console.WriteLine("{0}",bestsum);
for(intj=1;j<7;j++)
Console.Write("{0}",b[j]);
Console.WriteLine();
}
}
elseif(i<=sum&&(cn>=a[i+1]))
{
p=cn;
cn=n;
cs+=a[i];
csum+=1;b[i]=1;
jiayou(i++);
cn=p-a[i+1];
csum-=1;b[i]=0;
jiayou(i++);
}
}
staticvoidMain(string[]args)
{
jiayou
(1);
Console.Read();
}
}
}
回溯算法的正确性证明
回溯法具有“通用的解题法”之称它的解空间包括了所有的可行解与不可行解我们通过剪枝比较最终得到的最优解,所以使用回溯算法一定是可以得到最优解,即回溯算法解该题具有正确性。
回溯算法时间复杂度分析
由于回溯算法得到所有的解,最坏的情况在每个加油站都加油即n次,其次加油次数为n-1次,n-2次,n-3次……1次,0次。
又由于加油地点不同根据不同的组合,我们分析得出时间复杂度为2的n次方。
3.动态规划算法解决方案
动态规划基本思想
动态规划算法通常用于求解具有某种最优性质的问题。
在这类问题中,可能会有许多可行解。
每一个解都对应于一个值,我们希望找到具有最优值(最大值或最小值)的那个解。
设计一个动态规划算法,通常可按以下几个步骤进行:
(1)找出最优解的性质,并刻画其结构特征。
(2)递归地定义最优值。
(3)以自底向上的方式计算出最优值。
(4)根据计算最优值时得到的信息,构造一个最优解。
【问题分析】:
因为每次加油时剩余油量可知,在最少加油次数情况下,剩余油量的总和Add(b[],m,0)与到达终点时剩余油量D之和是一定的且无论是哪种最少加油次数情况Add(b[],m,0)+D是相同的。
因为
最后一次加油时总剩余油量为Add(b[],m,0);
倒数第二次加油时总剩余油量为Add(b[],m,0)-b[m-1];
……
第一次加油时总剩余油量为Add(b[],m,0)-Add(b[],m,1)=b[0];
根据每次加满油行驶的距离最远可知最少加油次数m。
动态规划算法程序(C)
#include
#include
intAdd(inta[],intn,intk)
{
intsb=0;
for(inti=k;ireturnsb;
}
intdongtai(inta[],intn,intN)
{
intj=0;
int*b=newint[m];//数组b[i]表示第i次加油时剩余油量,总加油次数为m
intD//到达终点时剩余油量
if(Add(a[],n,0)-Add(a[],k,0)N)
{
b[++j]=N-Add(a[],n,k);//每次加油时剩余油量
Add(b[],m,0)-=b[j];//倒数第j次加油后剩余的总油量
n=k;
if(Add(b[],m,0)==b[1])return(m-1)=j;
}
}
voidmain()
{
inta[];
intN;
scanf("%d,",a);
scanf("/n");
scanf("%d",&N);
dongtai(a[],n,N);
}
(四)贪心算法、动态规划与回溯算法比较
首先通过以上分析及证明,我们知道两种方法都能解决使汽车加油次数最少的问题。
从证明算法的正确性上回溯算法要更简单,但从时间复杂度上分析贪心算法要更优于回溯算法在计算机上更容易实现,动态规划介于两者之间,并不是本题最优的选择方案。
五、实验心得:
在贪心算法中,每次做出的选择仅在当前的状态下做出的最好的选择,即局部最优选择。
然后再去解做出这个选择后产生的相应的子问题。
不是每个问题用贪心算法都可以一定得到最优解,除非该问题具有贪心选择性质(所求问题的整体最优解可以通过一系列局部最优的选择而得到)和最优子结构性质。
在回溯算法中,我们学会了从多角度分析一个问题,通过解空间深度遍历来解决问题,得到最优解。
在回溯算法我们想到可以通过使每次加油前汽车内剩下的油量之和最小的思路,我们又想到了动态规划算法,动态规划法也可以解决最优解问题,所以我们又分析得出了动态规划的算法程序。
通过实验对贪心算法和回溯算法以及动态规划算法有了更深一步的理解,知道了它们适合解决哪类的问题,锻炼和提高了我们分析问题解决问题的能力,同时让我们更体会到团结一致协作的重要性。