概述
视频讲解地址
一、DFS的框架,必须熟记
void dfs(int k)
{
if(到达终点或者目的地)
{
输出问题解或者解得方案数+1;
}
for(int i = 0;i<="可扩展状态总数";i++)
{
按照规则,产生新的状态;
if(判断新状态是否合法)
{
保存合法结果;
dfs(k+1);
恢复现场,回溯;//可以没有,根据情况来定
}
}
}
int main()
{
.....
dfs(k);
return 0;
}
回溯法是DFS的一种搜索策略;那么它和DFS有什么区别呢?我们在应用的时候应该怎么辨别是否需要回溯呢?
听视频讲解吧,文字太难表述了!
回溯法是DFS的一种搜索策略
回溯法可以解决如下几种问题:
排列问题:N个数按照一定规则全排列,有几种排列方式
组合问题:如何按照一定规则在N个数中找出K个数的集合
切割问题:一个字符串按照一定规则切割,有几种切割方式
子集问题:一个N个数的集合中有多少符合条件的子集
棋盘问题:N皇后、数独等问题
二、排列组合类DFS
例1 全排列:
输出1…n(n<100)的全排列。 输入:n 输出:输出全排列,每个排列换行
样例输入:
3
样例输出:
1 2 3
1 3 2
21 3
2 3 1
3 1 2
3 2 1
【分析】
首先,需要一个数组来保存已经产生的排列;我们用int a[102];
接下来根据上面的搜索框架来构造该题的代码框架;
int n;
void dfs (int k)
{
if(k==n+1)
{
输出数组a[];
}
for(int i=1;i<=n;i++)
{
if(i是可用的)
{
a[k]=i;//保存当前的结果
dfs(k+1);//继续搜索第k+1个数
恢复现场,回溯;
}
}
}
int main()
{
cin >> n;
dfs(1);
return 0;
}
我们来解决这些问题:
1、输出数组a[],for(int i= 1;i <=n;i++) cout <<a[i]<<" ";
2、如何判定产生的i是否可用,也就是i是否已经出现过,两种方式:
(1)我们可以构造一个judge(int x,int y);来判断1-(x-1)的位置是否出现过y;
bool judge(int x,int y)
{
bool flag = true;
for(int i = 1;flag && i < x;i++)
{
if(a[i]==y) flag = false;
}
return flag;
则“i是可用的”就可以使用上述函数来代替了if(judge(k,i);这时候我们不需要恢复现场的,也就是没有回溯过程。
(2)使用标记数组vist[102];来记录1…n是否被使用过了,具体完整代码如下:
强烈推荐这种方式,比较通用!
#include<iostream>
using namespace std;
int a[102];
int vist[102]={0};
int n;//两个函数公用n,要使用全局变量。
void dfs(int k)
{
if(k==n+1)//已经放到n+1位置,说明已经完成任务
{
for(int i = 1;i <= n;i++) cout<<a[i]<<" ";
cout<<endl;
}
for(int i = 1;i <=n;i ++)
{
if(vist[i]==0)//i可以使用
{
a[k]=i;
vist[i]=1;//标记i已经被使用过了
dfs(k+1);//继续搜索第k+1个数
vist[i]=0;//回溯,把已经使用的i退回去
}
}
}
int main()
{
cin >> n;
dfs(1);
return 0;
}
扩展思维:
1、如果给定的数组a[]中进行全排列,代码应该如何改呢?自己试着实现以下。
#include<iostream>
using namespace std;
const int N = 10;
int path[N];
int vis[N]={ 0 };
int n;
int a[N];
void dfs(int k)
{
if(k==n+1)
{
for(int i= 1;i <=n;i++)
{
cout<< path[i]<<" ";
}
cout<<endl;
return ;
}
for(int i = 1;i <=n;i++)
{
if(!vis[i])
{
path[k]=a[i];//注意此处的变化
vis[i]=1;
dfs(k+1);
vis[i]=0;
}
}
}
int main()
{
cin >> n;
for(int i = 1;i <=n ;i++) cin >> a[i];//注意该代码是什么意思
dfs(1);
return 0;
}
排序和组合的本质区别在于是否有序。
体现在代码阶段:
那么既然是⽆序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,⽽不是从0开始!
例2、组合输出
从n个元素中抽出r个元素(不分顺序且r≤n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。
现要求你用DFS的方法输出所有组合。
例如
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<cstdio>
#include<cstdlib>
#include<cstring>
#define N 30
using namespace std;
int n,r;
int a[N];
int vis[N];
void dfs(int step)
{
int i;
if(step==r+1)
{
for(i=1;i<=r;i++)
cout<<" "<<a[i];
cout<<endl;
return;
}
for(i=a[step-1];i<=n;i++)//从当前已经选择的位置向后选
{
if(vis[i]==0)
{
a[step]=i;
vis[i]=1;
dfs(step+1);
vis[i]=0;
}
}
}
int main()
{
cin>>n>>r;
a[0]=1;
dfs(1);
return 0;
}
视频讲解
例题3组合总和
给定一个数组c和一个目标数t,找出c中所有可以使数字和为t的组合。 c中的数字每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集中不能包含重复的组合。
输入
第一行为n和t,表示数组c中包含n个正整数和目标数t;
第二行为n个正整数,中间用一个空格隔开
输出
若干行,一行表示一个符合题意的划分
样例:
输入
7 8
10 1 2 7 6 1 5
输出
1 1 6
1 2 5
1 7
2 6
【解析】
1、本题和例3有如下区别:
本题中c中的数字在每个组合中只能使用一次
本题中c中有重复的元素,而例3中没有重复元素
2、本题的难点在于:**集合c中有重复元素,但不能有重复组合 **
【知识点】
1、借助本题,请理解好搜索中的两种去重“树层去重”和“树枝去重”
2、所谓去重,其实就是使用过的元素不能重复选取。
”使用过“在树形结构上是有两个维度的,一个维度是同一树枝上使用过,另一个维度是同一树层上使用过。
回看一下题目,元素在同一个组合内是可以重复的,但是两个组合不能相同。所以我们要去重的是同一树层上”使用过“的元素,同一树枝上的元素都是一个组合里的,不用去重。
强调一下,树层去重是需要对数组排序的。
本题中增加一个bool类型数组used[],用来记录同一树枝上的元素是否使用过。
#include<bits/stdc++.h>
using namespace std;
int a[105];
bool used[105];
int ans[105];
int n,t;
int sum = 0;
int x = 0;
void dfs(int index)
{
if(sum==t)
{
for(int i = 1;i<=x;i++) cout<<ans[i]<<" ";
cout<<endl;
return ;
}
for(int i = index;i<=n;i++)
{
//used[i-1]==ture,说明 同一树枝使用过a[i-1]元素;used[i-1]==false,说明同一树层使用过a[i-1]元素
if(i>0&&a[i]==a[i-1]&&used[i-1]==false) continue;
sum+=a[i];
ans[++x]=a[i];
used[i]=true;
dfs(i+1);
//开始回溯
sum-=a[i];
used[i]=false;
x--;
}
}
int main()
{
cin>>n>>t;
for(int i = 1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
dfs(1);
return 0;
}
视频讲解
例四选数【NOIP普及组2002】
提示性图解
视频讲解
练习题目:(实践出真知,实践中体会编程成就感)
1、挑选数字
–视频讲解
2、小明爱正方形
–视频讲解
3、分书问题
–视频讲解
搜索相关内容
1、DFS第一课
3、回溯法–分割问题与子集问题
最后
以上就是笑点低冰棍为你收集整理的回溯法--排列组合类的全部内容,希望文章能够帮你解决回溯法--排列组合类所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复