目录
知识点
1.迭代加深
定义:
每次限定一个 m a x d e p maxdep maxdep最大深度,使搜索树的深度不超过 m a x d e p maxdep maxdep。
for(int maxdep=1;maxdep<=题目中给的最大步数;maxdep++){
dfs(0,maxdep);//0为出入函数中当前步数,maxdep为传入的最大深度。
if(success) break;//如果搜索成功则会在dfs函数中将success赋值为1。
}
使用范围:
1.在有一定的限制条件时使用,到达目标状态,则输出步数,否则输出
−
1
-1
−1。
2.题目中说输出所有解中的任何一组解。
为什么能够降低时间复杂度:
我们可能会在一个没有解(或解很深的地方无限递归然而题目中要求输出任何的一组解),所以我们限制一个深度,让它去遍历更多的分支,去更广泛地求解,(其实和
B
F
S
BFS
BFS有异曲同工之妙)。
2:估价函数
定义:
f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)
其中
f
(
n
)
f(n)
f(n)是节点的估价函数,
g
(
n
)
g(n)
g(n)是现在的实际步数,
h
(
n
)
h(n)
h(n)是对未来步数的最完美估价(“完美”的意思是可能你现实不可能实现,但你还要拿最优的步数去把
h
(
n
)
h(n)
h(n)算出来)。
应用:
void dfs(int dep,int maxdep){
if(evaluate()+dep>maxdep)return;
//evaluate函数为对未来估价的函数,若未来估价加实际步数>迭代加深的深度则return。
if(!evaluate){
success=1;
printf("%d\n",dep);
return;
}
......
}
3: A ∗ 和 I D A ∗ A^∗和IDA^* A∗和IDA∗的区别
A
∗
A^*
A∗是用于对
B
F
S
BFS
BFS的优化;
I
D
A
∗
IDA^*
IDA∗是对结合迭代加深的
D
F
S
DFS
DFS 的优化。
本质上只是在
B
F
S
BFS
BFS和
D
F
S
DFS
DFS上加上了一个估价函数。
何时使用因题而定:
例题
1.骑士精神
题目描述
在一个
5
×
5
5×5
5×5的棋盘上有12个白色的骑士和12个黑色的骑士,且有一个空位,在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2,纵坐标相差为1的格子)移动到空位上。
给定一个初始的棋盘,怎样才能进过移动变成如下目标棋盘:
为了体现骑士精神,他们必须以最少的步数完成任务
输入格式
第一行有一个正整数 T ( T < = 10 ) T(T<=10) T(T<=10),表示一共有 N N N组数据。接下来有 T T T个 5 × 5 5×5 5×5的矩阵, 0 0 0表示白色骑士, 1 1 1表示黑色骑士, ∗ * ∗表示空位。两组数据之间没有空行。
输出格式
对于每组数据都输出一行。如果能在15步以内(包括15步)到达目标状态,则输出步数,否则输出-1。
输入输出样例
输入 #1
2
10110
01*11
10111
01001
00000
01011
110*1
01110
01010
00100
输出 #1
7
-1
代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){ //快读
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int dx[9]={0,1,2,1,2,-1,-2,-1,-2}; //步数移动数组
int dy[9]={0,2,1,-2,-1,2,1,-2,-1}; //步数移动数组
char ch; //输入字符
bool flag; //判断是否有解
int su[10][10]; //输入的样例数组
int t; //输入个数
int k[7][7]={ //目标数组
{0,0,0,0,0,0},
{0,1,1,1,1,1},
{0,0,1,1,1,1},
{0,0,0,2,1,1},
{0,0,0,0,0,1},
{0,0,0,0,0,0}
};
inline int valuea(){ //估价函数 有几个不一样那么至少需要移动几次
int cnt=0;
for(int i=1;i<=5;++i)
for(int j=1;j<=5;++j) if(k[i][j]!=su[i][j]) cnt++;
return cnt;
}
inline bool judge(int x,int y){ //判断是否越界
if(x<1||x>5||y<1||y>5) return false;
return true;
}
void dfs(int l,int x,int y,int maxn){ //深搜函数 (搜索次数,当前空格坐标x,y,最大搜索次数)
if(l==maxn){
if(!valuea()) flag=1;
return;
}
if(flag==1) return; //如果搜到就不用再搜了
for(int i=1;i<=8;++i){
int px=x+dx[i],py=y+dy[i]; //更新空格坐标
if(!judge(px,py)) continue; //判断越界
swap(su[x][y],su[px][py]); //将骑士与空格交换
int valu=valuea();
if(valu+l<=maxn){ //估价+加当前次数小于等于最大搜索次数
dfs(l+1,px,py,maxn);
}
swap(su[x][y],su[px][py]); //回溯
}
}
int main(){
t=read();
int kx,ky; //记录空格坐标
for(int i=1;i<=t;++i){
flag=0;
for(int p=1;p<=5;++p)
for(int q=1;q<=5;++q){
cin>>ch;
if(ch=='*') su[p][q]=2,kx=p,ky=q; //记录空格
else su[p][q]=ch-'0'; //记录白 黑骑士
}
if(!valuea()){ //如果当前数组与答案数组相同
printf("0\n");
continue;
}
for(int maxn=1;maxn<=15;++maxn){ //从1次到15次最少搜索次数开始循环
dfs(0,kx,ky,maxn);
if(flag==1){ //如果搜到了
printf("%d\n",maxn);
goto fini; //绕过输出-1
}
}
printf("-1\n");
fini:;
}
}
2.铁盘整理
题目描述
在训练中,一些臂力训练器材是少不了的,小龙在练习的时候发现举重器械上的铁盘放置的非常混乱,并没有按照从轻到重的顺序摆放,这样非常不利于循序渐进的锻炼。他打算利用一个非常省力气的办法来整理这些铁盘,即每次都拿起最上面的若干个圆盘并利用器械的力量上下翻转,这样翻转若干次以后,铁盘将会按照从小到大的顺序排列好。那么你能不能帮小龙确定,最少翻转几次就可以使铁盘按从小到大排序呢?
例如:下面的铁盘经过如图所示的以下几个步骤的翻转后变为从小到大排列。
输入格式
共两行。第一行为铁盘个数 N N N( 1 ≤ N ≤ 16 1 \leq N \leq 16 1≤N≤16),第二行为 N N N 个不同的正整数,分别为从上到下的铁盘的半径 R R R( 1 ≤ R ≤ 100 1 \leq R \leq 100 1≤R≤100)。
输出格式
一个正整数,表示使铁盘从小到大有序需要的最少翻转次数。
输入输出样例
输入 #1
5
2 4 3 5 1
输出 #1
5
代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){ //快读
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int n; //铁盘个数
int a[20],b[20]; //存储铁盘半径(开两个用于离散化处理)
inline int valuea(){ //估价函数(如果后一个半径不等于前一个半径+1则至少需要一次)
int cnt=0;
for(int i=1;i<=n;++i)
cnt+=abs(a[i]-a[i+1])!=1;
return cnt;
}
bool flag; //判断是否有解
void dfs(int now,int maxn,int pre){ //深搜(当前搜索次数,最大搜索次数,上一个移动的盘子区间)
if(flag||now+valuea()>maxn) return; //如果有解或当前搜索次数+估价大于最大搜索次数,跳出
if(!valuea()){
flag=1;
return;
}
for(int i=1;i<=n;++i){ //1到n进行枚举
if(i==pre) continue; //如果上一个移动的是它,就跳过(因为没用)
reverse(a+1,a+i+1); //翻转
dfs(now+1,maxn,i);
reverse(a+1,a+i+1); //回溯
}
}
int main(){
n=read();
for(int i=1;i<=n;++i){ //读入铁盘半径
a[i]=read();
b[i]=a[i];
}
sort(b+1,b+n+1); //将铁盘半径排序(用作离散化处理)
for(int i=1;i<=n;++i){
a[i]=lower_bound(b+1,b+n+1,a[i])-b; //离散化(a[i]存储内容变成铁盘半径排名)
}
a[n+1]=n+1; //方便比较
for(int i=0;i<=16;++i){
flag=0;
dfs(0,i,0);
if(flag){
printf("%d",i);
return 0;
}
}
}