【算法系列】时间复杂度、深搜(连通性、剪枝)、宽搜、Flood Fill、图论

本文详细介绍了深度优先搜索(DFS)和宽度优先搜索(BFS)在排列数字、N皇后问题、迷宫、红黑树等经典问题中的实践,以及递推和递归在解决指数型、排列型和组合型问题中的运用。还涵盖了最短路模型、多源BFS、A*算法等高级搜索策略。

时间复杂度介绍

一般ACM或者笔试题的时间限制是1秒或2秒。
在这种情况下,C++代码中的操作次数控制在 1 0 7 ∼ 1 0 8 10^7∼10^8 107108 为最佳。

下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择:

  1. n ≤ 30 n≤30 n30, 指数级别, dfs+剪枝,状态压缩dp
  2. n ≤ 100 n≤100 n100 => O ( n 3 ) O(n^3) O(n3)floyd,dp,高斯消元
  3. n ≤ 1000 n≤1000 n1000 => O ( n 2 ) O(n^2) O(n2) O ( n 2 l o g n ) O(n^2logn) O(n2logn)dp,二分,朴素版Dijkstra、朴素版Prim、Bellman-Ford
  4. n ≤ 10000 n≤10000 n10000 => O ( n ∗ n ) O(n∗\sqrt{n}) O(nn )块状链表、分块、莫队
  5. n ≤ 100000 n≤100000 n100000 => O ( n l o g n ) O(nlogn) O(nlogn) => 各种sort,线段树、树状数组、set/map、heap、拓扑排序、dijkstra+heap、prim+heap、Kruskal、spfa、求凸包、求半平面交、二分、CDQ分治、整体二分、后缀数组、树链剖分、动态树
  6. n ≤ 1000000 n≤1000000 n1000000 => O ( n ) O(n) O(n), 以及常数较小的 O ( n l o g n ) O(nlogn) O(nlogn) 算法 => 单调队列、 hash、双指针扫描、并查集,kmp、AC自动机,常数比较小的 O ( n l o g n ) O(nlogn) O(nlogn) 的做法:sort、树状数组、heap、dijkstra、spfa
  7. n≤10000000n≤10000000 => O(n)O(n),双指针扫描、kmp、AC自动机、线性筛素数
  8. n ≤ 1 0 9 n≤10^9 n109 => O ( n ) O(\sqrt{n}) O(n )判断质数
  9. n ≤ 1 0 18 n≤10^{18} n1018 => O ( l o g n ) O(logn) O(logn)最大公约数,快速幂,数位DP
  10. n ≤ 1 0 1000 n≤10^{1000} n101000 => O ( ( l o g n ) 2 ) O((logn)^2) O((logn)2)高精度加减乘除
  11. n ≤ 1 0 100000 n≤10^{100000} n10100000 => O ( l o g k × l o g l o g k ) O(logk×loglogk) O(logk×loglogk)k表示位数,高精度加减、FFT/NTT
  12. int数据范围:-2147483648~2147483647、大约 2 ∗ 1 0 9 2*10^9 2109左右
  13. long long数据范围:正负 1 0 18 10^{18} 1018之间
  14. 输入的数据范围 < 1 0 5 <10^5 105,可以用cin、cout
  15. 输入的数据范围 ≥ 1 0 5 ≥10^5 105,建议用scanf、printf
  16. 2 16 ≈ 1 0 6 2^{16}≈10^6 216106 2 16 = 65536 2^{16}=65536 216=65536 2 63 ≈ 1 0 18 2^{63}≈10^{18} 2631018

前言

队列,bfs。栈,dfs,递归。
《深入理解计算机系统》
bfs: 数据结构:queue、空间:O(2^h)空间大、可以找到一条合法路径,最小步数,一层一层进行拓展,最短路。
dfs: 数据结构:stack、空间:O(n)空间小、不能找最短路径,不具有最短路性质
思路奇怪的:dfs
最短距离:bfs

技巧:

4方向偏移量:
int dx[4] = {
   
   -1, 0, 1, 0}, dy[4] = {
   
   0, 1, 0, -1};

8方向偏移量:
int dx[8] = {
   
   -1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {
   
   -1, 0, 1, 1, 1, 0, -1, -1};

8方向日字形偏移量:
int dx[8] = {
   
   -2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {
   
   1, 2, 2, 1, -1, -2, -2, -1};

注意: 对于多组输入的题目,不要忘记将判重数组初始化memset
深搜注意爆栈
深搜恢复现场:一个状态需要多次使用时才会恢复现场。

一、深搜

回溯、剪枝

1.1 深搜思想

关键:考虑顺序,遍历全部方案。
回溯时恢复现场st[]数组
path[]存路径

Flood Fill、图与树的遍历、

深搜的返回条件
//迷宫问题:是否走到最后一个点  
if(x==xb&&y==yb) return true;
//其他类搜索问题:是否搜索到最后一个点
if(cnt==n*m) {
   
   ans++;return;}
//搜索到没有解
cnt+=dfs(a,b);

1.2 基础题目

1.2.1 排列数字

在这里插入图片描述
在这里插入图片描述
假设有 3 个空位,从前往后填数字,每次填一个位置,填的数字不能和前面一样。

最开始的时候,三个空位都是空的:__ __ __

首先填写第一个空位,第一个空位可以填 1,填写后为:1 __ __

填好第一个空位,填第二个空位,第二个空位可以填 2,填写后为:1 2 __

填好第二个空位,填第三个空位,第三个空位可以填 3,填写后为: 1 2 3

这时候,空位填完,无法继续填数,所以这是一种方案,输出。

然后往后退一步,退到了状态:1 2 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 3 ,没有其他数字可以填。

因此再往后退一步,退到了状态:1 __ __。第二个空位上除了填过的 2,还可以填 3。第二个空位上填写 3,填写后为:1 3 __

填好第二个空位,填第三个空位,第三个空位可以填 2,填写后为: 1 3 2

这时候,空位填完,无法继续填数,所以这是一种方案,输出。

然后往后退一步,退到了状态:1 3 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 2,没有其他数字可以填。

因此再往后退一步,退到了状态:1 __ __。第二个空位上除了填过的 2,3,没有其他数字可以填。

因此再往后退一步,退到了状态:__ __ __。第一个空位上除了填过的 1,还可以填 2。第一个空位上填写 2,填写后为:2 __ __

填好第一个空位,填第二个空位,第二个空位可以填 1,填写后为:2 1 __

填好第二个空位,填第三个空位,第三个空位可以填 3,填写后为:2 1 3

这时候,空位填完,无法继续填数,所以这是一种方案,输出。

然后往后退一步,退到了状态:2 1 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 3,没有其他数字可以填。

因此再往后退一步,退到了状态:2 __ __。第二个空位上除了填过的 1,还可以填 3。第二个空位上填写 3,填写后为:2 3 __

填好第二个空位,填第三个空位,第三个空位可以填 1,填写后为:2 3 1

这时候,空位填完,无法继续填数,所以这是一种方案,输出。

然后往后退一步,退到了状态:2 3 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 1,没有其他数字可以填。

因此再往后退一步,退到了状态:2 __ __。第二个空位上除了填过的 1,3,没有其他数字可以填。

因此再往后退一步,退到了状态:__ __ __。第一个空位上除了填过的 1,2,还可以填 3。第一个空位上填写 3,填写后为:3 __ __

填好第一个空位,填第二个空位,第二个空位可以填 1,填写后为:3 1 __

填好第二个空位,填第三个空位,第三个空位可以填 2,填写后为:3 1 2

这时候,空位填完,无法继续填数,所以这是一种方案,输出。

然后往后退一步,退到了状态:3 1 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 2,没有其他数字可以填。

因此再往后退一步,退到了状态:3 __ __。第二个空位上除了填过的 1,还可以填 2。第二个空位上填写 2,填写后为:3 2 __

填好第二个空位,填第三个空位,第三个空位可以填 1,填写后为:3 2 1

这时候,空位填完,无法继续填数,所以这是一种方案,输出。

然后往后退一步,退到了状态:3 2 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 1,2,没有其他数字可以填。

因此再往后退一步,退到了状态:3 __ __。第二个空位上除了填过的 1,2,没有其他数字可以填。

因此再往后退一步,退到了状态:__ __ __。第一个空位上除了填过的 1,2,3,没有其他数字可以填。

此时深度优先搜索结束,输出了所有的方案。
在这里插入图片描述

算法:

  • 用 path 数组保存排列,当排列的长度为 n 时,是一种方案,输出。
  • 用 state 数组表示数字是否用过。当 state[i] 为 1 时:i 已经被用过,state[i] 为 0 时,i 没有被用过。
  • dfs(i) 表示的含义是:在 path[i] 处填写数字,然后递归的在下一个位置填写数字。
  • 回溯:第 i 个位置填写某个数字的所有情况都遍历后, 第 i 个位置填写下一个数字。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 10;
int n;
int path[N]; // 从0到n-1共n个位置 存放一个排列
bool st[N];// 存放每个数字的使用状态 true表示使用了 false表示没使用过

void dfs(int u)
{
   
   
    if(u==n)//数字填完了,输出
    {
   
   
        for(int i=0;i<n;i++)
            printf("%d ",path[i]);
        cout << endl;
        return;
    }
    
    for(int i=1;i<=n;i++)//空位上可以选择的数字为:1 ~ n
    {
   
   
        if(!st[i])//如果数字 i 没有被用过
        {
   
   
            path[u]=i;// 把 i 填入数字排列的位置上
            st[i]=true;// 表示该数字用过了 不能再用
            dfs(u+1);// 填下一位
            st[i]=false;// 恢复现场 该数字后续可用
        }
    }
}

int main()
{
   
   
    cin >> n;
    dfs(0);
    return 0;
}

1.2.2 n-皇后问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
DFS按行枚举

对角线 dg[u+i],反对角线udg[n−u+i]中的下标 u+in−u+i表示的是截距

下面分析中的(x,y)相当于上面的(u,i)

  • 反对角线 y=x+b, 截距 b=y−x,因为我们要把 b 当做数组下标来用,显然 b 不能是负的,所以我们加上 +n (实际上+n+4,+2n都行),来保证是结果是正的,即 y - x + n
  • 对角线 y=−x+b, 截距是 b=y+x,这里截距一定是正的,所以不需要加偏移量

核心目的: 找一些合法的下标来表示dgudg是否被标记过,所以如果你愿意,你取 udg[n+n−u+i] 也可以,只要所有(u,i)对可以映射过去就行

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 20;
int n;
char g[N][N];
int col[N],dg[N],udg[N];

void dfs(int u)
{
   
   
    if(u==n)
    {
   
   
        for(int i=0;i<n;i++)
            puts(g[i]);
        puts("");
    }
    
    for(int i=0;i<n;i++)
    {
   
   
        if(!col[i]&&!dg[i+u]&&!udg[i-u+n])
        {
   
   
            g[u][i]='Q';
            col[i]=dg[i+u]=udg[i-u+n]=true;
            dfs(u+1);
            col[i]=dg[u+i]=udg[n-u+i]=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;
}

DFS按每个元素枚举

每个位置都有两种情况,总共有 n^2 个位置

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 20;
char g[N][N];
int col[N],row[N],dg[N],udg[N];// 因为是一个个搜索,所以加了row
int n;

void dfs(int x,int y,int s)// s表示已经放上去的皇后个数
{
   
   
    if(y==n) x++,y=0;// 处理超出边界的情况
    
    if(x==n)// x==n说明已经枚举完n^2个位置了
    {
   
   
        if(s==n)// s==n说明成功放上去了n个皇后
        {
   
   
            for(int i=0;i<n;i++) puts(g[i]);
            puts("");
        }
        return;
    }
    
    //不放皇后
    dfs(x,y+1,s);
    
    //放皇后
    if(!col[x]&&!row[y]&&!dg[y-x+n]&&!udg[y+x])
    {
   
   
        g[x][y]='Q';
        col[x]=row[y]=dg[y-x+n]=udg[y+x]=true;
        dfs(x,y+1,s+1);
        col[x]=row[y]=dg[y-x+n]=udg[y+x]=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;
}

1.3 DFS中的连通性(能走到,不能保证最短)DFS、BFS均可以求解

1.3.1 迷宫

在这里插入图片描述
在这里插入图片描述
算法思想:
只能保证搜到,不能保证最短。
相对BFS,DFS(用系统栈)代码短一些。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 110;
char g[N][N];
bool st[N][N];
int k,n;
int xa,ya,xb,yb;

bool dfs(int x,int y)
{
   
   
    if(g[x][y]=='#') return false;
    if(x==xb&&y==yb) return true;
    
    st[x][y]=true;
    
    int dx[4] 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

For 丶I Forever

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值