一、基本思想
不撞南墙不回头
- 为了求得问题的解,先选择某一种可能情况向前探索;
- 在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索;
- 如此反复进行,直至得到解或证明无解。
二、算法原理
深度优先搜索(Depth First Search),是图遍历算法的一种。用一句话概括就是:"一直往下走,走不通回头,换条路再走,直到无路可走"。
假设我们有一个二叉树,共有10个节点,以下是DFS的简单示范:
从根节点开始向下搜索
然后搜索到2号节点
继续不断向深层处的节点搜索,搜索到4号节点
最后搜索到7号节点
当搜索到7号节点后,我们发现无路可走了,因为7号节点是当前这条路径下最深处的节点,因此,我们需要进行回溯操作
当回溯到4号节点时,我们发现4号节点并没有另一条路,也就是说从4号节点向下搜索的话,只能搜索到7号节点,但是可是刚刚才从7号节点回溯上来诶,我们总不可能又搜索到7号,然后又回溯到4号无限下去吧......所以,我们得再次回溯,也就是跳到2号节点上。
当再次跳到2号节点上时,我们发现从2号节点开始,还有另一条路可以走。那我们就走下去!
此时又有两条路可以走,我们先去往8号
走到8号,我们发现又走到头了,那就再对它使用回溯吧!!!
这次我们选择另一条路,走到9号
然后我们发现又双走到头了,因此,再次回溯,从9号跳到5号,再跳到2号,然后再跳到1号(因为5号,2号向下的路我们已经走过了,但我们发现1号节点向下的路还有一条是我们没走过的)
接下来搜索类似,我们走到3号,然后走到6号,然后走到10号
当10号走完后,这颗树的每个节点都被搜索过了,最后回溯到根节点
三、模板
1、C模板
int a[510]; //存储每次选出来的数据
int book[510]; //标记是否被访问
int ans = 0; //记录符合条件的次数
void DFS(int cur){
if(cur == k){ //k个数已经选完,可以进行输出等相关操作
for(int i = 0; i < cur; i++){
printf("%d ", a[i]);
}
ans++;
return ;
}
for(int i = 0; i < n; i++){ //遍历 n个数,并从中选择k个数
if(!book[i]){ //若没有被访问
book[i] = 1; //标记已被访问
a[cur] = i; //选定本数,并加入数组
DFS(cur + 1); //递归,cur+1
book[i] = 0; //释放,标记为没被访问,方便下次引用
}
}
}
2、C++模板
vector<int> a; // 记录每次排列
vector<int> book; //标记是否被访问
void DFS(int cur, int k, vector<int>& nums){
if(cur == k){ //k个数已经选完,可以进行输出等相关操作
for(int i = 0; i < cur; i++){
printf("%d ", a[i]);
}
return ;
}
for(int i = 0; i < k; i++){ //遍历 n个数,并从中选择k个数
if(book[nums[i]] == 0){ //若没有被访问
a.push_back(nums[i]); //选定本输,并加入数组
book[nums[i]] = 1; //标记已被访问
DFS(cur + 1, n, nums); //递归,cur+1
book[nums[i]] = 0; //释放,标记为没被访问,方便下次引用
a.pop_back(); //弹出刚刚标记为未访问的数
}
}
}
四、例题(持续更新)
1、例题一(全排列)
设有n个整数的集合{1,2,...,n},从中取出任意r个数进行排列(1<=r<n<=10),试列出所有的排列。
示例:
输入:n = 4, r = 3
输出:
1 2 3
1 2 4
1 3 2
1 3 4
1 4 2
1 4 3
2 1 3
2 1 4
2 3 1
2 3 4
2 4 1
2 4 3
3 1 2
3 1 4
3 2 1
3 2 4
3 4 1
3 4 2
4 1 2
4 1 3
4 2 1
4 2 3
4 3 1
4 3 2
24
思路:
1)定义两个数组 a[] 与 book[] ,其中数组a保存每次的排列数据,数组book用来标记 i 这个数是否被访问;
2)初始化相关数据;
3)递归填数并判断第i个数填入是否合法:
合法:填数,并判断是否已经到达环的终点。如果到达终点,打印结果;否则,继续填下一个 数;
不合法:选择下一种可能。
代码:
#include<iostream>
using namespace std;
int n, r, ans; //r个数进行全排列 ans为排列个数
int book[510]; //标记是否被访问
int a[510]; //记录每次的排列数据
void DFS(int cur){ //从{1,2,...,n}中取r个数构成的排列
if(cur == r){ //已经去够r个数
for(int i = 0; i < cur; i++){ //循环输出
cout << a[i] << ' ';
}
cout << endl;
ans++; //数量加1
return ;
}
for(int i = 1; i <= n; i++){ //循环遍历保证不漏
if(!book[i]){ //若没访问过
book[i] = 1; //标记已访问
a[cur] = i; //i符合条件加入
DFS(cur + 1); //寻找一个数字
book[i] = 0; //回溯:清除标记
}
}
}
int main(){
cin >> n >> r;
DFS(0);
cout << ans << endl;
return 0;
}