算法设计与分析第五章——回溯法课后习题

首先总结一下我们用回溯法解题的三步法:

1.确定是子集树还是排序树

2.确定剪枝条件

3.套模板


        首先判断出这是一个子集树。其次判断剪枝条件:如果剩余的元素之和小于c,说明没有可能使得元素和=c,则剪枝。

        如果元素和等于c,则不用再遍历其孩子直接输出

例题给出答案有误,答案代码直接照抄了之前例题5.1的装载问题,然而装载问题是求最优解,这个题目的要求仅仅只是求解子集,也就是需要求出所有子集

void backtrack(int i){
    if (cw = c)
    {
      printSet();//一个输出子集的方法
      return;
    }
    else{
      r-=w[i];//r代表该集合剩余重量
      if(cw + w[i]<=c){ //小于<=c说明还需要深入
        x[i]=1;
        cw+=w[i];
        backtrack(i+1);
        cw-=w[i];
      }
      if(cw+r>=c)//如果当前+剩余>=c说明可能有解,否则剪枝
      {
        backtrack(i+1)
        r+=w[i];
      }
    }
}


又是电路板排序问题,不过这题不是求最小密度,而是最小长度。由于不同排序结果不同,那么这应该是一个排序树问题。

判断剪枝条件:我们每次遍历记录下当前最小的长度。如果当前长度大于或等于最小长度则剪枝,

(顺便吐槽一下范例答案里的例题和输入样例都不是同一个数据)


这个问题和最大装载问题是相似的。首先我们判断改变顺序无影响,所以这是子集树。

其次判断剪枝条件: 总价格大于d,或者重量大于当前最小重量

此外我们还可以简化时间复杂度,我们可以将所有零件按照价格cij从小到大的顺序进行排序,这样在我们遍历的时候,如果当前价格已经超过了d,那么接下来那些更大价格的一定也超过d,就不要遍历,直接剪枝。


首先由于解和排序相关,所以一定是一个排序树

假设男1配女2,即P[1][2]*Q[2][1]不一定是最大的,因为P[1][2]和P[2][1]是不一样的,因此我们还需要交换次序比较P[2][1]*Q[1][2]的大小。而且由于求的是总和最大,因此不到最后一步出不了结果,我们没法剪枝。但是当当前的总和大于最大总和时我们需要更新最大总和。


简单来说就是将一个大集合分成两个子集,并且每个子集中的任意两个元素相加之和不能属于该子集。比如我们看输入样例给出2,那么分出的两个无和子集最大元素可达到8,分为了1248和3567,并且都是无和子集。

首先我们确定这是一个子集树。

其次从题目所给要求看,这个集合内每个元素只能被选取一次,假如分为两个子集,那么这个元素不是子集1就是属于子集2,不是这个子集就是其他子集的。

而且既然要分无和子集,因此任意已有的两个元素之和就不能等于该子集的另一个元素


 首先gf排序顺序不同是一定会产生不同结果的,所以这是一个排序树。

我们考虑一下输出条件,比如该题给出的15变4,我们直接想想最后一步,只有经过f变化或g变化变成4,那么f变换是3*i,i必须是整数,因此不能用f变换得出4,因此最后一步一定是用g变换,也就是当i=8或9的时候,因此当所得整数应为偶数时,结束条件是变化后的数q=m*2或者m*2+1。

而如果需要从15得到3,那么同理g变化只能得出偶数,因此最后一步是f变化,所以q=m/3(因为奇数只能f变化得出,因此m一定是三的倍数)。

此外我们还需要剪枝条件,那就是当步数大于等于当前最小步数则剪枝,当小于当前最小步数则继续。

不过题目好像就很单纯,直接找到m,回溯完事了。


5-9问题比较简单就略过了。5-10这个问题 要求n种颜色n种形状的宝石,每行/列都应该有不同颜色和形状,计算方案个数。

首先确定,排序变换会导致结果改变,这是一个排序树。

我们可以按行顺序一一展开,不过情况较为复杂。首先宝石的形状和颜色都不能重复,也就是说我们第一个选择就有n*n种,选择一个宝石之后第二个还有(n-1)*(n-1)种选择,以前的题目我们只需要考虑一种分支,现在则需要考虑两种,因此输入输出都应该变为两倍。我们可以先确定颜色,再确定形状。用a[n][n]来存储形状矩阵,b[n][n]来存储颜色,cc[i][j]来判断形状为i,颜色为j的宝石是否已经被使用过。如果某格选取了颜色i,形状j的宝石,则cc[i][j]就=1,且这格上的宝石的颜色和形状与行列上已有元素的形状和颜色矩阵内的值都不能相同,否则剪枝(不遍历)。

那么判断该种颜色形状的宝石是否可取,只需判断cc[a[i][j]][b[i][j]]=1?如果cc等于1则说明当前选取的行/列已有相同形状或颜色,就不可取。

另外在展示答案代码之前我还有一种想法,我们以每行为一层深度,因为如果某形状某颜色被某格[i][j]选取之后,那么整个i行j列都不能有该形状和该颜色,我们不妨每行的n格看作n次循环,每次我们选取后cc[a[i][j]][b[i][j]]=1,比如现在是cc[1][1]=1,也就是第1行第1格为1。那么该行第2格就要排除所有形状为1颜色为1的宝石,也就是所有的cc[1][n]和cc[m][1]都不能选,我们把这些格子都置为1。我们重复这一步直到第一行选完,最后这行选完,格子全部为1。接下来到了第2行,我们在选取a[2][1]时要比较与上面那格a[1][1],不能选取相同颜色或形状,因此要将cc[a[2-1][1]][b[2-1][1]]按照刚才的规则将cc矩阵的a[2-1][1]行和b[2-1][1]列全部变成2,然后在剩余的格子1中选择(满足选择条件k-cc=1),重复上述步骤,第二行选完cc矩阵将变为全2,以此类推,第i行第j列的选择需要满足:

以cc[a[i-1][j]][b[i-1][j]]格为中心的十字格子全部=k,从满足k-cc[][]=1的格子寻找要选择的宝石。每次遍历完cc矩阵都会变成格子全为k的矩阵

上面所提只是的个人一些想法,看看例题答案

 


回溯法走迷宫问题,要求每个格子只走一遍,且拐弯次数最少。对于罗密欧来说有八个方向可以走。

这是一个子集树问题,然后我们如果要尽可能少改变方向,那就应该选定一个方向一直走下去,直到该方向上不能再前进才改变方向。我们可以设置一个数组{(-1,-1),(0,-1),(1,-1),(-1,0),(1,0),(-1,1),(0,1),(1,1)}代表了八个方向(i,j),我们看看要前进的下一格[p+i][q+j]是否可行,可行则前进,并将[p+i][q+i]改为步数deps+1。若不可行则换下一个可行的方向,直到满足输出条件为止,同时用一个数字dirs记录变向次数。

剪枝条件:若超出边界或者房间封闭则不可行,或是转向的格子已经走过则剪枝。另外用一个数字记录走过的格子数,如果走过的格子数!=m*n-k-2(总格子-封闭房间-罗密欧朱丽叶所在两格),则不能走进朱丽叶所在格子。另外如果转向次数大于当前最小转向次数也剪枝。


 

 首先判断这是子集树,为什么不是排序树?因为每种数字最多用一次,如果树的根为1,孩子分别为234.假设调换12,根为2,孩子为134,那么这两个树完全不一样,因为根为1,如果往孩子3走,孩子3有孩子24,而根为2往孩子3走则孩子3有孩子14,明显就不一样了。不是说换了排序之后结果不同就一定是排序树。

判断剪枝条件:首先给出4个数字不必全部用到,每种最多用一次,因此如果有一个数字用了一次就不该继续遍历,只能遍历数字用了0次的。其次这是一个最优解的问题,我们记录当前最少的运算次数m,运算次数>=m还得不出结果的都剪枝。然后选择的时候需要数字和运算符交替选择


 

首先我们确定这个问题是子集树问题。其次来确定怎么遍历以及剪枝条件

我们用两个矩阵来表示,其中矩阵x[m][n]代表警卫机器人的位置矩阵,y[m][n]代表房间被监控矩阵。既然要遍历矩阵,那么最好是逐行遍历。

那么我们考虑一下最差的情况,最差情况(合理的最差情况,起码碰到黑的格子就不放)就是像把监视器放在四角,比如从(1,1)开始,那涂黑的格子只有它自己和相邻两个,然后往右数一个是黑的不放,再数一个是白的(1,3),放在这里,又只能涂黑三个。也就是说最差的情况,对剩下的t个格子每次只能涂黑三个格子,那么也有t/3个机器人,而最佳情况,对于剩下的t个格子,每次能涂黑五个格子,那么至少需要t/5个机器人

所谓控制关系,简而言之就是你能涂黑的我也能涂黑 ,你是我的子集。比如图上的例子,如果y(i,j+1)是黑的,那么p就控制q,因为p能包括本格在内涂黑5格,q只能涂黑2格(y(i,j+1)已经被涂黑不算),则称p控制q。同理若y(i,j+1)=y(i,j+2)=1,则p不仅控制q,而且控制r。因此我们总要先考虑p,是否取p则需要先考虑q的条件,再考虑r的条件。因此以pqr的顺序来拓展结点。

 

 

 后面的题目就不讲了,有时间再深入吧

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是第四章的部分习题及答案,希望对你有帮助。 1. 设计一个O(nlogn)时间复杂度的算法,找出一个整数数组中的最大差值,其中最大值必须位于最小值之后。 解答:可以使用分治法,将数组分成两部分,分别求出左边的最大值和右边的最小值,然后比较两个值之间的差值,取最大值即可。具体实现如下: ```python def max_diff(arr): if len(arr) < 2: return 0 mid = len(arr) // 2 left_max = max(arr[:mid]) right_min = min(arr[mid:]) return max(right_min - left_max, max_diff(arr[:mid]), max_diff(arr[mid:])) ``` 2. 设计一个算法,找出一个整数数组中的最大子序列和。 解答:可以使用动态规划法,定义一个状态数组dp,dp[i]表示以第i个元素结尾的最大子序列和,转移方程为:dp[i] = max(dp[i-1] + arr[i], arr[i])。最后返回dp数组中的最大值即可。具体实现如下: ```python def max_subarray(arr): if not arr: return 0 dp = [0] * len(arr) dp[0] = arr[0] for i in range(1, len(arr)): dp[i] = max(dp[i-1] + arr[i], arr[i]) return max(dp) ``` 3. 给定一个长度为n的整数序列,设计一个算法,找出其中第k大的数。 解答:可以使用快速排序的思想,每次选定一个pivot,将数组分成两部分,左边的元素都小于pivot,右边的元素都大于等于pivot。然后比较pivot的位置和k的大小,如果pivot的位置大于k,则在左边继续查找;如果pivot的位置小于k,则在右边继续查找。具体实现如下: ```python def quick_select(arr, k): if not arr or k > len(arr): return None pivot = arr[-1] left = [x for x in arr[:-1] if x < pivot] right = [x for x in arr[:-1] if x >= pivot] if len(right) == k - 1: return pivot elif len(right) > k - 1: return quick_select(right, k) else: return quick_select(left, k - len(right) - 1) ``` 4. 设计一个算法,找出一个无序整数数组中出现次数超过一半的数。 解答:可以使用摩尔投票法,遍历整个数组,维护一个候选数和计数器,如果当前元素等于候选数,则计数器加1;否则计数器减1。如果计数器归零,则将当前元素作为候选数。最后再遍历一遍数组,统计候选数的出现次数,如果出现次数超过一半,则返回该候选数。具体实现如下: ```python def majority_element(arr): if not arr: return None candidate = None count = 0 for num in arr: if count == 0: candidate = num count += (1 if num == candidate else -1) if arr.count(candidate) > len(arr) // 2: return candidate else: return None ``` 5. 设计一个算法,找出一个整数数组中的两个元素,使它们的和等于一个给定的数。 解答:可以使用哈希表,遍历整个数组,对于每个元素,如果它的补数已经在哈希表中,则返回它们的下标;否则将该元素加入哈希表中。具体实现如下: ```python def two_sum(arr, target): if not arr: return None table = {} for i, num in enumerate(arr): complement = target - num if complement in table: return (table[complement], i) else: table[num] = i return None ``` 希望这些答案能够帮助你解决算法设计与分析第四章的部分习题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值