DFS—深度优先搜索

递归函数代码形式

函数类型 函数名(形式参数):
	if(边界条件)
		边界处理
	else
		递推算法

1、斐波那契数列:

1 1 2 3 5 8 13 21 34 55 89 ...
已知前两项为1,之后每一项等于前两项之和。
现输入n,请输出兔子数列的第n项。

#include<bits/stdc++.h>
using namespace std;
int f(int n){
	if(n==1 || n==2)
		return 1;
	else	//else可省略,为什么? 
		return f(n-1)+f(n-2);
}
int main(){
	int n;
	cin>>n;
	cout<<f(n);
	return 0;
}

2、用递归法求n!的值。

F ( n ) = { 1 ( n = 0 ) n ∗ F ( n − 1 ) ( n > 0 ) F(n)=\begin{cases}1(n=0)\\n*F(n-1)(n>0)\end{cases} F(n)={1n=0nF(n1)n>0

不难分析出上题的递推式为 f a c ( n ) = f a c ( n − 1 ) ∗ n fac(n)=fac(n-1)*n fac(n)=fac(n1)n,边界条件为 f a c ( 0 ) = 1 fac(0)=1 fac(0)=1
求解过程如下:

f a c ( 4 ) = 4 ∗ f a c ( 3 ) = 4 ∗ ( 3 ∗ f a c ( 2 ) ) = 4 ∗ ( 3 ∗ ( 2 ∗ f a c ( 1 ) ) ) = 4 ∗ ( 3 ∗ ( 2 ∗ ( 1 ∗ f a c ( 0 ) ) ) ) = 4 ∗ ( 3 ∗ ( 2 ∗ ( 1 ∗ 1 ) ) ) fac(4)\\ =4*fac(3)\\ =4*(3*fac(2))\\ =4*(3*(2*fac(1)))\\ =4*(3*(2*(1*fac(0))))\\ =4*(3*(2*(1*1))) fac(4)=4fac(3)=4(3fac(2))=4(3(2fac(1)))=4(3(2(1fac(0))))=4(3(2(11)))

题解:

#include<bits/stdc++.h>
using namespace std;
long long f(long long n){
	if(n==1)
		return 1;
	else
		return n*f(n-1);
}
int main(){
	long long n;
	cin>>n;
	cout<<f(n);
	return 0;
}

3、用递归法求f(n)=1+2+3+…+n的值。

#include<bits/stdc++.h>
using namespace std;
int f(int n){
	if(n==1)
		return 1;
	else
		return n+f(n-1);
}
int main(){
	int n;
	cin>>n;
	cout<<f(n);
	return 0;
}

4、输入两个数,求其最大公约数。

根据上文中所讲的辗转相除法可得公式如下:
g c d ( a , b ) = { ( a % b = 0 ) g c d ( b , a % b ) ( a % b ≠ 0 ) gcd(a,b)=\begin {cases}(a\%b=0)\\gcd(b,a\%b)(a\%b\neq 0)\end{cases} gcd(a,b)={(a%b=0)gcd(b,a%b)a%b=0

#include<bits/stdc++.h>
using namespace std;
int f(int n,int m){
	if(n%m==0)
		return m;
	else
		return f(m,n%m);//该处return去除亦可正常运行,为什么? 
}
int main(){
	int n,m;
	cin>>n>>m;
	cout<<f(n,m);
	return 0;
}

5、跳台阶

花果山上有一洞,小猴每次采取跳1阶或者跳3阶的办法从山下跳跃上台阶进洞,编程输入台阶数,输出有多少种不同的跳法。

#include<bits/stdc++.h>
using namespace std;
int f(int n){
	if(n==0 || n==1 || n==2)
		return 1;
	else
		return f(n-1)+f(n-3); 
}
int main(){
	int n;
	cin>>n;
	cout<<f(n);
	return 0;
}

6、输出全排列

给定N(N<10),按照字典序输出所有的N排列
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

可以采用1~n循环填每一个空的思路来做,也符合题意的按字典序填数,每填好一个数后继续递归填下一个数

#include<bits/stdc++.h>
using namespace std;
const int N=15;
int n,a[N];

void f(int x){//函数f(x)定义:确定函数的第x个数
	if(x==n+1){
		for(int i=1;i<=n;i++)
			cout<<a[i]<<' ';
		cout<<endl;
		return;
	} else{
		for(int i=1;i<=n;i++){ //枚举第x个数的值 
			bool ok=true;
			for(int j=1;j<x;j++)//判断i是否在前x-1个数中出现过
				if(a[j]==i){ok=false;break;}
			if(ok){
				a[x]=i;
				f(x+1);
				a[x]=0;//执行完f(x+1)后会退回该处,恢复现场,亦可不写,因为新的赋值会把原值覆盖掉 
			}
		}
	}
}

int main(){
	cin>>n;
	f(1);
	return 0;
}

上述代码中“判断i是否在前x-1个数中出现过”使用了枚举法,导致代码效率不高,我们可以采用 used[] 数组来进行优化,i 被使用过则将 used[i] 记为true,否则为false

#include<bits/stdc++.h>
using namespace std;
const int N=15;
int n,a[N];
bool used[N]; 
void f(int x){//函数f(x)定义:确定函数的第x个数
	if(x==n+1){
		for(int i=1;i<=n;i++)
			cout<<a[i]<<' ';
		cout<<endl;
		return;
	} else{
		for(int i=1;i<=n;i++){ //枚举第x个数的值 
			if(!used[i]){
				a[x]=i;
				used[i]=true;
				f(x+1);
				used[i]=false;//执行完f(x+1)后会退回该处,恢复现场,必须得写, 
				a[x]=0;//可写可不写,递归经常会出现类似的对称型代码 
			}
		}
	}
	
}
int main(){
	cin>>n;
	f(1);
	return 0;
}

全排列是一种常见的组合算法,也经常通过不断交换数组中的元素的方式来生成全排列。在每一次递归中,我们将第一个元素与后面的元素进行交换,然后递归生成后面的排列,最后再恢复交换的元素。通过这种方式,我们可以生成所有可能的排列(非字典序)。

#include<bits/stdc++.h>
using namespace std;
int a[15];

// 交换两个元素的值
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 递归生成全排列
void f(int *a, int start, int end) {
    if (start == end) {
        // 打印当前排列
        for (int i=1;i<=end;i++) {
            cout<<a[i]<<' ';
        }
        cout<<endl;
    } else {
        for (int i=start;i<=end;i++) {
            // 交换第一个元素与后面的元素
            swap(&a[start], &a[i]);
            // 递归生成后面的排列
            f(a,start+1,end);
            // 恢复交换的元素
            swap(&a[start], &a[i]);
        }
    }
}

int main() {
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		a[i]=i;
	}
    f(a,1,n);
    return 0;
}

7、汉诺塔

Hanoi塔由n个大小不同的圆盘和三根木柱a,b,c组成。开始时,这n个圆盘由大到小依次套在a柱上,如图所示。 

这里写图片描述

要求把a柱上n个圆盘按下述规则移到c柱上: 
  (1)一次只能移一个圆盘; 
  (2)圆盘只能在三个柱上存放; 
  (3)在移动过程中,不允许大盘压小盘。 
问将这n个盘子从a柱移动到c柱上,总计需要移动多少个盘次? 

【问题解答】

解:设Hn为n个盘子从a柱移到c柱所需移动的盘次。

    显然,当n=1时,只需把a 柱上的盘子直接移动到c柱就可以了,故:H1=1。

    当n=2时,先将a柱上面的小盘子移动到b柱上去,然后将大盘子从a柱移到c柱,最后,将b柱上的小盘子移到c柱上,共记3个盘次,故:H2=3。

    以此类推,当a柱上有n(n>=2)个盘子时,总是先借助c柱把上面的n-1个盘子移动到b柱上,然后把a柱最下面的盘子移动到c柱上,再借助a柱把b柱上的n-1个盘子移动到c柱上,总共移动H(n-1)+1+H(n-1)个盘次。 

    ∴Hn=2H(n-1)+1,边界条件:H1=1

【递归实现】

#include<stdio.h>
void move(int n, char x, char y, char z)//将n个圆盘从x柱子上借助y柱子移动到z柱子上
{
     if(n == 1)
     	printf("圆盘编号 %d :从 %c 移动到 %c\n",n,x,z);
     else
     {
         move(n-1,x,z,y);
         printf("圆盘编号 %d:从 %c 移动到 %c\n",n,x,z);
         move(n-1,y,x,z);
      }
 }
int main()
{
    int n;//n代表圆盘的个数
    /*A,B,C分别代表三个柱子*/
    char ch1 = 'A';
    char ch2 = 'B';
    char ch3 = 'C';
 
     printf("请输入圆盘的个数:");
     scanf("%d",&n);
     move(n,ch1,ch2,ch3);
     return 0;
}

【递推实现】

#include<stdio.h>
int ct=1;//记录步数,在步骤中输出
void move(int n,char from,char to)
{
    printf("第 %2d 步:把第 %d 个盘子:  %c >>>>>>> %c\n",ct++,n,from,to);
}
int hanoi(int n)//输出步数:
{
    int cnt = 2,ans = 1;
    if(n == 1)
        return 1;
    else
        return 2* hanoi(n-1) +1;
}
void hanoi_tower(int n,char x,char y, char z) //输出步骤
{
    if(n==1)
        move(1,x,z);
    else{
        hanoi_tower(n-1,x,z,y);
        move(n,x,z);
        hanoi_tower(n-1,y,x,z);
    }
}
int main()
{
    int n;//盘子个数
    printf("输入盘子个数:\n");
    scanf("%d",&n);
    char x = 'A',y = 'B',z = 'C';
    int t = hanoi(n);
    printf("一共需要%2d步。\n",t);
    hanoi_tower(n,x,y,z);
    return 0;
}

8、n皇后问题

在N*N(N<=10)的棋盘上放置N个皇后,使得他们不能相互攻击。两个皇后能相互攻击当且仅当他们在同一行,或者同一列,或者同一对角线上。

【题解】来源:WBN

DFS基本格式:

#include<iostream>
using namespace std;
void dfs(int x){//层数
    if(达到目的) 输出(或者方案数+1else{
    	for(遍历所有的算符){
    		if(没有用到){
    			标记
    			dfs(k+1)//下一层递归
    			回溯
    		}
    	}
    }
}

在这道题目中,“层数”可以等于“行”,即第x层递归可以等价于第x行,那么只需要检验对角线以及列数。又因为行数已经可以忽略不考虑(一定符合条件),我们可以用一维数组(p[11])存放皇后.

1.检验列,只需判断p[j]是否与已经放入的“皇后”位置不同,即p[j]!=i;
2.检验对角线,只需要判断“皇后”的行列之差(的绝对值)是否相等

#include<bits/stdc++.h>
using namespace std;
int n,ans,p[11];//一维数组p用来记录列 
 
void Nqueen(int x){ 
	if(x==n+1){//边界条件 即此时已经找完 计算方案数 
		ans++;
		return;//返回函数 
	}
	else{
		for(int i=1;i<=n;i++){
			bool ok=true;
			for(int j=1;j<x;j++){//检查第x列之前 
				if((p[j]==i)||(abs(x-j)==abs(i-p[j]))){//第一部分判断皇后是否在同一列 第二部分判断是否在同一对角线 
					ok =false;
					break;
				}
			}
			if(ok){
				p[x]=i;//放入 
				Nqueen(x+1);//下一列 
				p[x]=0;
			}
		}
	}
}
 
int main(){
	cin>>n;
	Nqueen(1);//从第一行找起 
	cout<<ans;
	
	return 0;
}

9、确定进制

6*9=42对于十进制来说是错误的,但是对于13进制来说是正确的,即6(13)*9(13)=42(13)
现读入三个整数p q r,然后确定一个进制,使得 p *q =r
输入样例:
6 9 42
输出样例:
13

10、最大连续子序列和

给定一个长度为n的序列,求该序列的最大连续子序列和。(n<=100000,|元素值|<=1000)
输入样例:
10
2 5 -3 4 -9 -2 6 1 -8 7
输出样例:
8

【题解】来源:PJD
1.暴力搜索,没什么好说的,纯写了玩

#include<bits/stdc++.h>
using namespace std;
 
int main()
{
	int n,max,op,l=0;
	cin>>n;
	int a[n+1];
	for(int i=1;i<=n;i++)
	cin>>a[i];
	max=a[1];
	for(int i=1;i<=n;i++)
	{
		for(int j=n;j>=i;j--)
		{
			for(int o=i;o<=j;o++)
			{
				op+=a[o];
			}
			if(op>max)
			{
				max=op;
			}
		}
	}
	cout<<max;
	return 0;
}

2.DP

我们以洛谷里的测试样例为例

2 -4 3 -1 2 -4 3
第一个数:2

前两个数最大子段和:显然2-4<2 所以为2

前三:各个子段和分别为1,-1,3(注意,这里的子段是指包含第3个数的子段)

最大为3

在前三个的情况时,我们可以发现,因为前两个数的和为负数,加上第三个数反而没有第三个数大

所以可以很清楚发现状态转移方程为发f(n)=max(f(n-1)+h(n),h(n)) (f(n)指的是第1到n中所有包含第n个数的子段中的最大值,h(n)指的是第n个数的值。

综上,我们可以用简单的DP找出第1到n中所有包含第n个数的子段中的最大值

但很明显,不包括第n个数的子段中可能有字段和更大的情况,例如:

1 2 3 4 -1000 1

如果直接求出f(6),结果为1,但很明显,答案应该是1+2+3+4=10

所以我们可以存储f(1)到f(n),找其中的最值即可

以下是两种编码方式

:第一种,制表递推

#include<bits/stdc++.h>
using namespace std;
 
int o[200005],p[200005];//o存数列,p为表 
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) 
	cin>>o[i];
	int op=o[1];//op来存最大值,自底向上制表递推
	for(int i=1;i<=n;i++)
	{
		p[i]=max(p[i-1]+o[i],o[i]);
	} 
	for(int i=1;i<=n;i++)
	{
		op=max(op,p[i]);
	} 
	cout<<op;
	return 0;
}

可以发现制表,找最值的过程可以合在一个循环里

#include<bits/stdc++.h>
using namespace std;
 
int o[200005],p[200005];//o存数列,p为表 
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) 
	cin>>o[i];
	int op=o[1];//op来存最大值,自底向上制表递推
	for(int i=1;i<=n;i++)
	{
		p[i]=max(p[i-1]+o[i],o[i]);
		op=max(op,p[i]);
	}
	cout<<op;
	return 0;
}

DP第二种,自顶向下递归。其实就是把递推式里东西换了个位置。

#include<bits/stdc++.h>
using namespace std;
 
int o[200005],p[200005],op;//o存数列,p记忆化 
int ys(int bh)
{
	if(bh==1)
	return o[1];
	else
	return max(ys(bh-1)+o[bh],o[bh]);
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) 
	cin>>o[i];
	op=o[1];//op来存最大值
	for(int i=1;i<=n;i++)
	{
		p[i]=ys(i);
		op=max(op,p[i]);
	}
	cout<<op;
	return 0;
}

11、最大子矩阵

已知矩阵的大小等于矩阵中所有元素的和,给定一个nn(n<=300)的矩阵,找到该矩阵的最大非空子矩阵(大小至少为11)。
输入样例:
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
输出样例:
15

【题解】

#include <bits/stdc++.h>
using namespace std;

int n, a[305][305], answer, sum[305][305];

pair<int, int> vis[305][305];

void dfs(const int x, const int y, int s, int px, int py){
	if(px > n || py > n || vis[px][py] == make_pair(x, y)) return;
	vis[px][py] = make_pair(x, y);
	sum[px][py] = s;
	int w = 0;
	for(int t = x; t <= px; t++){
		s += a[t][py + 1];
		w += a[t][py + 1];
	}
	dfs(x, y, s, px, py + 1);
	s -= w;
	w = 0;
	for(int t = y; t <= py; t++){
		s += a[px + 1][t];
		w += a[px + 1][t];
	}
	dfs(x, y, s, px + 1, py);
	s -= w;
	answer = max(s, answer);
}

void dfs2(const int x, const int y, int s, int px, int py){
	if(px > n || py > n || vis[px][py] == make_pair(x, y)) return;
	vis[px][py] = make_pair(x, y);
	dfs2(x, y, sum[px][py + 1] + sum[x - 1][y - 1] - sum[x - 1][py + 1] - sum[px][y - 1], px, py + 1);
	dfs2(x, y, sum[px + 1][py] + sum[x - 1][y - 1] - sum[x - 1][py] - sum[px + 1][y - 1], px + 1, py);
	answer = max(s, answer);
}

int main(){
	cin >> n;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			cin >> a[i][j];
		}
	}
	dfs(1, 1, a[1][1], 1, 1);
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			if(i == 1 && j == 1) continue;
			dfs2(i, j, a[i][j], i, j);
		}
	}
	cout << answer << "\n";
	return 0;
}
/*
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
*/

12、素数环

从1~20一共20个数摆成一个环,要求相邻两个数的和是一个素数。输出一个合法答案。
无输入
输出样例:
一行,20个数,表示一个合法解。

【题解】来源:PWT

#include<bits/stdc++.h>//素数环 
using namespace std;
int a[25];
bool f[25];
void print(){//输出函数 
	for(int i=1;i<=20;i++){
		cout<<a[i]<<" ";
	}
	cout<<endl;
}
bool zs(int x){//判断质数 
	if(x==1)return 0;
	for(int i=2;i<=sqrt(x);i++){
		if(x%i==0)return 0;
	}
	return 1; 
}
void dfs(int k){//k是第 k个数字 
	for(int i=1;i<=20;i++){
		if(f[i]==0&&(k==1||zs(i+a[k-1]))){//判断数是否已被占用,如果是第一个数不需要判断和为素数 
			a[k]=i;f[i]=1;//标记数被占用 
			if(k==20&&zs(a[k]+a[1]))print();//20个数已满且首尾相加为素数输出 
			else dfs(k+1);
			f[i]=0;
		}
	}
} 
int main(){
	dfs(1);
    return 0;
}

13、四阶数独

#include<bits/stdc++.h>
using namespace std;
#define size 5
int a[size*size],n=4*4,ans=0;
int b1[size][5],b2[size][5],b3[size][5]; //分别记录行、列、四小块
void dfs(int x){//第x个空填什么 
	if(x>16){//如果所有空已经填满
		ans++;//增加结果数量
		/*输出放置方案
		for(int i=1;i<=n;i++){
				printf("%d ",a[i]);
				if(i%4==0) puts("");
		}
		puts("");*/
		return;
	}
	int row=(x-1)/4+1;//行编号
	int col=(x-1)%4+1;//列编号
	int block=(row-1)/2*2+(col-1)/2+1;//小块编号
	for(int i=1;i<=4;i++)
		if(b1[row][i]==0&&b2[col][i]==0&&b3[block][i]==0){
			a[x]=i;//放置
			b1[row][i]=1;b2[col][i]==1;b3[block][i]=1;//占位
			dfs(x+1);//下一层递归
			b1[row][i]=0;b2[col][i]==0;b3[block][i]=0;//取消占位 
			if(x<=4)break;
		}
	//函数运行结束后也会自动回到上一层 
} 
int main(){
	dfs(1);
	printf("%d",ans*24);
	return 0;
} 

/*判断元素属于第几块
	int num;
	if((x-1)%4<=1&&x<=6)//分类讨论
	num=1; 
	else if((x-1)%4<=1&&x<=14)
	num=3;
	else if((x-1)%4>1&&x<=8)
	num=2;
	else
	num=4; 
*/
//遇到了需要枚举排序的时候,搜索回溯会很好用。不需要生成所有的序列全排列,而是一个一个地填空,保证填空的时候序列是合法的,
//这样就可以不用枚举很多无效序列,节约程序运行时间 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

以太以北

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值