问题描述

有n个物品和一个容量为c的背包,从n个物品中选取装包的物品。物品i的重量为w[i],价值为p[i]。一个可行的背包装载是指,装包的物品总重量不超过背包的重量。一个最佳背包装载是指,物品总价值最高的可行的背包装载。
我们要求出x[i]的值。x[i] == 1表示物品i装入背包,x[i] == 0表示物品i没有装入背包。

问题的公式描述是:

//总价值最高的可能的背包装载,x[i]只能等于0或者1
max{p[1] * x[1] + p[2] * x[2] + ... + p[i] * x[i] + ... + p[n] * x[n]}

约束条件

//装入的所有物品的重量之和不大于背包容量
size_t totalWeight = 0;
for(size_t i = 1; i <= n; ++i)
{totalWeight += w[i] * x[i]; //x[i] = 0 || x[i] = 1
}
if(totalWeight <= c)//约束条件成立
else//约束条件不成立

回溯法

顾名思义,回溯法一个很显著的特征就是回溯,即在处理完某种情况或出现不能继续进行的情况时,要退回到之前的某个“交叉口”,处理另一种可能。
通常,回溯法会定义一个解空间,这个解空间通常是以图或树的形式呈现出来的。背包问题的解空间是树的形式,属于深度优先搜索。

问题分析

考察这样一个0/1背包问题: 总共4个物品,重量分别是w[1:4] = {8, 6, 2, 3},价值分别是p[1:4] = {8, 6, 2, 3},规定背包容量为12(即可以容纳的最大重量为12),求出获得最大价值的解情况。

回溯法首先要做的就是根据已知条件建立解空间树。

如图为根据4个物品的重量建立的解空间树,根节点A没有任何意义,每个节点的高度代表它是第几个节点(A节点高度为0)。对于一个节点Z,它的高度是H,则Z节点表示的就是物品H。左孩子表示装入物品H+1(权值是物品H+1的重量),右孩子表示不装入物品H+1(权值是0)。
可以理解为Z就是一个“交叉口”,向Z左边走是一种情况(即物品H+1装入背包的情况),向Z右边走是另一种情况(即物品H+1没有装入背包的情况)。
另外,如果Z的权值不是0,就证明Z的父节点的左孩子,表示物品H已经装入背包。

对于本题而言,起始位置在A点,当前可用容量为12.
1.因为12>8,所以可以向左孩子节点移动,此时位于B点,可用容量变为4,高度为1,表示第一个物品装入背包。
2.考虑B的左右孩子节点,因为当前可用容量不足以装入物品2,所以不能够移向D点,故移动到E点,可用容量仍为4,高度为2,表示第二个物品没有装入背包。
3.考虑E的左右节点J和K,因为当前可用容量可以装入物品3,所以可以移动到J点,可用容量变为2,高度为3,表示第三个物品装入背包。
4.考虑J的左右孩子节点,因为2<3,不足以装入物品4,所以只能够移动到J的右孩子结点。可用容量仍为2,高度为4,表示第四个物品没有装入背包。
5.到达叶子节点,这种情况下背包装入情况是[1,0,1,0],获得的总价值为10,记录此时的叶子节点。
6.开始向上回溯,即回到此时节点的父节点处,回到J点。可用容量为2,高度为3.
7.因为J的右孩子已经考虑过了,所以继续向上回溯,回到J的父节点E点。可用容量要加上J的重量,变为4,高度为2。这时回到了步骤3的交叉口(称为回溯)。
8.因为刚才移动到E的左孩子节点,所以回溯回来后移动到E的右孩子节点K。此时,可用容量不变,仍为4,高度为3。表示第三个物品没有装入背包。
9.考虑K的左右孩子节点,因为4>3,所以可以移动到K的左孩子节点,当前可用容量变为1,高度为4,表示第四个物品被装入背包。
10.到达叶子结点,这种情况下背包装入情况是[1,0,0,1],获得的总价值是11大于10,所以更新最优解的叶子节点。
11.继续向上回溯,直到处理完所有情况。

以上就是回溯法的大体思路,接下来会分别用递归和迭代两种方法求解。

递归求解

递归求解相对简单,不过要对回溯有比较好的理解。

为了减少递归调用传参的代价,可以把大部分的变量作为全局变量使用。

int *Weight; //Weight[i]表示物品i的重量
int *Profit; //Profit[i]表示物品i的价值量
int n; //物品个数

首先考虑如何构建解空间树,
一种方法是以树节点指针的形式描述,但是回溯起来需要另外添加父节点指针。
另一种方法是使用一维数组表示解空间树,对于某个节点Z,它在数组中的索引是index,则Z的左孩子索引是index * 2,Z的右孩子索引是index * 2 + 1,Z的父节点索引是index / 2。(这种描述方法和大根堆,小根堆一样)

这里采用一维数组描述解空间树,
1.需要事先算出树中节点个数。因为已知物品数量n,并且树中每一层代表一个物品,所以树的高度是n+1(算上根节点A)。利用等比数列求和公式可以求出节点个数为2的n+1次方-1。
2.初始化解空间树的一维数组。因为每一层的节点数量和该层的高度有关,所以可以事先算出这一层第一个节点的索引和最后一个节点的索引。然后一个赋值成Weight[i],一个赋值成0,每次i += 2。

初始化解空间树的代码如下:

//节点个数为2的n+1次方-1
size_t pNodeSize = 1;
for(size_t i = 1; i <= n+1; ++i)pNodeSize *= 2;
pNodeSize--;//根节点的索引为1,权值为0
int *pTree = new int[pNodeSize+1];
pTree[1] = 0;for(size_t pHeight = 1; pHeight <= n; ++pHeight)
{//高度为pHeight的第一个节点的索引为2的pHeight次方size_t pStartNode = 1;for(size_t i = 1; i <= pHeight; ++i)pStartNode *= 2;//高度为pHeight的最后一个节点的索引为pStartNode * 2 - 1for(size_t i = pStartNode; i < pStartNode * 2; i += 2){//高度为pHeight表示的就是第pHeight个物品,重量即为Weight[pHeight]pTree[i] = Weight[pHeight]; //左孩子pTree[i+1] = 0; //右孩子}
}

由上面的图片发现,每一层的赋值实际上就是一个w[i],一个0,一个w[i],一个0…
w[i]永远是父节点左孩子的权值,如果到达这个节点就表示将物品i装入背包。
0永远是父节点右孩子的权值,如果到达这个节点就表示物品i没有装入背包。

到此为止,解空间树的初始化工作就完成了。现在添加全局变量

int n; //物品个数
int Capacity; //背包容量
int *Weight; //Weight[i]表示物品i的重量
int *Profit; //Profit[i]表示物品i的价值量
int *pTree; //解空间树,pTree[i]表示节点i的重量,同时又可以用于判断是否装入物品i
int pNodeSize; //节点数量
int pLastNode; //最优解的叶子结点索引
int pMaxProfit; //最优解的值,初始为0
int pHeight; //当前高度,初始为0,始终表示当前结点的高度
int pCurrentProfit; //当前价值量,初始为0
int pCurrentWeight; //当前加入背包的总重量,初始为0

递归程序核心步骤就是向左右孩子节点移动的过程,创建递归函数

void Backpack(size_t pCurrentNode);

递归函数表示的是在节点pCurrentNode这个位置,考虑向它左右孩子移动的问题(也就是选择物品H+1和不选择物品H+1的问题)。
pHeight始终是pCurrentNode的高度,也表示第几个物品。

1.如果向左孩子移动,即pCurrentNode * 2,表示第pHeight + 1个物品装入背包。则pCurrentProfit需要加上第pHeight + 1个物品的价值量,同时pCurrentWeight需要加上第pHeight + 1个物品的重量。
2.如果向右孩子移动,即pCurrentNode * 2 + 1,表示第pHeight + 1个物品不装入背包。pCurrentProfit和pCurrentWeight都不需要改变。

需要注意
1.pHeight在刚进入函数时表示的是pCurrentNode节点的高度,也表示当前考虑的是第几个物品。pHeight+1表示下一个物品,即pCurrentNode * 2和pCurrentNode * 2 + 1表示的物品。
2.只有背包剩余容量足够装入下一个物品时,才向左孩子节点移动。而向右孩子节点移动不需要考虑背包剩余容量是否足够装入,因为往右孩子移动意味着不装入下一个物品

另外考虑回溯的情况,当向左孩子移动之后经过一系列操作返回到该节点处,也就是说Backpack(pCurrentNode * 2);返回后,说明左孩子节点表示的物品(下一个物品)装入背包的情况已经考虑完了,需要把调用Backpack(pCurrentNode * 2);之前为pCurrentWeight和pCurrentProfit加上的值减去,因为它们加的值是下一个物品的重量和价值(它已经考虑完了,接下来需要考虑右节点,下一个物品没有装入背包的情况了)

所以两种情况的考虑代码如下:

void Backpack(int pCurrentNode)
{   //到达叶子节点,更新最优解,记录最优解的叶子结点if(pCurrentNode * 2 > pNodeSize){if(pCurrentProfit > pMaxProfit){pMaxProfit = pCurrentProfit;pLastNode = pCurrentNode;}return;}//高度加一,此时表示的是下一个物品(pCurrentNode左孩子和右孩子表示的物品)pHeight++; //如果当前背包装入的重量加上下一个物品的重量仍然小于等于背包容量//则可以将下一个物品加入背包if(pCurrentWeight + Weight[pHeight] <= Capacity){//假设pCurrentNode表示的是物品i,则此时加上的是物品i+1的重量和价值//因为物品i的重量和价值已经在上一层递归中加上了pCurrentWeight += Weight[pHeight];pCurrentProfit += Profit[pHeight];Backpack(pCurrentNode * 2); //跳转到左孩子,表示将物品i+1(pHeight)装入背包//回溯到这个节点后,物品i+1装入背包的情况已经考虑完,需要将物品i+1的重量和价值减去//开始考虑物品i+1没有装入背包的情况pCurrentWeight -= Weight[pHeight];pCurrentProfit -= Profit[pHeight];}//跳转到右孩子节点,表示物品i+1没有装入背包(i + 1 == pHeight)Backpack(pCurrentNode * 2 + 1);//返回后,回溯到这个节点,物品i+1没有装入的情况也已经考虑完,需要向上回溯,高度减一pHeight--;
}

大体的流程已经完成。现在考虑一个问题,在一系列操作之后,从节点Z的左孩子节点回溯到Z。假设节点Z表示的是物品i,此时
pCurrentWeight表示的是物品1,2,…,i装入背包情况的总重量,即前i个物品装入背包的重量。
pCurrentProfit表示的是前i个物品装入背包的总价值。
假设存在一个变量pRemainingProfit,它表示从物品i+2到物品n的价值总和。那么我们就可以根据pRemainingProfit和pCurrentProfit的大小来决定是否还有必要跳转到右孩子节点。
像这样,为跳转到右孩子增加一个限制条件。

...
if(pRemainingProfit + pCurrentProfit > pMaxProfit)Backpack(pCurrentNode * 2 + 1);
...

注:因为上述是为了判断是否向右孩子跳转,又因为右孩子表示物品i+1不装入背包,所以剩余价值不包括物品i+1的价值。

优化后的代码如下(注意需要将pRemainingProfit加入到全局变量,初始化为所有物品的总价值和):

void Backpack(int pCurrentNode)
{   //到达叶子节点,更新最优解,记录最优解的叶子结点if(pCurrentNode * 2 > pNodeSize){if(pCurrentProfit > pMaxProfit){pMaxProfit = pCurrentProfit;pLastNode = pCurrentNode;}return;}//高度加一,此时表示的是下一个物品(pCurrentNode左孩子和右孩子表示的物品)pHeight++; //如果pCurrentNode表示物品i, 则加一后的pHeight表示物品i+1,pRemainingProfit应该把物品i+1的价值减掉pRemainingProfit -= Profit[pHeight];//如果当前背包装入的重量加上下一个物品的重量仍然小于等于背包容量//则可以将下一个物品加入背包if(pCurrentWeight + Weight[pHeight] <= Capacity){//假设pCurrentNode表示的是物品i,则此时加上的是物品i+1的重量和价值//因为物品i的重量和价值已经在上一层递归中加上了pCurrentWeight += Weight[pHeight];pCurrentProfit += Profit[pHeight];Backpack(pCurrentNode * 2); //跳转到左孩子,表示将物品i+1(pHeight)装入背包//回溯到这个节点后,物品i+1装入背包的情况已经考虑完,需要将物品i+1的重量和价值减去//开始考虑物品i+1没有装入背包的情况pCurrentWeight -= Weight[pHeight];pCurrentProfit -= Profit[pHeight];}//跳转到右孩子节点,表示物品i+1没有装入背包(i + 1 == pHeight)if(pRemainingProfit + pCurrentProfit > pMaxProfit)Backpack(pCurrentNode * 2 + 1);//返回后,回溯到这个节点,物品i+1没有装入的情况也已经考虑完,需要向上回溯,高度减一,pRemainingProfit加回pRemainingProfit += Profit[pHeight];pHeight--;
}

迭代求解

迭代求解就是将递归的每一步细化,但是考虑到回溯的问题,需要解决两个问题。
1.为了不重复回到已经到达的节点,需要一个一维数组pReach[],pReach[i] == 1表示达到过节点i,pReach[i] == 0表示没有到达过节点i。
2.回到父节点只需pCurrentNode /= 2即可,这也是用一维数组存储树节点的好处。

其他的就是细化递归代码的工作了。

首先考虑对每一个节点的处理(只处理之前没有到达过的节点)
1.如果该节点的权值不为0,表示将物品装入背包,则需要将pCurrentProfit和pCurrentWeight更新,然后标识为到达。
2.如果该节点的权值为0,表示不将该物品装入背包,则不需要做任何处理

pHeight始终表示pCurrentNode的高度,也表示当前考虑第几个物品。

if(pReach[pCurrentNode] == 0)
{//只在装入该物品时才更新pCurrentWeight和pCurrentProfitif(pTree[pCurrentNode] != 0){pCurrentWeight += pTree[pCurrentNode];pCurrentProfit += Profit[pHeight];}pReach[pCurrentNode] = 1;
}

其次考虑到达叶子节点的情况
1.如果需要更新最优解的值,则更新并记录最优解的叶节点索引
2.到达叶子节点之后,向上回溯。

if(pCurrentNode * 2 > pNodeSize)
{if(pCurrentProfit > pMaxProfit){pMaxProfit = pCurrentProfit;pLastNode = pCurrentNode;}//回溯部分
}

然后考虑向左右节点移动的情况。
1.向左移动,首先之前没有到达过左孩子节点,然后剩余容量要足够装入下一个物品。
2.向右移动,之前没有到达过右孩子节点。
3.如果都不满足(即左右节点都已经考虑完),向上回溯。

//向左孩子移动
if(pReach[pCurrentNode * 2] == 0 &&pCurrentWeight + pTree[pCurrentNode * 2] <= Capacity)
{pCurrentNode *= 2;pHeight++;
}
//向右孩子移动
else if(pReach[pCurrentNode * 2 + 1] == 0)
{pCurrentNode = pCurrentNode * 2 + 1;pHeight++;
}
//如果左右孩子都已经到达过,就不需要再次移动。
//这时需要向上回溯
else
{//回溯部分
{

最后考虑回溯部分。
由对节点的处理部分可以得知,假设pCurrentNode表示的是物品i,那么pCurrentWeight和pCurrentProfit表示的是物品1,2,…,i的背包装入问题的当前重量和当前价值。
所以在回溯到父节点之前需要将物品i的重量和价值减去(如果物品i装入背包的话)。
同时改变当前节点令其表示其父节点,高度减一。

//回溯部分
//由解空间树可以看出,pTree[pCurrentNode]!=0表示选择了该物品
//只有当选择该物品时(第pHeight个物品),才需要改变当前重量和当前价值
if(pTree[pCurrentNode] != 0)
{pCurrentWeight -= pTree[pCurrentNode];pCurrentProfit -= Profit[pHeight];
}
//回溯到父节点,高度减一
pCurrentNode /= 2;
pHeight--;

接下来,利用递归程序中判断是否需要移动到右孩子节点的方法,在迭代程序中添加判断部分。假设pCurrentNode表示物品i,则只有当
从物品i+2到n的价值量(剩余价值量) + 当前价值量 > 当前最优解(pMaxProfit)时,才需要跳转到右孩子节点。因为只有这种情况,才有可能产生比当前最优解更优的解,否则不会产生更优的解,没有意义。
体现在程序中就是在跳转到右孩子节点的if判断中增加一条约束:

...
//增加价值量的判断
else if(pReach[pCurrentNode * 2 + 1] == 0 &&pRemainingProfit + pCurrentProfit > pMaxProfit)
{pCurrentNode = pCurrentNode * 2 + 1;pHeight++;
}
...

假设pCurrentNode表示的是物品i,需要注意的是,pRemainingProfit始终表示的是i+2,i+3,…,n这几个物品的价值总和。因为向右跳转证明物品i+1没有被装入背包,它的价值和重量不会算在pCurrentWeight和pCurrentProfit中。

接下来可以明确的是,肯定是一个循环来执行上述求解过程,在第一次循环中pCurrentNode = 1,表示的是根节点(A),这个节点没有实际的意义。
为什么不是2呢,因为在选择第一个物品时有装入和不装入两种情况,而如果一上来就另pCurrentNode = 2,那么就相当于默认选择了物品1,不选择物品1的情况被忽略了。
然后考虑,假设从节点A跳转到左孩子节点B,然后在A的左半部分执行一系列操作回溯到A点后,开始向右半部份跳转,又执行一系列操作回溯到A点,这时A的左右孩子都已经考虑过了,需要执行“向左右孩子节点移动的情况”中else部分,即向上回溯,这时pCurrentNode /= 2,导致pCurrentNode = 0,在这时我们需要终止算法。所以很显然需要一个while循环,判断的条件就是pCurrentNode != 0。即

int pCurrentNode = 1;
while(pCurrentNode != 0)
{...
}

将上面的代码组装起来
1.先计算解空间树的节点数量
2.初始化解空间树
3.初始化pReach数组,用于判断某个节点是否到达过
4.初始化各种变量,如pCurrentWeight,pCurrentProfit,pCurrentNode…
5.while循环开始回溯程序

注意不要忘记添加pRemainingProfit的修改代码:

void Backpack(int Weight[], int Profit, int n, int Capacity)
{//计算解空间树的节点个数size_t pNodeSize = 1;for (size_t i = 1; i <= n + 1; ++i){pNodeSize *= 2;}pNodeSize--;//初始化解空间树,对于某一个节点Z,Z的下一层节点表示物品i//Z的左孩子表示选择该物品,Z的右孩子表示不选择该物品//相应的左孩子的权值就是物品i的重量,右孩子的权值为0int *pTree = new int[pNodeSize + 1];//pReach数组记录节点的到达情况,遇到过的节点值为1,反之为0.用于回溯判断int *pReach = new int[pNodeSize + 1];for (size_t pHeight = 1; pHeight <= n; ++pHeight){//为解空间树赋值,每层都是weight[pHeight], 0, weight[pHeight], 0, .....size_t pStartNode = 1;for (size_t i = 1; i <= pHeight; ++i)pStartNode *= 2;for (size_t i = pStartNode; i < pStartNode * 2; i += 2){pTree[i] = Demension[pHeight];pTree[i + 1] = 0;pReach[i] = pReach[i + 1] = 0;}}//pRemainingProfit:记录剩余的总价值量,用于判断是否还需要在右子树中查找//若当前高度为height,则pRemainingProfit记录的永远是从物品height+2到backCnt的总价值size_t pRemainingProfit = 0;for (size_t i = 1; i <= n; ++i)pRemainingProfit += theProfit[i];size_t pLastNode = 0; //当前最优解的最后一个节点size_t pMaxProfit = 0; //当前最优解size_t pCurrentWeight = 0;  //当前重量size_t pCurrentProfit = 0;  //当前总价值size_t pHeight = 0; //节点高度,同时也是物品的索引,表示第pHeight个物品size_t pCurrentNode = 1; //当前节点while (pCurrentNode != 0){//如果之前没有到达过当前结点,则将当前结点的重量,价值加入//更新节点到达情况if(pReach[pCurrentNode] == 0){   if(pTree[pCurrentNode] != 0){pCurrentWeight += pTree[pCurrentNode];pCurrentProfit += theProfit[pHeight];}pReach[pCurrentNode] = 1;}//判断是否到达叶子节点//如果到达叶子节点,同时当前的总价值大于上一次的最优解,则将最优解更新为当前的总价值//同时记录最优解对应的最后一个节点位置if (pCurrentNode * 2 > pNodeSize){if (pCurrentProfit > pMaxProfit) {pMaxProfit = pCurrentProfit;pLastNode = pCurrentNode;}//到达叶子节点,更新完数据后就应该向上回溯,回溯方法pCurrentNode /= 2,返回父节点。//每次向上回溯,都应该将该层的重量和价值从当前价值和当前重量中减去//注意只有当是左孩子的时候才减去价值         if(pTree[pCurrentNode] != 0){pCurrentWeight -= pTree[pCurrentNode];pCurrentProfit -= theProfit[pHeight];}pCurrentNode /= 2;  pHeight--;//注:这里不需要更新pRemainingProfit,因为pRemainingProfit表示的是从当前结点的孙子开始计算的总价值//而此时当前结点跳到叶节点的父节点处,没有孙子节点。}//没有到达叶子节点else{//剩余价值量减去当前结点孩子的价值pRemainingProfit -= theProfit[pHeight+1];//当当前结点的左孩子没有达到过且容量足以满足左孩子的重量时,跳转到左孩子if (pReach[pCurrentNode * 2] == 0 &&pCurrentWeight + pTree[pCurrentNode * 2] <= theCapacity){pCurrentNode *= 2;pHeight++;}//当当前结点左孩子节点已经到达过,而右孩子没有到达过,//同时当前价值量加上剩余价值量有大于当前最优解的可能时,跳转到右孩子处//注:因为右孩子表示没有选择该物品,所以该物品的价值就无需考虑,//这也正是为什么pRemainingProfit表示的是从孙子节点开始的价值量,因为右孩子节点的价值就是0else if (pReach[pCurrentNode * 2 + 1] == 0 && pCurrentProfit + pRemainingProfit > pMaxProfit){pCurrentNode = pCurrentNode * 2 + 1;pHeight++;}else{//左右孩子都不满足条件,则继续向上回溯if(pTree[pCurrentNode] != 0){pCurrentWeight -= pTree[pCurrentNode];pCurrentProfit -= theProfit[pHeight];}pCurrentNode /= 2;//将孩子节点的价值加回pRemainingProfit += theProfit[pHeight+1];pHeight--;}}}
}

输出背包装入情况

上面递归和迭代两个程序中,在更新最优解的同时记录了一个变量pLastNode,它表示的是最优解的叶子节点在数组pTree中的索引。又因为从根节点到该叶子节点只有一条路径,所以可以从叶子节点不断跳转到父节点(即不断的pLastNode /= 2),直到pLastNode等于1。在这个过程中,如果到达的节点权值为0,那么表示相应高度对应的物品没有装入背包,如果达到的节点权值不为0,那么表示相应高度对应的物品装入背包。所以在上述过程中,只需要根据pTree[pLastNode]的值就可以得到背包装入问题。

pHeight = n;
while(pLastNode != 1)
{if(pTree[pLastNode] == 0)std::cout << "Backpack" << pHeight << ": " << 0 << std::endl;elsestd::cout << "Backpack" << pHeight << ": " << 1 << std::endl;pLastNode /= 2;pHeight--;
}

0/1背包问题-----回溯法求解相关推荐

  1. 回溯法求解0-1背包问题

    回溯法求解0-1背包问题时比较随机序列和按 v/w 降序排列的算法 问题描述: 针对0-1背包问题,尝试用回溯法. 物品总数N=10,背包容量 C=26, 物品的重量数组为w={7,3,10,12,1 ...

  2. 01背包问题【回溯法求解】通俗易懂,适合小白

    本人此时还是一名研一的小菜鸡,刚学会了这个算法的基本概念,来总结一下,谁知道今后的我再看到这篇自己写的博客的时候会不会笑出来,哈哈哈哈哈哈哈哈,所以吗,错了的化大佬们评论指正就好了. 还有系列文章动态 ...

  3. 回溯法求解0-1背包问题(细节分析)

    回溯法求解0-1背包问题(细节分析) 论temp数组的必要性和判断cv>bestv的不必要性 回溯法 应用回溯法求解问题时,首先应明确定义问题的解空间,该解空间应至少包含问题的一个最优解.例如, ...

  4. 【算法分析】实验 4. 回溯法求解0-1背包等问题

    目录 实验内容 实验目的 实验结果 步骤1:描述与分析 步骤2:策略以及数据结构 步骤3 步骤4 步骤5 步骤6 实验总结 实验内容 本实验要求基于算法设计与分析的一般过程(即待求解问题的描述.算法设 ...

  5. java背包算法回溯法_【算法分析】实验 4. 回溯法求解0-1背包等问题

    [TOC] 实验内容 本实验要求基于算法设计与分析的一般过程(即待求解问题的描述.算法设计.算法描述.算法正确性证明.算法分析.算法实现与测试),通过回溯法的在实际问题求解实践中,加深理解其基本原理和 ...

  6. c语言 用回溯算法解决01背包问题,回溯法解决01背包问题

    <回溯法解决01背包问题>由会员分享,可在线阅读,更多相关<回溯法解决01背包问题(21页珍藏版)>请在人人文库网上搜索. 1.回溯法解决01背包问题,回溯法解决01背包问题, ...

  7. 回溯法求解N皇后问题(Java实现)

    回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并以此慢慢地扩大问题规模,迭代地逼近最终问题的解.这种迭代类似于穷举并且是试探性的,因为当目前的可 ...

  8. php生成迷宫图片,PHP实现基于回溯法求解迷宫问题的方法详解

    本文实例讲述了PHP实现基于回溯法求解迷宫问题的方法.分享给大家供大家参考,具体如下: 引言 最近在leetcode上看了些算法题,有些看着很简单的很常用的东西,竟然一下子想不出来怎么求解,比如说:实 ...

  9. 回溯法求解图着色问题

    回溯法求解图着色问题 #include <iostream> #include <cstdlib> using namespace std; #define n 5 #defi ...

最新文章

  1. 函数式思维: 利用 Either 和 Option 进行函数式错误处理 类型安全的函数式异常...
  2. python表达式的值是 y 和n是什么意思_python中^是什么意思
  3. 《Spring1之 第一次站立会议(重发)》
  4. NAT对数据业务的影响
  5. 苹果电脑如何设置屏保时间?
  6. 网易云音乐的品牌竞争力研究
  7. 细数历史上那些有名的程序媛
  8. 现场总线技术笔记——2、网络结构(7层结构、互联设备)
  9. HTML5期末大作业:动漫A网站设计——动画漫展学习资料电影模板(6页) 网页设计作业 / 动漫网页设计作业,网页设计作业 / 动漫网页设计成品,网页设计作业 / 动漫网页设计成品模板下载
  10. linux 开机运行应用程序
  11. MATLAB实现地球表面上两点之间的仰角和方位角计算
  12. GB28181语音对讲对接华为IVS平台遇到的坑和解决办法
  13. 加入域时出现“不能访问网络位置”错误信息
  14. 海外博士/博士后职位网站汇总
  15. 还在自建代码仓库?阿里云 云效 的这款企业级代码管理工具免费、还香!
  16. python3 NCR 字符解码
  17. K8S与Vault集成,进行Secret管理
  18. 【重读经典】《Python核心编程(第3版)》
  19. 宅急送项目第五天笔记!
  20. face++与python实现人脸识别签到(考勤)功能

热门文章

  1. Java黑皮书课后题第10章:*10.5(显示素数因子)编写一个程序,提示用户输入一个正整数,然后以降序显示它的所有最小因子
  2. Java黑皮书课后题第8章:*8.2(求矩阵对角线元素的和)使用下面的方法头编写一个方法,求n*n的double类型矩阵中主对角线上所有数字的和。编写一个程序,读取一个4*4的矩阵,显示主对角线和
  3. 爬虫代理及ssl验证
  4. Python中为什么推荐使用isinstance来进行类型判断?而不是type
  5. Android的基本常用的短信操作
  6. 真恶心,用安卓模拟器开微信不能找附近的人
  7. IOS设计模式之四(备忘录模式,命令模式)
  8. 特权同学笔记-榨干FPGA片上存储资源
  9. 【转】 已知有个rand7()的函数,返回1到7随机自然数,让利用这个rand7()构造rand10() 随机1~10...
  10. 修改web.config