关于参照《算法笔记》学习DFS时的一些自身理解
首先DFS是一种“出栈入栈”思想。
到达分岔口时入栈,离开分岔口(即该分岔口处所有可走路径都已遍历完成)或者死胡同时出栈。
这里附上我觉得对我理解帮助比较大的一篇博客
递归是用来解决该类问题的有利工具
毕竟递归即是系统利用系统栈来存放递归的每一层状态。因此其本质还是栈。
在使用递归时,递归式即为分岔口,递归边界即为死胡同。
拿Fibonacci数列举例来说,F(0)与F(1)是递归边界,也即“死胡同”,F(n)=F(n-1)+F(n-2) (n>=2) 是递归式,那么F(n)就可理解为一个分岔口。
一类DFS常见解决方法:
将问题的一个解看为一个序列,枚举这个序列的所有子序列(事先对序列中可能
出现的所有元素指定一个顺序,那么对于每个次序上的元素都有“选取/不选取”两个分岔口)—— 从中根据需要找出某个特征最优的子序列。
其实当我们解决深度优先搜索问题时,首先应该分析问题找出“分岔口”与“死胡同”,那么当确定问题可以用这类方法解决时,“分岔口”便非常容易找到,即为每个元素“选取”或“不选取”,那么死胡同即为题目中的一些约束条件,比如最后结果中一共有几个元素,或者最后结果的和不能超过多少。当然在这个过程中我们也可以把符合条件的解看成一个死胡同,只不过此时不是直接返回,而是先对结果进行存储或者相关处理
例如对于序列{1,2,3}来说 (假设题目要求按递增顺序输出结果),那么其子序列有{1}、{2}、{3}、{1,2},{1,3}、{2,3}、{1,2,3}。
拿其中的{1}举例来说:也即在第一个分岔口“是否选取第一个元素1”中,选择了“选取”这条岔路,在第二个分岔口“是否选取第二个元素2”时,选择了“不选取”这条岔路,在第三个分岔口“是否选取第三个元素3”时,选择了“不选取”这条岔路,此时已经处理了最后一个元素,那么就到达了一个死胡同(因为现在没有其他限制),也就产生了一种可能的结果序列{1}。那么退回到上一个分岔口,也即“是否选择第三元素3”,此时便该走“选取”这条岔路,便产生了{1,3}这一序列。
PAT (Advanced Level) Practice 1103 Integer Factorization (30分)
(参照了一些算法笔记的思路以及自己的简化)
int n, k, p,maxn=0;
vector<int> temp, ans;
int facs[410] = { 0 };
/**
深度优先搜索,index代表当前正要对哪个元素进行抉择选取,sum1为当前结果中各元素的平方和,
sum2位当前结果中各元素的和
*/
void DFS(int index, int sum1, int sum2,int num)
{
if (sum1 == n && sum2 > maxn&&num==k) {
ans = temp;
maxn = sum2;
return;
}
//剪枝操作,符合条件时才继续向深处遍历
if (sum1 + facs[index] <= n&&num+1<=k) {
temp.push_back(index);
//由于一个子序列中一个元素可以重复多次,因此选取该元素后,下一个检验的还是该元素
DFS(index,sum1 + facs[index], sum2 + index,num+1);
temp.pop_back();
}
//不选取当前元素
if(index>1)
DFS(index - 1, sum1, sum2,num);
}
int main()
{
scanf("%d %d %d", &n, &k, &p);
int index;
//先提前算出每个数指定次方的结果
for (int i = 0;; i++) {
if (pow(i, p) <= n) {
facs[i] = pow(i, p);
index = i;
}
else
break;
}
//由于题目要求选择字典序最大的方案,那么从后往前遍历可以保证当出现两个结果序列元素值相等时,
//保留字典序大的方案
DFS(index, 0, 0,0);
if (ans.size() == 0)
printf("Impossible\n");
else
{
printf("%d=%d^%d", n, ans[0], p);
for (int i = 1; i < ans.size(); i++)
printf(" + %d^%d",ans[i], p);
}
}