20200115

1 深度优先搜索(DFS)

基本思想

为了求得问题的解,先选择某一种可能情况向前(子结点)探索,在探索过程中,一旦发现原来的选择不符合要求,就回溯至父亲结点重新选择另一结点,继续向前探索,如此反复进行,直至求得最优解。深度优先搜索的实现方式可以采用递归或者栈来实现。

复杂度的计算

复杂度分为两个部分,一部分是状态数,一部分是扩展后继状态的复杂度
所以要想优化搜索,要么减小状态数,要么减小扩展后继的计算量

实现框架
void dfs(int dep, int n)
    {
        if(dep == n)
        {
         }
        for(int j =1; j <= n; j++)
         {
             if(is_used[j] != 1)
             {
                 is_used[j] = 1;
                 dfs(dep + 1, n);
                 is_used[j] = 0;
             }
        }
    }

2 广度优先搜索(BFS)

深度优先搜索(DFS):用来求最优值,我们通常不知道最优答案在哪一层,比较耗时。

queue Q;
Q.push( startState );
While ( ! Q.empty ) {
    curState = Q.front();
    if( curState ==  endState ) return true;
       mark[curState] = true;
    extState = extend ( curState );
    If( !mark[extState] ) {
        Q.push( extState );
    } 
}

3 迭代加深搜索(IDDFS)

用 DFS 去模拟 BFS 的过程
限制深度的 DFS 搜索,减小了空间的消耗
定义:
迭代加深搜索是在速度上接近广度优先搜索,空间上和深度优先搜索相当的搜索方式。由于在使用过程中引入了深度优先搜索,所以也可以当作深度优先搜索的优化方案。
迭代加深搜索适用于当搜索深度没有明确上限的情况。

例如下图的一棵搜索树,在进行深度优先搜索前先规定好这次搜索的最大深度dep,当搜索到达dep却还没搜索到结果时回溯。之后不断加大搜索深度,重新搜索,直到找到结果为止。虽然这样搜索次数会累计很多次,但每一次搜索的范围和下一次搜索的范围相比微不足道,所以整体搜索速度不会受太大影响。
由于深度是从小到大逐渐增大的,所以当搜索到结果时可以保证搜索深度是最小的。这也是迭代加深搜索在一部分情况下可以代替广度优先搜索的原因(还比广搜省空间)。

算法:
假设G是需要搜索到的结果。

当 dep = 1 时搜索深度为1,搜索到节点 A,未搜索到结果,dep++ 并进行下一次深搜

当 dep = 2 时搜索深度为2,搜索到节点 A,B,C,D 未搜索到结果,dep++ 并进行下一次深搜。

当 dep = 3 时搜索深度为3,搜索到节点 A,B,C,D,E,G 搜索到结果G,停止全部搜索并记录记录结果。

4 双向广度搜索

每次扩展不仅扩展起始节点端,同时扩展终点端的搜索状态,直到中途相遇
定义:
如果已经知道搜索的开始状态和结束状态,要找一个满足某种条件的一条路径(一般是最短路径),为了避免无谓的“组合爆炸”产生,就可以采取双向广度搜索算法,也就是从开始状态和结束状态同时开始搜索,一个向前搜,一个向后找。
这样做的好处是什么?
我们不妨假设每次搜索的分支因子是rr,如果最短的路径长为LL的话(也就是搜了LL层),那么,用一般的BFS算法(不考虑去掉重复状态),总的搜索状态数是rLrL;而如果采取双向BFS算法,那么,从前往后搜,我们只需要搜索L2L2层,从后往前搜,我们也只要搜L2L2层,因此,搜索状态数是2×rL22×rL2,比普通BFS就快了很多了。
双向BFS算法的实质还是BFS,只不过两边同时开始BFS而已。还是可以利用队列来实现:可以设置两个队列,一个用于向前的BFS,另一个用于向后的BFS,利用这两个队列,同时从前、后开始层次遍历搜索树。

5 基本数据结构(栈与队列)

题目目录

1 全排列 LGP1706

2020011600028

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int a[25];
bool vis[25];
int n;
int ans=0; 
void dfs(int x){
    if(x==n+1){
        for(int i=1;i<=n;i++){
            printf("%d ",a[i]);
        }
        printf("
");
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i]==1) continue;
        vis[i]=1;
        a[x]=i;
        dfs(x+1);
        vis[i]=0;
    }
    return ;
}
int main( ){
    
    scanf("%d",&n);
    dfs(1);
    return 0;
} 

2 八皇后 LGP1219

2020011600029

八皇后

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int a[25];
bool vis[25];
int n;
int ans=0; 
void f(int x){
    if(x==n+1){
        ans++;
        if(ans<=3){
            for(int i=1;i<=n;i++){
                printf("%d ",a[i]);
            }
            printf("
");
        }
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i]==1) continue;
        bool q=1;
        for(int j=1;j<x;j++)
        {
            if(abs(x-j)==abs(i-a[j])){
                q=0;
                break;
            }
            
        }
        if(q){
            vis[i]=1;
            a[x]=i;
            f(x+1);
            vis[i]=0;
        }
        
    }
    return ;
}
int main( ){
    scanf("%d",&n);
    f(1);
    printf("%d",ans); 
    return 0;
} 

nn皇后

在一个 n×nn×n 的国际象棋棋盘上放置nn个皇后,使得它们中任意 2 个之间都不互相“攻击”,即任意 2 个皇后不可在同行、同列、同斜线上。求 NN 皇后问题的所有放法。
输入 nn,每行输出一种方案,每种方案顺序输出皇后所在的列号,各个数之间用空格隔开。

输入

4

输出

2 4 1 3 3 1 4 2

#include<iostream> 
#include<cstdio> 
#include<cmath> 
using namespace std; 
int m,d[15],b[15],found=0,n; 
bool place(int x)//判断第 x 行的 d[x]列是否可以放置皇后 
{ 
    for(int i=1;i<x;i++) 
    if ((abs(d[i]-d[x])==abs(i-x))||(d[i]==d[x])) return false; 
    return true; 
}
void print() { 
    found=1; 
    for(int i=1;i<=n;i++) printf("%d ",d[i]);
    printf("
"); 
}
void dfs(int x) //表示第 x 行 
{ 
    for(int i=1;i<=n;i++) //选择列 
    { 
        d[x]=i; 
        if (place(x)) //如果第 x 行的第 i 列可以放置皇后 
        { 
            if (x==n) print(); //最后一行放置成功,则输出答案 else dfs(x+1); //继续放置下一行 
        } 
    } 
}
int main() { 
    int i,j; 
    scanf("%d",&n); 
    dfs(1); 
    if(found==0) printf("no solute!"); 
    return 0; 
}

3 生日蛋糕 LGP1731 [NOI1999]

2020011600030

题意描述

做一个生日蛋糕,生日蛋糕是由 mm个圆柱体构成的,且满足以下条件

  1. 从下往上,圆柱半径和高都越来越小
  2. 总体积为 nn
    求表面积最小的蛋糕制作方案(底面不算表面积)

题解

什么规律也没有,貌似只能搜索了
用什么表示状态?
(i,Hi,Ri,Vi,Si)(i,Hi,Ri,Vi,Si) 表示第 ii 层,高度为 HiHi,半径为 RiRi,已用体积为 ViVi,当前表面积为SiSi
可惜这样的算法是不能通过的,我们要考虑剪枝
最优化剪枝:如果当前已用表面积加上余下最小的侧面积大于已知最优解,剪枝
可行性剪枝:剩下的材料太多或太少,做不了恰好剩下的层数,剪枝

4 LGP1379 八数码难题

2020011600031

题意描述

有一块 3 × 3 板子,上面有 8 个空格分别写着 1 到 8 的 8 个数,有一个格子为空格,每次可以选择空格与周围某个数字格交换,问最少多少步可以移动成最后一个格为空格,其余格为 1 到 8顺次排列的状态

#include <cstdio>
#include<map>
#include<queue>
#include<algorithm>
#define ll long long
using namespace std;
const ll dx[]={-1,0,0,1},dy[]={0,-1,1,0};
ll n;
bool check(int nx,int ny){
    return(nx<0||ny<0||nx>2||ny>2);
}
int  main()
{
    scanf("%lld",&n);
    queue<ll> q;
    q.push(n);
    map<ll,ll> m;
    m[n]=0;
    while(!q.empty())
    {
        int u=q.front(); 
        int c[3][3],f=0,g=0,n=u;q.pop();
        if(u==123456780)break;
        for(ll i=2;i>=0;i--)
            for(ll j=2;j>=0;j--)
            {
                c[i][j]=n%10,n/=10;
                if(!c[i][j])f=i,g=j;
            }
        for(ll i=0;i<4;i++)
        {
            ll nx=f+dx[i],ny=g+dy[i],ns=0;
            if(check(nx,ny))continue; 
            swap(c[nx][ny],c[f][g]);
            for(ll i=0;i<3;i++)
                for(ll j=0;j<3;j++)ns=ns*10+c[i][j];
            if(!m.count(ns))
            {
                m[ns]=m[u]+1;
                q.push(ns);
            }
            swap(c[nx][ny],c[f][g]);
        }
    }
    printf("%d",m[123456780]);
    return 0;
}

题解

首先我们已知原始状态,先把原始状态压入队列
接着我们每次有几个扩展,每次取出队头状态扩展后,把新扩展的且未出现过的几个状态压入队列,同时记录每个状态的深度
然后把新的状态压入队列
状态怎么存?
一共有 9! 状态,我们可以预先设计一种规则来表示,把一个棋盘状态映射成一个数字
判重则只需要一个数组即可

5 倒牛奶 LGP1215(USACO1.4)

2020011600032

题意描述

有三个桶,容量分别为 a,b,ca,b,c 升
一开始 AA 桶没有水,BB 桶也没有水,CC 桶水是满的
每次可以把一个桶里的水倒入另一个桶,直到桶被灌满或者桶空了
求 AA 桶空了的时候,CC 桶的水可能是几升
a,b,c20a,b,c≤20

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
int a,b,c;
bool vis[25][25][25];
int k=0,ans[100];
void dfs(int va,int vb,int vc){
    if(vis[va][vb][vc]) return ;
    if(va==0){
        k++;
        ans[k] = vc;
    }
    vis[va][vb][vc] = 1;
    if(vc){
        if(va<a) dfs(min(a,va+vc),vb,vc-(min(a,va+vc)-va));
        if(vb<b) dfs(va,min(vb+vc,b),vc-(min(b,vb+vc)-vb));
    }
    if(vb){
        if(va<a) dfs(min(a,va+vb),vb-(min(a,va+vb)-va),vc);
        if(vc<c) dfs(va,vb-(min(c,vc+vb)-vc),min(c,vc+vb));
    }
    if(va){
        if(vc<c) dfs(va-(min(c,va+vc)-vc),vb,min(c,vc+va));
        if(vb<b) dfs(va-(min(b,va+vb)-vb),min(b,va+vb),vc);
    }
    return ;
}
int main(){
    scanf("%d%d%d",&a,&b,&c);
    dfs(0,0,c);
    sort(ans+1,ans+k+1);
    for(int i=1;i<=k;i++) printf("%d ",ans[i]);
    return 0;
}

题解

每次有 66 种选择,直接搜索即可
用一个数组判断是否搜到重复状态
visa,bvisa,b 表示 AA 桶中aa 升水,BB 桶中 bb 升水这个状态是否已经搜过了

6 骑士问题

2020011600033

题意描述

在一个 n×nn×n 的棋盘中摆放尽量少的骑士(类似中国象棋中的马,但没有蹩马腿的规则),使得棋盘的每一格都会被至少一个骑士攻击到。但是骑士无法攻击到它自己站的位置。

另一版本

在一个标准8×8的国际象棋棋盘上,棋盘中有些格子是可能有障碍物的。已知骑士的初始位置和目标位置,你的任务是计算出骑士最少需要多少步可以从初始位置到达目标位置。有障碍物的格子当然不可能到达。
标准的8×8的国际象棋棋盘中每一个格子可以用唯一的编号确定。行用1~8这8个数字依次表示,列用“a”~“h”这8个字母依次表示。例如下图(a)的骑士所在位置(图中有n的格子)的编号为“d4”(注意“d”和“4”之间没有空格),我们知道国际象棋中的骑士可以按“L”路线移动(一个方向走2个格子,接着垂直方向走一个格子)。因此,如图(a)所示的骑士(位于d4),可以到达位置c2,b3,b5,c6,e6,f5,f3和e6,e2(图中有“x”标记的格子)。此外,骑士不能移出棋盘。

骑士可以按照移动规则自由地在棋盘上没有障碍物的格子中移动。图b给出了一个骑士移动的例子,也就是输入样例中第一组数据对应的例子。
初始格子用“n”标记,目标格子用“N”标记,有障碍物的格子用“b”标记。一个可行的移动序列在图中用数字标记出来(a1,b3,a5,c6,e5,g4,h2,f1)。总共需要7步才能完成。事实上,这也是最少的步数了。

题解

求最小,用广度优先搜索
每次扩展状态之后把状态放入尾部,然后记录每个状态摆放了多少个骑士,如果发现某个状态合法,那就说明这就是答案了

1 process(state)
2 for each possible next state from this one
3 enqueue next state
4 search()
5 enqueue initial state
6 while !empty(queue)
7 state = get state from queue
8 process(state)

状态去重?
不用考虑去重的问题,因为不会出现重复

有初始目标和结束目标我们通常想到的算法就是BFS。

原文地址:https://www.cnblogs.com/ziyuan122625/p/12213610.html