【C++】回溯算法基础入门

回溯算法是一种通过试探来解决问题的算法,它尝试分步的做出选择,如果在某一步发现错误,则退回一步重新选择,直至找到所有可能的解决方案。本文详细介绍了回溯算法的概念、框架,并给出了包括打靶问题、全排列、工作分配、回文数、0/1背包、放棋子、数的划分、n皇后问题、迷宫问题和配对问题在内的多个应用实例,帮助读者深入理解回溯算法的原理和应用。

简介

  • 回溯算法基于深度优先搜索,实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
  • 由于非递归式回溯算法较难实现,本文只介绍递归式回溯。

回溯算法框架

int a[n+1];//这里用下标1~n
void DFS(int i){
   
   //搜索第i层
	if(i>n){
   
   //搜索结束
		//结果处理(输出结果,方案计数等)
	}
	for(int j=下界;j<=上界;j++){
   
   
		if(限界函数&&约束条件){
   
   //满足限界函数和约束条件
			x[i]=j;//保存当前层的结果
			...//回溯入场准备工作
			DFS(i+1);//搜索下一层
			...//回溯退回清理工作
		}
	}
}

例题

打靶问题

题目描述

一个人打 10 10 10次靶(范围在 0 0 0环到 10 10 10环),问这 10 10 10次打靶之后,共中 90 90 90环的情况的个数。

输入格式

输出格式

输出中 90 90 90环的个数。

样例

参考题解

#include <iostream>
using namespace std;
int cnt=0;
void DFS(int k,int v){
   
   //k表示打第i次靶,v表示前i-1次靶得分 
    if(k==10){
   
   
        if(v+10>=90)//第10环满分,总分大于等于90说明有解 
            cnt++;//结果+1 
        return;
    }
    for(int i=0;i<=10;i++){
   
   //枚举第i次打靶得分 
    	if(v+i+10*(10-k)>=90){
   
   //如果后面几次都得满分达到90分才有解 
    		DFS(k+1,v+i);//搜索k+1层,更新得分v+i 
		}
    }
}
int main(){
   
   
    DFS(1,0);
    cout<<cnt;//输出方案数 
    return 0;
}

全排列1

题目描述

n n n个不同元素中任取 m ( m ≤ n ) m(m≤n) m(mn)个元素,按照一定的顺序排列起来,叫做从 n n n个不同元素中取出 m m m个元素的一个排列。

输入格式

一行一个正整数 n n n.

输出格式

输出所有排列,每个数字之间以空格隔开,每个结果之间以换行隔开。

样例输入

3

样例输出

1 1 1
1 1 2
1 1 3
1 2 1
1 2 2
1 2 3
1 3 1
1 3 2
1 3 3
2 1 1
2 1 2
2 1 3
2 2 1
2 2 2
2 2 3
2 3 1
2 3 2
2 3 3
3 1 1
3 1 2
3 1 3
3 2 1
3 2 2
3 2 3
3 3 1
3 3 2
3 3 3

数据范围与提示

1 ≤ n ≤ 7 1\le n \le 7 1n7

参考题解

#include <iostream>
using namespace std;
int x[8],n;
void DFS(int k){
   
   //k表示第k个数的搜索 
    if(k>n){
   
   //搜索结束,输出x[]数组 
        for(int i=1;i<=n;i++){
   
   
        	printf("%d ",x[i]);
		}
		printf("\n");
		return;
    }
    for(int i=1;i<=n;i++){
   
   //枚举x[k] 
    	x[k]=i;//标记x[k]=i 
    	DFS(k+1);//搜索下一层 
	}
}
int main(){
   
   
	cin>>n;
    DFS(1);
    return 0;
}

全排列2

题目描述

1 ∼ n 1\sim n 1n这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式

一个整数 n n n

输出格式

按照从小到大的顺序输出所有方案,每行 1 1 1个。
首先,同一行相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

输入样例

3

输出样例

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

数据范围与提示

1 ≤ n ≤ 9 1\le n \le 9 1n9

参考题解

#include <iostream>
#include <algorithm>
using namespace std;
int x[10],visit[10],n;
void DFS(int k){
   
   //k表示第k个数的搜索 
    if(k>n){
   
   //搜索结束,输出x[]数组 
        for(int i=1;i<=n;i++){
   
   
        	printf("%d ",x[i]);
		}
		printf("\n");
		return;
    }
    for(int i=1;i<=n;i++){
   
   //枚举x[k]
    	if(!visit[i]){
   
   //如果i未被访问 
    		x[k]=i;//标记x[k]=i
    		visit[i]=1;//i标记1表示访问了 
    		DFS(k+1);//搜索下一层
    		visit[i]=0;//回溯回退,标记i为0表示访问 
		} 
	}
}
int main(){
   
   
	fill(visit,visit+10,0);//标记visit为0表示未访问 
	cin>>n;
    DFS(1);
    return 0;
}

组合的输出

题目描述

排列与组合是常用的数学方法,其中组合就是从 n n n个元素中抽出 r r r个元素(不分顺序且 r ≤ n r\le n rn),我们可以简单地将n个元素理解为自然数 1 , 2 , … , n 1,2,\dots ,n 1,2,,n,从中任取 r r r个数。
现要求你用递归的方法输出所有组合。
例如 n = 5 , r = 3 n=5,r=3 n=5,r=3,所有组合为:

1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5 

参考题解

#include <iostream>
#include <algorithm>
using namespace std;
int x[21],visit[21],n,r;
void DFS(int k){
   
   //k表示第k个数的搜索 
    if(k>r){
   
   //搜索结束,输出x[]数组 
        for(int i=1;i<=r;i++){
   
   
        	printf("%d ",x[i]);
		}
		printf("\n");
		return;
    }
    for(int i=x[k-1]+1;i<=n;i++){
   
   //枚举x[k],输出组合数不能有重复,当x[i]>x[i-1]时保证无重复
    	if(!visit[i]){
   
   //如果i未被访问 
    		x[k]=i;//标记x[k]=i
    		visit[i]=1;//i标记1表示访问了 
    		DFS(k+1);//搜索下一层
    		visit[i]=0;//回溯回退,标记i为0表示访问 
		} 
	}
}
int main(){
   
   
	x[0]=0;//为了方便下界使用,见DFS第二个for循环
	fill(visit,visit+21,0);//标记visit为0表示未访问 
	cin>>n>>r;
    DFS(1);
    return 0;
}

排列的生成

题目描述

给出 n n n m m m,请编程按字典序输从 1 , 2 , … , n 1,2,\dots ,n 1,2,,n中选择 m m m个数的所有排列。

输入格式

一行包含两个整数 n , m n,m n,m,两个整数之间用一个空格分开。

输出格式

按字典序输出所有可能的排列,每个排列输出一行,元素之间用一个空格分开。

样例输入

3 2

样例输出

1 2
1 3
2 1
2 3
3 1
3 2

数据范围与提示

1 ≤ m ≤ n ≤ 11 1\le m \le n \le 11 1mn11

#include <iostream>
#include <algorithm>
using namespace std;
int visit[12];//标记访问 
int num[12];//标记存储 
int n,m;//n的m排列 
void DFS(int k){
   
   
    if(k==m+1){
   
   //输出当前方案 
        for(int i=1;i<=m;i++){
   
   
            cout<<num[i]<<' ';
        }
        cout<<endl;
        return;
    }
    for(int i=1;i<=n;i++){
   
   //查找可选值 
        if(!visit[i]){
   
   //可选 
            visit[i]=1;//标记已选 
            num[k]=i;//记录已选 
            DFS(k+1);//搜索下一层 
            visit[i]=0;//回退,标记未选 
        }
    }
}
int main(){
   
   
    cin>>n>>m;
    fill(visit,visit+12,0);//访问标0 
    DFS(1);//调用回溯 
    return 0;
}

工作分配

题目描述

n n n项工作要分配给 m m m个人完成,每个人只能从事一项工作,且每项工作只能由一人完成。已知第 i i i个人完成第 j j j项工作的工费是 c [ i ] [ j ] c[i][j] c[i][j]元,那么怎么给每个人分配工作才能使得总工费最小。

输入格式

一个整数 n n

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cout0

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

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

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

打赏作者

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

抵扣说明:

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

余额充值