其实算法设计:回溯法解决01背包问题的问题并不复杂,但是又很多的朋友都不太了解,因此呢,今天小编就来为大家分享算法设计:回溯法解决01背包问题的一些知识,希望可以帮助到大家,下面我们一起来看看这个问题的分析吧!
2. 算法要素
1. 解决方案空间
使用回溯法解决实际问题时,首先确定解的形式,定义问题解空间。
(1) 解以n元组{x1, x2, x3…. xn}的形式组织
(2)解的取值范围
例如0-1背包问题:
0-1背包问题可以抽象为一个容量有限的背包(容量设置为W)。每种商品都有自己的体积w和价值v。目的是用总价值最大的物品装满这个容量有限的背包。事物
例如,有3 个项目,解的组织形式为{x1, x2, x3}。其解分量xi的取值范围很简单,xi=0或xi=1。 xi=0表示第i件物品没有放入背包,xi=1表示第i件物品放入背包,所以
xi{0,1}
包含3 件物品的0-1 背包问题具有所有可能的解:{0,0,0}、{0,0,1}、{0,1,0}、{0,1,1}、{1 ,0 ,0}, {1,0,1}, {1,1,0}, {1,1,1}
解空间:由所有可能的解组成的空间,我们需要根据问题的约束条件在解空间中寻找最优解,如下图所示:
解空间越小,搜索效率越高。解空间越大,搜索效率越低。
(3) 搜索解空间
隐式约束是指对问题能否获得可行或最优解的约束。
如果不满足隐式约束,则意味着无法获得问题的可行或最优解,无需沿着该节点的分支进行搜索,相当于删除该分支。因此,隐式约束也称为剪枝函数。本质不是去掉分支,而是不再寻找分支。
例如,在3件物品的0-1背包问题中,如果放入前2件物品后背包变得超重(x1=1,x2=1),那么就不需要考虑是否应该放入第三件物品。放入背包中。修剪后如下图所示:
隐式约束(剪枝函数)包括约束函数和边界函数
解空间的大小和剪枝函数的好坏直接影响搜索效率,因此这两项是搜索算法的关键。在空间搜索的时候,有几个术语需要解释一下:
(1) 扩展节点:正在增长子节点的节点
(2) 活节点:自身已生成的节点,但其所有子节点尚未生成。
(3) 死节点:所有子节点都已生成的节点
(4)后代:节点E的子树中的所有节点都是E的后代。
(5) 祖先:从节点E到树根的路径上的所有节点都是E的祖先
2、解决问题步骤:
(1)定义解空间
由于解空间的大小对搜索效率影响很大,因此采用回溯法首先定义一个合适的解空间,并确定解空间包括解的组织形式和显式约束。
解的组织形式:解的组织形式标准化为n元组{x1,x2,…….xn},但具体表达含义不同。
显式约束:限制解值的范围。显式约束可以控制解空间的大小。
(2)确定解空间的组织形式
解空间的组织结构通常用解空间树的形象来表示。根据解空间的不同,解空间分为子集树、排列树和m叉树。
(3) 搜索解空间
回溯法遵循深度优先搜索策略,基于约束(约束函数和边界函数)在解空间中搜索问题的可行或最优解。当发现当前节点不满足求解条件时,则回溯并尝试其他路径。
如果问题只需要可行解,则只需设置约束函数即可。如果需要最优解,则需要设置约束函数和极限函数。
解的组织形式是一般的n元组形式,解的组织结构是解空间的形象表达。解空间和隐式约束是控制搜索效率的关键。显式约束控制解空间
3. 算法设计过程
以01背包问题为例:假设有n件物品,每件物品i对应的值为vi,重量为wi,购物车容量为W,每件物品只有1件。它要么已加载,要么未加载,并且不能拆分。如何装载总价值最大的物品?
2、设计流程
(1)定义问题解空间
每个项目有且仅有2 个状态,已加载或未加载。我们使用变量xi 来表示第i 件商品是否被加载到购物车中的行为。如果用’0’表示没有装入背包,用’1’表示装入背包,那么xi的值为0或1,i=1,2,3 ,4,5….第i件商品装入购物车xi=1; xi=0 未加载到购物车中。该问题的解形式是一个n元组,每个分量的值为0或1
由此,问题解空间为{x1, x2, x3,xi,xn},其中显式约束xi=0或1,i=1,2,3.n
(2)确定解空间的组织结构
问题的解空间描述了2的n次方可能的解,即n个元素组成的集合的所有子集的数量。例如,对于3 件商品的购物车问题,解空间为:{0,0,0}, {0,0,1}, {0,1,0}, {0,1,1}, { 1,0, 0}、{1,0,1}、{1,1,0}、{1,1,1}。这个问题有2^3 种可能的解决方案
(3) 搜索解空间
【1】限制条件
解空间包含2 的n 次方可能的解。存在某些或某些物品无法装载的情况。因此,需要设置约束来判断装载物品的总重量是否超过总容量。如果是这样,那么这是一个不可行的解决方案;否则,这是一个可行的解决方案。搜索过程不再搜索导致不可行解决方案的节点及其子节点。限制条件是:
w1x1 + w2x2 + w3x3+….=W
【2】边界条件
可行的解决方案可能不止一种。问题的目标是找到装入购物车的商品总价值最大的可行解,即最优解。因此需要设置边界条件来加快最优解的速度
根据解空间的组织结构,对于任意中间节点z(中间状态),从根节点到z节点的分支所代表的状态已经确定,从z到其后代节点的分支所代表的状态是不确定的。换句话说,如果解空间树中z的层数为t,则表示第1项到第t-1项的状态已经确定。我们只需要沿着z的分支延伸就可以轻松确定第t项的状态。然后确定前t项的状态。但从t+1到n的项目状态仍然不确定。这样,确定前t件物品的状态后,当前装入购物车的物品的总价值用cp表示。已装载物品的较高价值不一定是最佳的,因为仍然存在未确定的物品。
我们还不确定第t+1 到n 项的实际状态,因此只能使用估计。假设第t+1件商品到第n件商品已全部装入购物车。第t+1项至第n项的总值用rp表示。因此,cp + rp 是从根开始并经过中间节点z 的所有项的总和。可行解的取值上界如图所示:
如果值上界小于等于当前搜索到的最优值(最优值用bestp表示,初始值为0),则表示无法从中间节点z继续搜索到后代节点以获得比当前解决方案更好的可行解决方案。没有必要继续寻找。否则,继续搜索z的后代节点。极限条件为:
cp + rp 最佳
3. 搜索流程
从根节点开始,按深度优先进行搜索。根节点首先成为活节点,同时也是当前的扩展节点。由于子集树的左分支的值约定为“1”,因此沿着扩展节点的左分支延伸表示加载项。这时需要判断该item是否可以加载,即约束条件是否成立。如果成立,则生成左子节点,左子节点成为活动节点和当前扩展节点,并继续扩展至深度节点;如果未建立,则左子节点成为活动节点和当前扩展节点。然后剪掉扩展节点的左分支并沿其右分支扩展。右边的分支代表该商品没有被加载到购物车中,这肯定会导致一个可行的解决方案。但沿着右边的分支延伸是否可以得到最优解呢?这需要通过边界条件来判断。如果满足边界条件,则意味着可能会导致最优解,即生成右子节点,右子节点成为活节点和当前扩展节点,继续向深度扩展节点;如果不满足边界条件,则切断扩展节点。右分支追溯到最近的祖先活节点。直到所有活节点变成死节点,搜索过程结束。
4. 算法案例
(1)、案例1
假设有4个物品,每个物品的权重w为(2,5,4,2),v的值为(6,3,5,4),只能放入的总容量W袋子是10。 问题应该将哪些物品装入袋子中才能获得最大价值?
1. 算法设计流程
(1)、初始化
sumw和sumv分别用于统计所有物品的总重量和总价值。总和=2 + 5 + 4 +2=13,
sumv=6 + 3 + 5 +4=18,所以sumv W不能满载,需要搜索求解。初始化当前放入购物车的商品重量cw=0;当前放入购物车的商品价值cp=0;当前最优值bestp=0
(2) 开始搜索第1层(t=1)
要扩展1号节点,首先确定cw + w[1]=2 W,满足约束条件,扩展左分支。
设x[1]=1,cw=cw + w[1]=2,cp=cp + v[1]=6,生成2号节点,如图:
(3) 展开2号节点(t=2)
首先判断cw + w[2]=7 W,满足约束条件,展开分支,令x[2]=1,
cw=cw + w[2]=7,cp=cp + v[2]=9,生成3号节点,如图:
(4) 展开3号节点(t=3)
首先判断cw + w[3]=11 W,超出购物车容量,无法放入第三件商品。然后判断bound(t + 1)是否大于bestp。 bound(4)中剩余的项只是第4项,rp=4,
cp + rp=13,bestp=0,因此满足边界条件,右子树展开。令x[3]=0,生成4号节点,如图:
(5) 展开节点4(t=4)
首先判断cw + w[4]=9 W,满足约束条件,展开左分支,令x[4]=1,令
cw=cw + w[4]=9,cp=cp + v[4]=13,生成节点号5
(6) 展开5号节点(t=5)
t n ,找到一个当前最优解,用bestx[]保存当前最优解{1,1,0,1},保存当前最优解bestp=cp=13,5号节点成为死节点
(7)回溯到4号节点(t=4),一路回到2号节点
回溯到4号节点,回溯时,cw=cw w[4]=7,cp=cp v[4]=9,怎么加,怎么减回来?节点4的右子树还没有生成。检查bound(t+1)是否大于bestp。 bound(5) 中没有剩余项目。 rp=0,cp + rp=9,bestp=13,因此不满足边界条件。 4号右子树节点无法扩展。节点4 成为死节点
向上回溯到节点3。节点3 的左子节点和右子节点已被检查并且是死节点。
向上回溯到2号节点。回溯时,cw=cw-w[2]=2,cp=cp-v[2]=6。如何加减
如下图:
(8) 展开节点2(t=2)
节点2的右子树还没有生成。检查bound(t + 1)是否大于bestp。 bound(3) 中剩余的项是第3 项和第4 项,rp=9,cp + rp=15,bestp=13,因此满足边界条件,扩展右子树。令x2=0生成节点6
(9) 展开6号节点(t=3)
首先判断cw + w[3]=6 W,满足约束条件,展开左分支,令x[3]=1,
CW=cw+w[3]=6,cp=cp+v[3]=11,生成7号节点,如图:
(10) 展开节点7(t=4)
首先判断cw + w[4]=8 W,满足约束条件,展开左分支,令x[4]=1,
cw=cw + w[4]=8,cp=cp + v[4]=15,生成8号节点,如图:
(11) 展开节点8(t=5)
t n (n=4),求当前最优解,用bestx[]保存当前最优解{1,0,1,1},保存当前最优解值bestp=cp=15,8号节点变为死节点
回溯到7号节点。回溯时,cw=cw-w[4]=6,cp=cp-v[4]=11。如何加减?
(12) 展开节点7(t=4)
节点7的右子树还没有生成。检查bound(t + 1)是否大于bestp。 bound(5)中没有剩余乘积,rp=0,cp + rp=11,bestp=15,因此不满足边界条件。不再展开节点7的右子树。节点7成为死节点
向上回溯到6号节点。回溯时,cw=cw w[3]=2。如何加减cp=cp-v[3]=6
(13) 展开6号节点(t=3)
节点6的右子树还没有生成。检查bound(t+1)是否大于bestp。 bound(4)中剩余的项是第4项,rp=4,cp + rp=10,bestp=15,所以不满足。边界条件,不再扩展节点6的右子树
向上回溯到2号节点。2号节点的左右子节点已经被检查过,都是死节点。
回溯到1号节点,回溯时cw=cw-w[1]=0,cp=cp-v[1]=0。如何加减?
(14) 展开1号节点(t=1)
节点1的右子树还没有生成。检查bound(t+1)是否大于bestp。 bound(2)中剩余的项为第2、3、4项,rp=12,cp+rp=12,bestp=15。因此,不满足限制条件,节点1的右子树为no更长的扩展。节点1为死节点,所有节点均为死节点,算法结束。如图所示:
2. 代码实现
(1)伪代码
【1】计算上界的函数:
计算上限是指计算已加载物品的值cp与剩余物品的总值rp之和。我们已经知道已加载项目的cp 值。我们不确定应该装载哪些剩余物品。我们基于假设它们都已加载,即基于最大值(剩余项目的总价值)来估计,因此获得的值是可加载项目值的上限
//计算上限:已加载物品的价值+剩余物品的总价值
双界(int i )
{
int rp=0;//剩下的项是第i项
while( i=n )//计算序列中剩余项的值
{
rp +=v[i];
我++;
}
return cp + rp ;//返回上界
}
【2】根据约束和极限条件搜索解函数
t表示当前扩展节点在第t层,cw表示当前放置项的权重,cp表示当前放置项的值。
如果t n 表明已经到达叶子节点,记录最优值和最优解并返回。否则判断是否满足约束条件,如果满足,则查找左子树。因为左子树代表放置物品,所以令x[t]=1,表示放置第t个物品。 cw +=w[t],表示当前放置的物品重量增加w[t]。 cp +=v[t],表示当前放置的物品的值增加v[t]。 backTrack(t+1)表示递归,深度优先搜索第t+1层。返回时,即向上追踪时,必须减去增加的值,cw-=w[t],cp-=v[t]
判断是否满足限制条件,如果满足则搜索右子树。因为右子树表示没有放置该项,所以令x[t]=0。当前放置的物品的重量和价值不会改变。 backTrack(t+1)表示递归,深度优先搜索第t+1层
//表示当前扩展节点在第t层
无效backTrack(int t)
{
if(t n)//已到达叶子节点
{
for(j=1; j=n; j++)
{
最佳x[j]=x[j];
}
//保存当前最优解
最佳p=cp;
返回;
}
//如果满足约束,则搜索左子树
if(cw + w[t]=W)
{
x[t]=1;
CW +=w[t];
cp +=v[t];
回溯(t+1);
CW -=w[t];
cp -=v[t];
}
//如果满足限制条件,则搜索右子树
if(bound(t + 1) bestp)
{
x[t]=0;
回溯(t+1);
}
}
完整的程序代码如下:
#包括iostream
#包含字符串
#包含算法
#定义M 105
使用命名空间std;
//n代表n件商品,W代表购物车的容量
int i , j , n , W;
//w[i]代表第i个物品的重量,v[i]代表第i个物品的价值
双w[M],v[M];
//表示第i件商品是否放入购物车
布尔x[M];
//当前体重
双连续波;
//当前值
双CP;
//当前最优值
双最佳;
//当前最优解
布尔最佳x[M];
//计算上限,即剩余物品的总价值
双界(int i )
{
//剩下的项是i–n项
整数rp=0;
//按照重量和价值递减的顺序加载物品(以物品为单位)。
而(i=n)
{
rp +=v[i];
我++;
}
返回cp + rp;
}
//用于搜索空间编号,t表示当前扩展节点在第t层
无效backTrack( int t )
{
if(t n)//已到达叶子节点
{
for(j=1;j=n;j++)
{
//保存当前最优解
最佳x[j]=x[j];
}
bestp=cp;//保存当前最佳值
返回;
}
//如果满足约束,则搜索左子树
if(cw + w[t]=W)
{
x[t]=1;
CW +=w[t];
cp +=v[t];
回溯(t + 1);
CW -=w[t];
cp -=v[t];
}
//如果满足约束,则搜索右子树
if( 界(t + 1) bestp)
{
x[t]=0;
回溯(t + 1);
}
}
无效演示(双W,int n)
{
//将当前放入购物车的商品重量初始化为0
CW=0;
//将当前放入购物车的商品值初始化为0
cp=0;
//初始化当前最优解
最佳p=0;
//用于统计所有物品的总重量
双总和=0.0;
//用于统计所有物品的总价值
双和=0.0;
对于(i=1;i=n;i++)
{
sumv +=v[i];
sumw +=w[i];
}
如果(总和=W)
{
最佳p=总和;
count ‘放入购物车的商品的最大价值为:’ bestpendl;
cout ‘所有商品均放入购物车’;
返回;
}
回溯(1);
cout ‘放入购物车的商品的最大价值为:’bestpendl;
cout ‘放入购物车的商品编号为:’;
for(i=1;i=n; i++)
{
if(bestx[i]==1)
计算我’ ‘;
}
库滕德尔;
}
int main()
{
cout ‘请输入物品数量n:’;
辛恩;
cout ‘请输入购物车容量W:’;
辛W;
cout ‘请依次输入每件物品的重量w和价值v,以空格分隔’;
for(i=1;i=n;i++)
{
cinw[i]v[i];
}
演示(W,n);
返回0;
}
5.算法时间复杂度和空间复杂度分析
(1)时间复杂度
回溯方法的运行时间取决于它在搜索过程中生成的节点数量。边界函数可以大大减少生成节点的数量,避免无效搜索,加快搜索速度。
左节点需要确定约束函数,右节点需要确定限制函数。那么最坏情况下左节点和右节点分别有多少个呢?让我们看一下大小为n 的子集树。最坏情况的状态如下:
【1】节点总数:
【2】左右子节点数量
总节点数减去根节点,再除以2,得到左右子节点数。左右子节点个数为:
约束函数的时间复杂度为O(1),边界函数的时间复杂度为O(n)。在最坏的情况下有
左子节点调用约束函数
有
在每个右子节点上都会调用有界函数,因此回溯方法的时间复杂度为
(2)空间复杂度
回溯法会产生一个解空间。在任何一次搜索时,只保留从起始节点到当前扩展节点的路径。从起始节点开始的最长路径是n。程序中我们使用bestp[]数组记录最长路径作为最优解,因此算法的空间复杂度为O(n)
6.算法优化
在上面的程序中,上界函数是当前值cp 与剩余项rp 的总值之和。这个估计太高了,因为剩余物品的重量很可能超过购物车的容量。因此,我们可以减小上限,从而加快剪枝速度,提高搜索效率。
上界函数bound():当前值cp + 剩余容量可容纳的剩余物品的最大值brp
为了更好地计算和应用上界函数剪枝,首先将项目按照其单位权重值(值/权重)从大到小进行排序,然后按排序顺序检查每个项目。
#包括iostream
#包含字符串
#包含算法
#定义M 105
使用命名空间std;
//n代表商品数量,W代表购物车容量
int i , j , n , W;
//w[i]代表第i个物品的重量,v[i]代表第i个物品的价值
双w[M],v[M];
//x[i]=1表示第i件商品放入购物车
布尔x[M];
双连续波; //当前体重
double cp;//当前值
double bestp;//当前最优值
double bestx[M];//当前最优解
/**
*计算上限:
*用剩余物品填满剩余背包容量可以获得的最大价值
*/
双界(int i)
{
//剩下的项是第i–n项
双裂=W – CW;//剩余容量
双brp=0.0;
while(i=n w[i] 裂口)
{
裂口-=w[i];
brp +=v[i];
我++;
}
//使用切割的方法来填充背包。这是上限。求解时不允许切割。
如果(我=n)
{
brp+=v[i]/w[i]*裂口;
}
返回cp + brp;
}
//用于搜索空间编号,t表示当前扩展节点在第t层
无效backTrack(int n)
{
if(t n)//已到达叶子节点
{
for(j=1;j=n;j++)
{
最佳x[j]=x[j];
}
bestp=cp;//保存当前解
返回;
}
//如果满足约束,则搜索左子树
if(cw + w[t]=W)
{
x[t]=1;
CW+=W[t];
cp+=v[t];
回溯(t+1);
CW-=w[t];
cp-=v[t];
}
//如果满足约束,则搜索右子树
if(bound(t+1) bestp)
{
x[t]=0;
回溯(t+1);
}
}
//定义物品结构,包括序列号和单位重量值
结构体对象
{
整数ID; //商品序列号
double d;//单位重量值
};
//按照单位重量值从大到小对物品进行排序
bool cmp(对象a1,对象a2)
{
返回a1.da2.d;
}
无效演示(int W,int n)
{
//初始化
//将插入的物品的权重初始化为0
CW=0;
//将放置的item的值初始化为0
cp=0;
//将当前最优值初始化为0
最佳p=0;
//用于统计所有物品的总重量
双和
= 0;
//用来统计所有物品总价值
double sumv=0;
//物品结构体类型,用于按单位重量价值(价值/重量比)排序
Object Q[n];
//辅助数组,用于把排序后的重量和价值传递给原来的重量价值数组
double a[n+1] , b[n+1];
for(i = 1;i <=n ;i++)
{
Q[i-1].id = i;
Q[i-1].d = 1.0*v[i]/w[i];
sumv+=v[i];
sumw+=w[i];
}
if(sumw <= W )
{
bestp = sumv;
cout<<“放入物品最大价值为:”<<bestp<<endl;
cout<<“所有物品均放入”;
return;
}
//按单位重量价值(价值/重量比)从大到小排序
sort(Q ,Q + n,cmp);
for(i=1;i<=n;i++)
{
//把排序后的数据传递给辅助数组
a[i]=w[Q[i-1].id];
b[i]=v[Q[i-1].id];
}
for(i=1;i<=n;i++)
{
//把排序后的数据传递给w[i]
w[i] = a[i];
v[i] = b[i];
}
backTrack(1);
cout<<“放入的物品最大价值为:”<<bestp<<endl;
cout<<“放入购物车的物品序号为”;
for(i =1;i <= n;i++)
{
if(bestx[i]==1)
cout<<Q[i-1].id<<“”;
}
cout<<endl;
}
int main()
{
cout<<“请输入物品的个数n:”;
cin >>n;
cout<<“请输入购物车的容量W:”;
cin >>W;
cout<<“请依次输入每个物品的重量w和价值v,用空格分开”;
for(i=1;i<=n;i++)
{
cin>>w[i]>>v[i];
}
demo(W,n);
return 0;
}
原创文章,作者:小su,如若转载,请注明出处:https://www.sudun.com/ask/98402.html
用户评论
不忘初心
终于找着关于回溯法的讲解了!我一直觉得这玩意蛮玄妙的,看了这个文章还算是稍微有点了解了。就是说,对于01背包,这种方法能够找到所有可行的方案,而不是像贪心算法那样只找出一种解决方案?
有15位网友表示赞同!
羁绊你
算法设计一直是我比较头疼的地方,还好这篇文章讲解得很详细,回溯法原来是这样运作的,感觉还挺优雅的。希望能多看看其他算法的设计思路,比如动态规划、贪心等等。
有20位网友表示赞同!
孤街浪途
01背包问题确实很经典,以前只知道用穷举法解决,效率太低了。这篇博文介绍的回溯法看起来就高级很多,而且通俗易懂,这下可以用来应对更复杂的场景了!
有8位网友表示赞同!
。婞褔vīp
这个回溯法的实现代码也放出来了,我打算试着自己敲一遍,看看效果如何。不过这篇文章只针对01背包问题,其他问题能不能用这种方法解决呢?希望能看到更多应用场景的讲解!
有17位网友表示赞同!
龙吟凤
文章讲得真好理解,终于明白了回溯法的基本原理了!之前一直觉得算法学习太难,现在感觉只要一步步来,慢慢就会掌握。
有17位网友表示赞同!
古巷青灯
还是喜欢看一些代码实现的过程,这样能更加直观地理解算法的运行机制。不过这篇文章的代码实现略显简陋,希望能看到更加完善和针对不同场景的代码示例。
有15位网友表示赞同!
殃樾晨
对于刚开始学习算法的人来说,我觉得这篇博文写的太深入了,很多概念我还没学透就遇到了,希望可以有更基础的文章铺垫一下。
有16位网友表示赞同!
顶个蘑菇闯天下i
回溯法确实是个很巧妙的方法,但它的缺点也很明显,搜索空间容易过大导致时间复杂度很高。对于规模较大的问题,可能需要考虑其他更有效的算法了。
有14位网友表示赞同!
青衫故人
文章中提到的时间复杂度分析太简略了,缺乏具体的解释和计算过程。希望能有更多深入的分析,让读者更加清晰地理解算法的时间复杂度特性。
有7位网友表示赞同!
断桥残雪
回溯法解决01背包问题的原理确实很巧妙,但实际应用场景是不是会比较局限呢?对于更大规模的问题,这个方法的效率可能难以满足要求
有18位网友表示赞同!
把孤独喂饱
我觉得这篇文章还不错的,把回溯法的基本原理说得清清楚楚 不过,对于我这样的菜鸟来说,能不能再详细点讲解一下具体的操作步骤?比如在代码实现中,具体怎么实现“剪枝”操作呢?
有11位网友表示赞同!
微信名字
01背包问题确实很经典,但是很多时候我们只需要找到最优解而不是所有可行方案,回溯法真的必要吗?有没有更直接的方法可以更快地找到最优解?
有20位网友表示赞同!
淡淡の清香
总感觉这篇文章讲得有些枯燥乏味,缺少一些生动的例子和实际应用场景的展示。希望能有更 menarik和易懂的讲解,能给我带来更大的启发。
有8位网友表示赞同!
算了吧
对于算法设计来说,回溯法只是一种工具,不能一味追求它的优美之处而忽视其适用范围和局限性。需要根据具体问题的情况选择适合的算法,才能真正发挥其效用。
有11位网友表示赞同!
挽手余生ら
我觉得这篇博文更适合有一定编程基础的人阅读,对于刚入门算法的小白来说,有些概念可能理解起来困难。希望作者能针对不同读者群体,制作一些更加通俗易懂的讲解内容。
有11位网友表示赞同!
◆残留德花瓣
学习算法真是越学越深,回溯法这个方法原来还有这么多讲究!这篇博文让我受益匪浅,开阔了我的视野,同时也激发了我进一步学习算法设计的兴趣。
有7位网友表示赞同!