dfs
定义
dfs全称深度优先搜索,简称深搜。本质上是暴力把所有的路径都搜索出来,它运用了回溯,保存这次的位置并深入搜索,都搜索完便回溯回来,搜下一个位置,直到把所有最深位置都搜一遍(找到目的解返回或者全部遍历完返回一个事先定好的值)。要注意的一点是,搜索的时候有记录走过的位置,标记完后可能要改回来。dfs一般借用递归完成整个算法的构造。
模板
1.我更喜欢使用
void dfs(int u){
if (出局判断){//到头就走
做要做的事 一般为输出
} else {
for (int i = 开始的地方; i <= n; i++){//从上一个数开始依次增加,枚举每一种情况
if (used[i] == 0) {//判断是否用过
加入结果 设为用过
dfs(u + 1);//下一个数字
回溯:回到没用过
}
}
}
}
void dfs(int u){
for (int i = 开始的地方; i <= n; i++){//从上一个数开始依次增加,枚举每一种情况
if (出局判断){//到头就走
一般为输出
}
if (used[i] == 0) {//判断是否用过
加入结果 设为用过
dfs(u + 1);//下一个数字
回溯:回到没用过
}
}
}
例题
1.全排列
很简单,不多说,直接上代码!
void print(){
for(int i=1;i<=n;i++) printf("%5d",a[i]);//记得占五位
printf("\n");
}
void dfs(int step){
if(step==n+1){//到达答案
print();
} else {
for(int i=1;i<=n;i++){
if(vis[i]==0){//这个数没有使用过
vis[i]=1;//置为1
a[step]=i;
dfs(step+1);
vis[i]=0;//回溯
}
}
}
}
提醒:dfs记得从1开始
2.n 个数中取 r 个数的全排列
题目描述
设n个整数的集合{1,2,3,n},从中取出r个进行排序,输出排序结果。
输入一行:两个正整数 n 和 r (r<n<10)。
输出字典序从小到大的全排列及全排列的总个数。
样例输入
3 2
样例输出
1 2
1 3
2 1
2 3
3 1
3 2
6
我们只需在上面代码改改,便可通过。
void print(){
for(int i=1;i<=n;i++) printf("%d",a[i]);
printf("\n");
}
void dfs(int step){
if(step==r+1){//到达答案 将n改为r即可
print();
} else {
for(int i=1;i<=n;i++){
if(vis[i]==0){//这个数没有使用过
vis[i]=1;//置为1
a[step]=i;
dfs(step+1);
vis[i]=0;//回溯
}
}
}
}
3.素数环
题目描述
输入正整数n,把整数1,2,3,…,n组成一个环,使得相邻两个整数之和均为素数。
输入格式
第1行:2个整数,n(n<=18) 和 k(1<=k<=10)
第2行:共有k个从小到大排列的整数,表示要输出的解的编号。
输出格式
前k行,每行一组解,对应于一个输入。
第k+1行:一个整数,表示总的方案数。
样例输入
10 4
1 2 5 8
样例输出
1 2 3 4 7 6 5 8 9 10
1 2 3 4 7 10 9 8 5 6
1 2 3 8 5 6 7 10 9 4
1 2 3 10 9 8 5 6 7 4
96
这道题需要单独判素数,但在dfs中很浪费时间,所以可以打一个素数bool类型表。
如下
void prime(int x){
for(int i=2;i<=sqrt(x);i++){
if(x%i==0){
s[x]=false;
return;
}
}
s[x]=true;
}
或者真不是为了凑字数
void prime() {
is_prime[0] = is_prime[1] = true; is_prime[2] = false;
for (int i = 2; i <= 36; i++)
if (!is_prime[i])
for (int j = 2; j <= 36 / i; j++)
is_prime[i * j] = true;
} // 筛质数
当然也可使用打表
然后便可以放心的使用dfs模板,总代码如下
#include <bits/stdc++.h>
using namespace std;
int n, k, x[15], tot, q = 1;
int vis[20], a[20];
bool s[40];
void prime(int x) {
for (int i = 2; i <= sqrt(x); i++) {
if (x % i == 0) {
s[x] = false;
return;
}
}
s[x] = true;
}
bool isprime(int x) {
if (s[x]) return true;
else return false;
}//判读是否为素数
void print() {
for (int i = 1; i <= n; i++) printf("%d ", a[i]);
printf("\n");
}
void dfs(int step) {
if (step == n + 1 && isprime(a[n] + a[1])) {//注意他是一个环形,要判断首尾
tot++;//个数加1
if (tot == x[q]) {//正好为要寻找的数
q++;
print();
}
} else {
for (int i = 2; i <= n; i++) {
if (vis[i] == 0 && isprime(i + a[step - 1])) {
vis[i] = 1;
a[step] = i;
dfs(step + 1);
vis[i] = 0;
}
}
}
}
int main() {
scanf("%d %d", &n, &k);
for (int i = 1; i <= k; i++) {
scanf("%d", &x[i]);
}
for (int i = 2; i <= 36; i++) {
prime(i);
}//打出素数表
a[1] = 1, vis[1] = 1;//第一个数一定为1
dfs(2);
printf("%d", tot);
return 0;
}
提醒:记得从二开始
4.八皇后
我们先用一个图来理解皇后怎么走
所以我们需要判断4个行,列,左对角线,右对角线。
代码如下
#include<bits/stdc++.h>
using namespace std;
int h[8];
int a[16];//左对角线
int b[16];//右对角线
int v[8][8];//棋盘
int cont=0;
void print(){
cont++;
cout<<"No. "<<cont<<endl;
for(int i=0;i<8;i++){
for(int j=0;j<8;j++){
cout<<v[j][i]<<" ";
}
cout<<endl;
}
}
void dfs(int step){
for(int j=0;j<8;j++){
if(h[j]==0&&a[step+j]==0&&b[step-j+7]==0){
a[step+j]=1,b[step-j+7]=1,h[j]=1;//标记
v[step][j]=1;//放下皇后
if(step==7){
print();//有8个皇后便打印
} else{
dfs(step+1);
}
a[step+j]=0,b[step-j+7]=0,h[j]=0,v[step][j]=0;//回溯
}
}
}
int main(){
dfs(0);
return 0;
}
提醒:记得从0开始(根据程序而定)
5.马的遍历
题目描述
中国象棋半张棋盘4*8(包括0,0)。马自左下角往右上角跳。今规定只许往右跳,不许往左跳。
输出格式
第一行:0,0–>0,0–>2,1–>3,3–>1,4–>3,5–>2,7–>4,8
第二行:一个整数,表示第几种跳法。
注意,要输出两个 0,0!
样例输入
无
样例输出
0,0–>0,0–>2,1–>4,2–>3,4–>4,6–>2,7–>4,8
0,0–>0,0–>2,1–>4,2–>3,4–>1,5–>3,6–>4,8
……
…
总的跳法数
数据范围与提示
方向说明:x[4]={2,1,-1,-2},y[4]={1,2,2,1}
这是一个爬行类深搜,方向数组如上提示
#include <bits/stdc++.h>
using namespace std;
int vis[10][5];
int xx[4]={2,1,-1,-2},yy[4]={1,2,2,1};//方向数组
int tot;//个数
int a[10][2];//答案
void print(int n){
printf("0,0");
for(int i=0;i<n;i++){
printf("-->%d,%d",a[i][0],a[i][1]);
}
printf("\n");
}
void dfs(int x,int y,int step){
if(x==4&&y==8){
print(step);
tot++;
}
else {y
for(int i=0;i<4;i++){
int nx=x+xx[i];//更新x
int ny=y+yy[i];//更新y
if(nx>=0&&nx<=4&&ny>=0&&ny<=8&&vis[nx][ny]==0){//判断是否出界及是否用过
vis[nx][ny]=1;
a[step][0]=nx,a[step][1]=ny;//记录答案
dfs(nx,ny,step+1);
vis[nx][ny]=0;//回溯
}
}
}
}
int main(){
dfs(0,0,1);
printf("%d",tot);
return 0;
}
dfs(0,0,1)指的是从0,0开始,已经有一步了
6.正方形
题目描述
给定一组不同长度的木棍,是否有可能将它们端对端地连接起来形成 个正方形?
输入格式
第1行输入包含N,即测试数据的数量。 每组测试数据第一个数为 ,即木棒的根数。之后有 个整数, 每个都给出了一根棍子的长度 。
输出格式
对于每种情况,如果可以形成正方形,则输出 yes 或 no,每个结果占 行。
样例输入
3
4 1 1 1 1
5 10 20 30 40 50
8 1 7 2 6 4 4 3 5
样例输出
yes
no
yes
这道题很“简单” 我差点也不会
直接在代码内讲述
#include <bits/stdc++.h>
using namespace std;
int t,n,l[25],len;
bool f;//用于剪枝
bool vis[25];
void dfs(int x,int side,int side_len){//x:个数 side:剩余个数 side_len:剩余最长长度
if(side_len==len) side_len=0,side++,x=n;
if(f||side==4){//结束条件
f=1;
return;
}
for(int i=x;i>=1;i--){//一定为倒叙,如若从大到小,可以正序
if(!vis[i]&&side_len+l[i]<=len){
vis[i]=1;
dfs(i-1,side,side_len+l[i]);
if(f) return;//剪枝
vis[i]=0;
}
}
}
int main(){
scanf("%d",&t);
while(t--){
f=len=0;
memset(vis, 0, 25);//初始化 可以用循环
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&l[i]);
len+=l[i];//求总长度
}
sort(l+1,l+n+1);//排序也是用来剪枝
if(len&3||l[n]>len/4){//剪枝 len&3相当于len%4!=0
puts("no");
continue;
}
len>>=2;//相当于/4
dfs(n,1,0);//可有为n,0,0
if(f) puts("yes");
else puts("no");
}
return 0;
}
你学废了吗?