基础图论与搜索(一)

DFS(深度优先搜索)

基本概念

深度优先搜索(Depth First Search)一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(n!)以上。

  • 如果找到多个,则任选一个顶点,然后继续从该顶点出发;

  • 如果一个都没有找到,则回退到之前访问过的顶点,看看是否有漏掉的;

深搜总结起来就是:

(1)先想好递归方程

(2)处理好枚举数据,对已遍历的数据要标记

(3)一定一定要还原现场,这是回溯的决定性条件

算法思想

回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的方法为回溯法,而满足回溯条件的某个状态的点称为“回溯点”

DFS模板

void dfs()//参数用来表示状态  
{  
    if(到达终点状态)  
    {  
        ...//根据题意添加  
        return;  
    }  
    if(越界或者是不合法状态)  
        return;  
    if(特殊状态)//剪枝
        return ;
    for(扩展方式)  
    {  
        if(扩展方式所达到状态合法)  
        {  
            修改操作;//根据题意来添加  
            标记;  
            dfs();  
            (还原标记);  
            //是否还原标记根据题意  
            //如果加上(还原标记)就是 回溯法  
        }  
    }  
}  

DFS一般用于求解问题有多少种情况,多少条路径,最大路径等等。

例题

全排列(洛谷P1706)

#include<bits/stdc++.h>
using namespace std;
int n,a[10],q[50000],v[10],p;
void dfs(int index)//选择第step位置的数
{
    if(index==n+1)//第n+1个位置时,表示前面已经选择了n个数
    {
        for(int i=1; i<=n; i++)//进行输出
        {
           printf("%5d",q[i]);//%5d是保留五个场宽
        }
        cout << '\n';
        return;//返回上步继续选择
    }
    for(int i=1; i<=n; i++)//遍历所有可选择的数字
    {
        if(v[i]==0)//满足未被使用的条件
        {
            q[index]=a[i];//选择数字i,放入数组q
            v[i]=1;//将数字i进行标记
            dfs(index+1);//进行下步选择
            v[i]=0;//撤销数字i的标记
        }
    }
    return ;
}
int main()
{
    cin >>n;
    for(int i=1; i<=n; i++)a[i]=i;
    dfs(1);
    return 0;
}

迷宫(洛谷P1605)

#include<bits/stdc++.h>
using namespace std;
int n,m,t,a[10][10],v[10][10],ans;
int d[][2]={{0,1},{1,0},{0,-1},{-1,0}};
struct point
{
    int x,y;
};
point start,fi;
void dfs(point s)//选择在s点下步的点
{
    point temp;
    if(s.x==fi.x&&s.y==fi.y)//到达终点,可达路径加一
    {
        ans++;
        return;//返回上个点
    }
    for(int i=0;i<4;i++)//遍历每个点的四个方向
    {
        temp.x=s.x+d[i][1];//四个方向的移动
        temp.y=s.y+d[i][0];
        if(temp.y>=1&&temp.y<=n&&temp.x>=1&&temp.x<=m&&a[temp.y][temp.x]==0&& v[temp.y][temp.x]==0)//若下个点在迷宫范围内且为没有被访问过的非障碍点
        {
            v[temp.y][temp.x]=1;//标记选择的点,防止重复遍历
            dfs(temp);//以选择的点进行新的遍历
            v[temp.y][temp.x]=0;//撤销标记
        }
    }
    return;
}
int main()
{
    cin >> n >> m>> t;
    cin >> start.x >> start.y >> fi.x >> fi.y;
    point temp;
    for(int i=0;i<t;i++)
    {
        cin >> temp.x >> temp.y;
        a[temp.y][temp.x]=1;
    }
    v[start.y][start.x]=1;
    dfs(start);
    cout << ans;
    return 0;
}

BFS(广度优先搜索)

BFS(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和广度优先搜索类似的思想。属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。

简单来说,BFS是一种图搜索的算法,目的是用于搜索检查每一个可以达到的点,直到找到结果为止。他和DFS的区别:DFS是一条路走到黑,如果没用路就返回,而BFS是从上往下层次依序遍历,我们一般用队列来实现BFS的遍历。

算法步骤:

1. 首先将根节点放入队列中。

2. 从队列中取出第一个节点,并检验它是否为目标。

如果找到目标,则结束搜寻并回传结果。

否则将它所有尚未检验过的直接子节点加入队列中。

3. 若队列为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传"找不到目标"。

4. 重复步骤2。

BFS模板

struct node //结构体用于保存每一状态信息 

{

    ......    

};

void bfs()

{

    queue<node> Q; // 定义存放结构体的队列Q 

    起点入队

    标记起点

    while(!Q.empty()) // 队非空 

    {

        node u=Q.front(); //获取队首信息(结构体)
        Q.pop();//每次从队首把所有可能的状态走完,队首要出队  
        for(拓展接下来所有可能的状态) 

        {

            得到并记录新的状态信息

            判断状态是否合法

            若合法

            {

                当前标记为已访问 

                Q.push(合法节点);//状态入队

                判断是否到达目标

                若满足,输出答案,return ;      

            } 

        }

        

    }

}

BFS一般用于解决最短路径,最短步骤等最优问题。

例题

离开中山路(洛谷P1746)

#include <bits/stdc++.h>
using namespace std;
int a, b, c, d, n, vis[1005][1005];
int dc[] = {0, 0, -1, 1};
int dr[] = {-1, 1, 0, 0};
char s[1005][1005];
struct point//当前的位置结构体
{
    int c, r, step;
};

int bfs(point start)
{
    queue<point> q;
    q.push(start);//起点入队
    vis[start.r][start.c] = 1;//标记起点
    while (!q.empty())
    {
        point front = q.front(),p;//获取队首元素
        q.pop();//队首元素出队
        for (int i = 0; i < 4; i++)//遍历队首元素的所有可能
        {
            p.r = front.r + dr[i];
            p.c = front.c + dc[i];
            p.step = front.step + 1;//得到一个新的点,步数加一
            if (p.r >= 0 && p.r <= n && p.c >= 0 && p.c <= n && s[p.r][p.c] == '0' && vis[p.r][p.c] == 0)//判断是否在迷宫内且到访问
            {
                vis[p.r][p.c] = 1;//标记这个点
                q.push(p);//将这个点入队
            }
            if (p.r == c && p.c == d)
            {
                return p.step;
            }
        }
    }
    return 0;
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> s[i][j];
        }
    }
    cin >> a >> b >> c >> d;
    point s;
    s.r = a;
    s.c = b;
    s.step = 0;
    cout << bfs(s);
    return 0;
}

奇怪的电梯(洛谷P1135)

#include<bits/stdc++.h>
using namespace std;
int n,a,b,s[205],visited[205];
struct node
{
    int id,step;
};

int bfs(node start)
{
    queue<node> q;
    q.push(start);//将起始楼层入队
    visited[start.id]=1;//标记楼层
    while (!q.empty())
    {
        node front=q.front(),down,up;//获取现在所在楼层的状态
        down.id=front.id-s[front.id];//当前楼层向下可到达的楼层
        up.id=front.id+s[front.id];//当前楼层向上课到达的楼层
        down.step=front.step+1;//步数加一
        up.step=front.step+1;
        if (front.id==b)//如果当前楼层为目标楼层,返回结果
        {
            return front.step;
        }
        
        if (down.id>=1 && visited[down.id]==0)//判断当前楼层向上是否是可达的
        {
            visited[down.id]=1;
            q.push(down);//入队
        }
        if (up.id<=n && visited[up.id]==0)//与上相同
        {
            visited[up.id]=1;
            q.push(up);
        }
        q.pop();//在遍历了所有可能后,将队首元素出队
    }
    return -1;
}
int main()
{
    cin >> n >> a >> b;
    for (int i = 1; i <= n; i++)
    {
        cin >> s[i];
    }
    node s;
    s.id=a;
    s.step=0;
    cout << bfs(s);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值