【AcWing】基础课搜索与图论

DFS与BFS

数据结构
BFS: 队列(先进先出)
在这里插入图片描述

步骤:

1、首先A入队列,
2、A出队列时,A的邻接结点B,C相应进入队列
3、B出队列时,B的邻接结点A,C,D中未进过队列的D进入队列
4、C出队列时,C的邻接结点A,B,D,E中未进过队列的E进入队列
5、D出队列时,D的邻接结点B,C,E,F中未进过队列的F进入队列
6、E出队列,没有结点可再进队列
7、F出队列

DFS:栈(先进后出)

在这里插入图片描述

步骤:
1、首先A入栈,
2、A出栈时,A的邻接结点B,C相应入栈 (这里假设C在下,B在上)
3、B出栈时,B的邻接结点A,C,D中未进过栈的D入栈
4、D出栈时,D的邻接结点B,C,E,F中未进过栈的E、F入栈(假设E在下,F在上)
5、F出栈时,F没有邻接结点可入栈
6、E出栈,E的邻接结点C,D已入过栈
7、C出栈


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


int check(参数)
{
    if(满足条件)
        return 1;
    return 0;
}
 
void dfs(int step)
{
        判断边界								//递归出口
        {
            相应操作
        }
        尝试每一种可能
        {
               满足check条件
               标记
               继续下一步dfs(step+1)
               恢复初始状态(回溯的时候要用到)
        }
}
**基本参数:**
path [ i ]       路径点
u                层数
st [ i ]         记录已经用过的点,定义成布尔类型

全排列问题

代码:

#include<iostream>
using namespace std;
const int N=10;
int n;
int path[N];
bool st[N];
void dfs(int u){
    if(u==n){
        for(int i=0;i<n;i++)
            printf("%d",path[i]);
        puts("");
        return;
    }
    for(int i=1;i<=n;i++){
        if(!st[i]){
            path[u]=i;
            st[i]=true;
            dfs(u+1);
            st[i]=false;
        }
    }
}
int main(){
    cin>>n;
    dfs(0);
    return 0;
}

在这里插入图片描述


n皇后问题

在这里插入图片描述
n皇后问题

//解法一
//为什么解法一没有row?因为解法一是按行枚举的,这就保证了每一行只有一个。
#include<iostream>
using namespace std;
const int N=20;
int n;
char g[N][N];
bool col[N],dg[N],udg[N];//3个数组来记录列、主对角线和次对角线的方式
/*
u:	当前行数
n:	总列数
i:	当前列数
dg :主对角线
udg :副对角线
col[i]:当前行的第i列
*/
void dfs(int u){
    if(u==n){
        for(int i=0;i<n;i++) puts(g[i]);
        puts("");
        return;
    }
    for(int i=0;i<n;i++){  //for循环嵌套递归+if剪枝
        if(!col[i]&&!dg[u+i]&&!udg[n-u+i]){
            g[u][i]='Q'; //在这里面i,u是变量 表示列数和行数 ,n是定值,表示一个n×n的矩阵
            //udg里面i-u或者u-i都行,关键是能通过i和u确定一个唯一的编号。
            col[i]=dg[i+u]=udg[i-u+n]=true;//(x+y)(x-y+n) 行数+列数 ,行数 - 列数 + n(加的n是为了防止出现负数)
            dfs(u+1);//udg[i-u+n] 这里面n是防止出现负数的,可以自己取一个合理的数字
            col[i]=dg[u+i]=udg[i-u+n]=false;
            g[u][i]='.';
        }
    }
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            g[i][j]='.';
    dfs(0);
    return 0;
}
//解法二
#include<bits/stdc++.h>
using namespace std;
const int N=20;
int n;
char g[N][N];
bool row[N],col[N],dg[N],udg[N];

void dfs(int x,int y,int s){
    if(y==n) y=0,x++;
    if(x==n){
        if(s==n){
            for(int i=0;i<n;i++) puts(g[i]);
            puts("");
        }
        return;
    }
    //不放皇后
    dfs(x,y+1,s);
    //放皇后
    if(!row[x]&&!col[y]&&!dg[x+y]&&!udg[x-y+n]){
        g[x][y]='Q';
        row[x]=col[y]=dg[x+y]=udg[x-y+n]=true;
        dfs(x,y+1,s+1);
        row[x]=col[y]=dg[x+y]=udg[x-y+n]=false;
        g[x][y]='.';
    }
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            g[i][j]='.';
    dfs(0,0,0);
    return 0;
}

关键的地方就是对角线判断判断的方法:
一种判别方式是 如果两个坐标行和列的差相等,则它们在同一条主对角线上 如果两个坐标行和列的和相等,则它们在同一条副对角线上 而本题用的是第二种方式

首先让我们来看一下矩阵的对角线有那些性质:

在这里插入图片描述

我们发现主对角线上的点的坐标(x,y)满足x=y
在蓝色这条斜线上的坐标(x,y)满足x-y=1
在粉色这条斜线上的坐标(x,y)满足x-y=-1
同样的,我们也可以吧主对角线的坐标(x,y)表示为x-y=0
所以可以用x-y来计算斜线的编号
x-y的值是从(-4+1)~(4-1)
但是数组下标(在c++)没有负数,所以我们把x-y+n当做数组下标,编号从1~(2×4-1)
同样的,对于n×n的棋盘,共有2n+1条左斜线,我们可以把向左倾斜的斜线编号(x-y+n)

在这里插入图片描述

下面来解决一下向右倾斜的斜线编号(还是以4*4的棋盘为例)
不难看出,在标红的这条向右倾斜的主对角线上点的坐标满足x+y=5
在粉色的这条斜线上x+y=4
在蓝色的这条斜线上x+y=6
同样的,向右倾斜的斜线共有(2×4-1)条,按照x+y来编号,编号为2~8,
同样的,对于n×n的棋盘,共有2n+1条右斜线,向右倾斜的斜线标号为(x+y)
这个对角线关键就是找到一个定值,就是一个特征,这条线上的点都存在这个特征。比如相加或者相减的结果是一个常数。

广度优先搜索

走迷宫问题

在这里插入图片描述
样例输出为8;

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

typedef pair<int,int> PII;

const int N=110;

int n,m;
int g[N][N];//存储地图
int d[N][N];//存储最短路径
PII q[N*N];//队列
PII Prev[N][N];//记录下前一个点是哪个,方便输出队列
/*
hh:队头
tt:队尾
*/
int bfs()
{
    int hh=0,tt=0;
    q[0]={0,0};

    memset(d,-1,sizeof d);
    d[0][0]=0;

    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};

    while(hh<=tt)
    {
        auto t=q[hh++];

        for(int i=0;i<4;i++)
        {
            int x=t.first+dx[i],y=t.second+dy[i];
            if(x>=0&&x<n&&y>=0&&y<m&&g[x][y]==0&&d[x][y]==-1)//点在边界内 并且点可以走 并且点还没有走过。
            {
                d[x][y]=d[t.first][t.second]+1; //标记该点
                Prev[x][y]=t;
                q[++tt]={x,y}; //将该点加入队列
            }
        }
    }
    int x=n-1,y=m-1;
    while(x||y){
        cout<<x<<' '<<y<<endl;
        auto t=Prev[x][y];
        x=t.first,y=t.second;
    }
    return d[n-1][m-1];
}

int main(){
    cin>>n>>m;
    
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            cin>>g[i][j];
    
    cout<<bfs()<<endl;
    return 0;
}
// 0 1 0 0 0
// 0 1 0 1 0
// 0 0 0 0 0
// 0 1 1 1 0
// 0 0 0 1 0

int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}的解释
数组来表示上下左右四个方向的移动
在这里插入图片描述

树与图的遍历:拓扑排序

树的重心

#include<string>
#include<iostream>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;

const int N=100010,M=N*2;

int n,m;
int h[N],e[M],ne[M],idx;  //h 存储头节点,e存节点的值 ne存每一个节点的next值
bool st[N];   //布尔数组存下哪个已经被遍历过

void add(int a,int b)   //插入一条由a指向b的边
{ 
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u)
{
    st[u]=true; //标记,已被搜索过

    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!st[j]) dfs(j);
    }
}

int main()
{
    memset(h,-1,sizeof h);
}

最短路

最小生成树

二分图:染色法、匈牙利算法

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值