迭代加深和IDE*
放在前面
1.DFS总结
2.在这一部分中,经常在dfs内部用到这样一种形式
bool dfs(...)
{
if(dfs(...)) return true;// 链式反应
...
return false;
}
我们称上面在 \(dfs\) 内部直接返回 \(true\) 的这种操作为链式反应,它会一路返回 \(true\),最终结束循环。
其含义就是只要有一种情况符合条件,那么最终答案就是 \(true\)。
只有当所有情况都搜索完毕,还没有找到一个 \(true\) 的条件,才返回 \(false\)
迭代加深搜索(IDDFS)
一、介绍
1.什么是迭代加深
首先,它是深度优先搜索,其次它与普通深度优先搜索不同的是,每次深搜都会有搜索的最大深度限制,如果没有找到解,那么就增大深度,再进行深搜,如此循环直到找到解为止,这样可以找到最浅层的解。
标志性的结构如下:
while(!dfs(maxdepth))
maxdepth ++;
2.优势和劣势
了解了原理,大家也许会有疑问,那为啥不不直接用广度优先搜索呢?那是因为 \(IDDFS\) 有如下几个优势:
1.时间复杂度只比 \(BFS\) 稍差一点(虽然搜索 \(k+1\) 层时会重复搜索 \(k\) 层,但是整体而言并不比广搜慢很多)。(在图解(2)中有说明)
2.空间复杂度与深搜相同,却比广搜小很多。
3.利于剪枝(迭代加深本质上还是 \(DFS\) ,而 \(DFS\) 利于剪枝,\(BFS\) 不便于剪枝)。
3.一个可能的疑问
我们已经知道,迭代加深是按照深度逐渐加深去搜索的,这就会导致产生大量重复搜索,那么如果重复搜索的太多,效率会不会比普通的 \(DFS\) 要低呢?
答案是不会的!
因为普通的 \(DFS\) 的增长规模是指数级别的,我们重复搜索的 \(1\) 到 \(d - 1\) 层的所有节点之和可能都没有第 \(d\) 层的节点多。
4.图解
(1)
(2)
5.使用时机
当搜索树非常深,但是我们能确定答案一定在浅层节点时,就可以使用迭代加深DFS。
二、例题
1.题目描述
2.分析
首先可以证得一个非常重要的结论(性质),本题序列的长度 \(m\) 肯定是不大于 \(10\) 的。
证明如下:
我们贪心的让每一个元素都是最大值,那么 \(X[i] = X[i - 1] + X[i - 1]\),此时的序列为:\(1,2,4,8,16,32,64,128...\),我们发现,在\(m = 8\) 时,元素的大小就已经题目超过了题目给定的最大值 \(100\)。(然后详细的??不太会证了)
然后,通过这个性质,我们可以想到迭代加深这个做法,因为题目答案的深度是比较小的,而我们在搜索过程中可能会搜索很深的距离,最极端的情况,贪心的让每一个元素都是最小值,那么序列为 \(1,2,3,4,5,6.....\),如果要搜索到最大值 \(100\),\(dfs\) 的深度来到了 \(100\) 层,这是无法接受的。
3.代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n, path[17];
bool st[107];
bool dfs(int u, int max_depth)// 当前处于第几层,最大层数(从1开始)
{
if(u == max_depth + 1) return path[max_depth] == n;
// 剪枝:避免重复搜索,每个数只搜索一次,st的值不需要回溯
memset(st, false, sizeof st);
// 从之前的序列中找出两个数
// 剪枝:从序列中最大的数开始找的数大,结束快,可以减少搜索次数
for(int i = u - 1; i >= 1; i -- )
{
for(int j = i; j >= 1; j -- )// 搜索[i,j]和[j,i]是等价的
{
int s = path[i] + path[j];
if(s <= path[u - 1] || s > n) continue;
if(st[s]) continue;
st[s] = true;
path[u] = s;
if(dfs(u + 1, max_depth)) return true;// 如果本次搜索成功,直接返回
}
}
// 这里别忘了
return false;
}
int main()
{
path[1] = 1;
while(cin >> n, n)
{
int depth = 1;
if(n != 1)// 第一层恒的值恒为1,不需要搜索
{
depth ++ ;
while(!dfs(2, depth))// 第一层恒为1,所以从第二层开始搜索
depth ++ ;
}
for(int i = 1; i <= depth; i ++ ) cout << path[i] << ' ';
cout << endl;
// 我们在前面已经证明得到了depth<=10的结论,我们可以验证一下
if(depth > 10) puts("FALSE!");// 这句话在n<=100的条件下永远不会执行
}
return 0;
}
IDE*
一、介绍
IDA*
的全称为:Iterative deepening A*
,即基于迭代加深的 A*
算法
IDA*
实质上就是对迭代加深加了一个启发式的剪枝
二、原理
在迭代加深代码框架基础上,对每个状态点引入一个估价值(该点距离目标点所需的最小步数),如果某状态点的深度加上该点的估价值>迭代加深限制的最大层数则直接返回不再向下层继续搜索
同A*
算法一样,都需要保证估价值 <= 真实值
该算法的难点仍在于如何确定估价函数
三、例题
1.题目描述
注:由于不同题目确定估价函数的方式各不相同,这里的题目仅是给出一个应用实例,无法做出推广
排书
2.分析
本题的难点主要在于如何确定估价函数。
由于我们的估价函数要小于真实值,所以我们希望我们的估价函数的含义可以表示为:最少需要多少步,可以把该序列变换到目标序列。
如该图下方所示,我们移动一段区间(假设该区间只包含一个元素 \(2\) ),将它移动到一个数的后面,这里是将区间 \([2, 2]\) 移动到 \(1\) 的后面,此时,有三个数的相邻关系被修改:\((1,3)\rightarrow(1,2),(3,2)\rightarrow(3,4),(2,4)\rightarrow(2,3)\)
我们可以发现,移动一段区间,最多会改修三个数的相邻关系,也就是说,如果原序列中有 \(cnt\) 个数之间的相邻关系是错误的,那么我们最少需要 \(\lceil cnt / 3 \rceil\) 步就可以完成。
然后再 \(dfs\) 的过程中,每次选出一段区间,将这段区间放在某个数的后面,这个数必须在区间的右边(排除等效冗余)。
3.代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 25;
int T, n;
int w[N], backup[5][N];
// backup 之所以开两维,第一位为5,是因为我们在u =1,2,3,4,5时都可能需要用到w数组
// 而题目规定深度最大为5,所以开到5
int f()// 含义:**至少**需要cnt步才能完成
{
int cnt = 0;
for(int i = 0; i + 1 < n; i ++ )
if(w[i] + 1 != w[i + 1])
cnt ++ ;
return (cnt + 2) / 3; // <==> ceil(cnt / 3);
}
bool dfs(int u, int max_depth)
{
if(u + f() > max_depth) return false; //A*
if(f() == 0) return true;
//交换两个区间
for(int len = 1; len <= n; len ++ )// 枚举区间大小
{
for(int l = 0; l + len - 1 < n; l ++ )
{
int r = l + len - 1;
for(int k = r + 1; k < n; k ++ ) // 把当前区间放到k的后面
{
// 就相当于,把[r + 1, k]移到[l, r]的前面来,再把[l, r]移到后面
memcpy(backup[u], w, sizeof w);
int y = l;// y就指向当前位于那个位置
for(int x = r + 1; x <= k; x ++ , y ++ ) w[y] = backup[u][x]; // 把[r + 1, k]移到[l, r]的前面来
for(int x = l; x <= r; x ++, y ++ ) w[y] = backup[u][x];// 再把[l, r]移到后面
if(dfs(u + 1, max_depth)) return true; //一路true返回
memcpy(w, backup[u], sizeof w); // 回溯
}
}
}
return false; // 不要忘了
}
int main()
{
cin >> T;
while(T -- )
{
cin >> n;
for(int i = 0; i < n; i ++ ) cin >> w[i];
int depth = 0;
while(depth < 5 && !dfs(0, depth)) depth ++ ;
if(depth >= 5) puts("5 or more");
else cout << depth << endl;
}
return 0;
}
相关例题
例题一、IDE*
1.题目描述
2.分析
3.代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int pos[8][8] = {
{0, 2, 6, 11, 15, 20, 22},
{1, 3, 8, 12, 17, 21, 23},
{10, 9, 8, 7, 6, 5, 4},
{19, 18, 17, 16, 15, 14, 13},
{23, 21, 17, 12, 8, 3, 1},
{22, 20, 15, 11, 6, 2, 0},
{13, 14, 15, 16, 17, 18, 19},
{4, 5, 6, 7, 8, 9, 10},
};
int oppesite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
int centre[8] = {6, 7, 8, 11, 12, 15, 16, 17};
int n, w[N], path[N];
int res, depth;
int f()
{
static int num[4];
memset(num, 0, sizeof num);
for(int i = 0; i < 8; i ++ ) num[w[centre[i]]] ++ ;
int maxn= -1;
for(int i = 1; i <= 3; i ++ ) maxn = max(maxn, num[i]);
return 8 - maxn;
}
void operate(int u)
{
int t = w[pos[u][0]];
for(int i = 0; i < 6; i ++ ) w[pos[u][i]] = w[pos[u][i + 1]];
w[pos[u][6]] = t;
}
//当前深度,最大深度,上一个节点
bool dfs(int u, int max_depth, int last)
{
if(u + f() > depth) return false;
if(f() == 0) return true;
for(int i = 0; i < 8; i ++ )
{
if(oppesite[i] != last)
{
operate(i);
path[u] = i;
if(dfs(u + 1, max_depth, i)) return true;
operate(oppesite[i]);
}
}
return false;//不返回false会出错
}
int main()
{
while(cin >> w[0], w[0])
{
for(int i = 1; i < 24; i ++ ) cin >> w[i];
//迭代加深
depth = 0;
while(!dfs(0, depth, -1)) depth ++ ;
if(!depth) cout << "No moves needed" << endl;
else
{
for(int i = 0; i < depth; i ++ )
{
char ch = path[i] + 'A';
cout << ch;
}
cout << endl;
}
cout << w[6] << endl;
}
return 0;
}