进击高手【第三期】(dfs)

本文详细介绍了深度优先搜索(DFS)的基本概念、模板及其在全排列、组合、素数环、八皇后问题和马的遍历等经典问题中的应用。通过实例解析DFS的实现过程,并提供了相应的代码实现,帮助读者深入理解DFS算法。
摘要由CSDN通过智能技术生成

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;
}

你学废了吗?

end

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值