这次练习主要针对的是“回溯法”
简单介绍一下,回溯法->深度优先搜索算法->dfs(Depth First Search)
所以个人习惯上都是对于任何需要回溯的问题,其函数命名为dfs。
深度优先搜索,本质上是对一颗搜索树进行搜索。
相较于BFS来说,DFS搜索顺序为“找到一个节点一直搜索到叶子结点,到了叶子再回头”
对于BFS顺序为:A,B,C,D,E,F,G……
对于DFS顺序为:A,B,D,H,D,I,D,B,E,J,E,K,E,B,A……
BFS和DFS两者之间:
1、dfs相较于bfs比较方便好使,因为过程不借助队列
2、如果同一个问题用bfs和dfs都能实现情况下,通常用bfs,
原因有:搜索空间一定的情况下,dfs比bfs多了一步回溯的操作。
例如迷宫问题明显时BFS优势大。
对于期末考试考查以下三种能力
1、子集树(01背包问题,幻方数)
2、排列树(TSP问题)
3、类多叉树遍历(李白打酒,n皇后)
热身环节
子集树
算法本质
顾名思义,利用集合的思路进行操作。
集合有三种性质,(无序性,互异性,确定性)
在求解过程中,利用更多的是“互异性” (即集合中不存在两个相同的元素)。
引入问题
01背包
问题描述:
对于给定一个背包,其背包有一定的承重能力,给出有n个物品,物品以<value,weight>形式出现。
请问在背包承重范围内,实现背包的最大价值。
题解:
假定 每个物品“取和不取” => 取 <-> '1' 不取 <-> '0'
然后对于3个物品的背包问题共有2^3=8种情况,如下所示
(000) -> {}
(001) -> {1}
(010) -> {2}
(011) -> {1,2}
(100) -> {3}
(101) -> {1,3}
(110) -> {2,3}
(111) -> {1,2,3}
由于集合的互异性,每次在物品放与不放时,都需要判断当前集合中是否存在物品。
我们需要借助一个标记数组来进行操作
标记数组名可以为“vis(visit)” , "book" , "st(state)" , "used"
问题转化为:构建一颗搜索树,高度为n层,每一个节点都表示背包状态,
同时每一层都是表示物品放和不放,若走左子树<->物品放,否则走右子树<->物品不放。
答案即为:所有叶子结点的最小值.
1 //dfs子集树解决01背包问题 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 const int N = 5; 6 7 int value[] = { 45 , 25 , 25 }; 8 int weight[] = { 16 , 15 , 15 }; 9 int V = 30 ; 10 11 int n = 3 ; //物品数量 12 int ans ; //答案 13 int val , w ; //每个结点中<value,weight>的状态 14 int vis[N] ; //物品标记状态 15 16 void dfs( int step ){ 17 18 //到达叶子结点 19 if( step == n ){ 20 ans = max( ans , val ); 21 return ; 22 } 23 24 //物品不在背包中,且放物品后还在背包承受范围内. 25 if( vis[step] == 0 && w + weight[step] <= V){ 26 //对于第Step个物品进行标记,同时更新结点对应的<val,w>状态 27 vis[step] = 1 ; 28 w += weight[step] ; 29 val += value[step] ; 30 31 //往左子树走 32 dfs( step + 1 ); 33 34 //回溯,返回结点后需要把第step个物品取出来, 35 //同时恢复 结点对应的<val,w>状态 36 val -= value[step] ; 37 w -= weight[step] ; 38 vis[step] = 0 ; 39 } 40 //往右子树走 41 dfs( step + 1 ); 42 } 43 44 int main() 45 { 46 dfs(0) ; 47 printf("%d\n",ans); 48 return 0 ; 49 }