第三章搜索与图论(一)

文章目录

  • DFS与BFS区别
  • DFS
    • 全排列
    • n皇后
  • BFS
  • 树和图的遍历
    • 树和图的存储
    • 数和图的遍历
      • 深度优先遍历
      • 宽度优先遍历
      • 图的宽搜应用

框架

image-20220807203130059

DFS与BFS区别

DFS:

执着:一直走到头,回去的时候边回去边看能不能向下走

BFS:

稳重:每次只扩展一层,不会离家太远

image-20220807203747530

算法数据结构空间特征
DFSstackO(h)不具有最短性
BFSqueueO(2h)、指数级别“最短路”

DFS

DFS中重要概念:回溯+剪枝

DFS熟称“暴搜”,最重要的是需要考虑顺序

画一棵树

全排列

给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。现在,请你按照字典序将所有的排列方法输出。输入格式
共一行,包含一个整数 n。输出格式
按字典序输出所有排列方案,每个方案占一行。数据范围
1≤n≤7
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

步骤

  1. 找到第一个解

image-20220807204519261

  1. 回溯

    image-20220807204619705

  2. 最终结果

image-20220807204721240

image-20220807204935128

没有必要区分DFS与递归,DFS就是递归。

虽然看上去是树的形式,但存储的话只会存当前路径,回溯的时候就没有了。

没有必要把整颗树存储下来。

不需要真的把栈写出来,系统会为我们做回溯。写在递归函数中,有隐藏栈来维护,不需要开额外空间。

回溯中一定要注意恢复现场,下来的时候是什么样子,回去之后就是什么样子

image-20220807205500783

#include <iostream>
using namespace std;
const int N = 10;int n; 
//将状态[路径]存储下来,当向下搜时,path上数字会逐渐填满
int path[N];
//需要知道当前位置上可以填哪些数,即清楚哪些数已经用过了。等于true,表示该点被用过了
bool st[N];void dfs(int u)
{//一开始在第0个位置,当到达第n个位置,表明均填满,此时输出即可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;//记录i已经被用过了st[i] = true;//将状态处理好后,递归至下一层dfs(u+1);//dfs结束时,表明下面的所有路都走完了,就要回溯;//回溯时注意恢复现场。出去时什么样,回来时什么样,回溯后继续运行for循环// path[u] = 0没有什么用,因为path[u]的值会被不断覆盖掉。不管是几都没问题,因此没必要恢复//path[u] = 0;st[i] = false;}
}int main()
{cin>>n;dfs(0);return 0;
}

n皇后

n− 皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

image-20220807213121695

现在给定整数 n,请你输出所有的满足条件的棋子摆法。输入格式
共一行,包含整数 n。输出格式
每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。每个方案输出完成后,输出一个空行。注意:行末不能有多余空格。输出方案的顺序任意,只要不重复且没有遗漏即可。数据范围
1≤n≤9
输入样例:
4
输出样例:
.Q..
...Q
Q...
..Q...Q.
Q...
...Q
.Q..

重要的是顺序顺序一定要想清楚

  1. 全排列

    每一行只有一个皇后

    第一行皇后可以放在哪一列

    注意剪枝

    image-20220807213502463

    提前判断当前方案是不合法的,停止向下搜索,直接回溯

    image-20220807213937481

    对角线

    两种对角线的截距b有两种:

    y-x:截距不能是负数;所以添加偏移量n

    y+x

    image-20220807214504247

    对角线的数目是2*N-1

    #include <iostream>using namespace std;const int N = 20;int n;
    //存储方案
    char g[N][N];
    //状态数组:列、正对角线、反对角线
    bool col[N],dg[N],udg[N];//对行进行遍历
    void dfs(int u)
    {//当到达第u行,当找到一组方案时if(u == n){//输出for(int i = 0;i<n;i++) puts(g[i]);puts("");return;}//从第一列开始枚举for(int i = 0 ;i<n;i++)//这一列之前没有放过并且对角线上没有放过并且反对角线上没有放过//i代表y,u代表x 反对角线 -x+y+nif(!col[i] && !dg[u+i] && !udg[-u + i +n]){//在第u行第i列放置皇后g[u][i] = 'Q';//第i列为true,表示这一列/对角线/反对角线上已经有皇后了col[i] = dg[u+i] = udg[-u+i+n] = true;dfs(u+1);//恢复现场col[i] = dg[u+i] = udg[-u+i+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 ;
    }

    时间复杂度:n*n!

  2. 上一种方式经过一步分析,即每一行放置一个皇后。

    也可以采用一种更原始的方式进行枚举八皇后问题。

    一格格枚举,每个节点代表一个格子

    n2个格子

    image-20220807221416555

    考虑格子的边界问题:如果出界,直接返回

    image-20220807221727110

    时间复杂度:2n2

    #include <iostream>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)
    {//到达y的边界后,y置为0,跳转至下一行if(y == n) y = 0,x++;//枚举到最后一行,需要停止if(x == n){//皇后个数等于n,找到一组解//s有可能小于n,有可能一个皇后都没有摆,只有n个皇后才有解if(s == n){for(int i = 0; i<n;i++){puts(g[i]);}puts("");}//注意:此处必须有return,否则无限递归导致空间不足return;}/*枚举当前格子的两种选择上一种方式有n种选择*///不放皇后:直接递归至下一个格子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 ;
    }

BFS

一圈一圈,搜索的距离离当前起点越来越远

image-20220807224840393

给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。数据保证 (1,1) 处和 (n,m) 处的数字为 0,且一定至少存在一条通路。输入格式
第一行包含两个整数 n 和 m。接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。数据范围
1≤n,m≤100
输入样例:
5 5
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
输出样例:
8

样例:

image-20220807225134408

BFS:

每个数字表示它是第几层被扩展到的。

image-20220807225341347

若采用深搜,结果不一定对。可以保证找到终点,但不能保证是最短的

image-20220807225754092

深搜没有固定框架但宽搜有固定框架。

  1. 将初始状态放到队列中
  2. 写while循环:队列不为空
    1. 每次把队头拿出来
    2. 扩展队头
    3. 结束

image-20220807230120604

假设绿点是队头,尝试向上下左右四个方向拓展

image-20220807230644637

用向量表示方向

上 (-10//横坐标减一,纵坐标不变
右 (0+1//横坐标不变,纵坐标加一
下 (+10//横坐标加一,纵坐标不变
左 (0-1//横坐标不变,纵坐标减一

image-20220807231006308

#include <iostream>
#include <algorithm>
#include <cstring>
//一般需要队列,此处手写队列
//#include <queue>
using namespace std;
//用于表示(x,y)
typedef pair<int,int> PII;
const int N = 110;
int n,m;
int g[N][N]; //存储图,不改变图的信息,只改变队列
int d[N][N]; //存储每个点到起点的距离
//队列,用于存储当前点
PII q[N*N];int bfs()
{//队头hh,队尾tt,由于队列中现在存放第一个数据,因此tt = 0 ;空队列tt = -1;q[0] = {0,0};int hh = 0 ,tt = 0;//初始化为-1memset(d,-1,sizeof d);//从[0,0]点开始走,一开始距离为0d[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++){//(x,y)表示沿着该方向可以走到哪个点int x = t.first + dx[i],y = t.second + dy[i];//判断点是否在边界以内 并且 点是可以走的 并且 这个点还没有走过// 如果已经走过,表明该点不是第一次搜到,bfs是第一次搜到的点才是最短距离// 注意 x的边界为n,y的边界为mif(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1){//当前点的距离是之前点的距离加1d[x][y] = d[t.first][t.second] + 1;//将点添加至队列中q[++tt] = {x,y};}}}//返回右下角点的距离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;}

如何显示路径–新建一个变量【数组】存储Prev,记录该位置上的点是由上面哪个点扩展而来的

#include <iostream>
#include <algorithm>
#include <cstring>
//一般需要队列,此处手写队列
//#include <queue>
using namespace std;
//用于表示(x,y)
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];int bfs()
{//队头hh,队尾tt,由于队列中现在存放第一个数据,因此tt = 0 ;空队列tt = -1;q[0] = {0,0};int hh = 0 ,tt = 0;//初始化为-1memset(d,-1,sizeof d);//从[0,0]点开始走,一开始距离为0d[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++){//(x,y)表示沿着该方向可以走到哪个点int x = t.first + dx[i],y = t.second + dy[i];//判断点是否在边界以内 并且 点是可以走的 并且 这个点还没有走过// 如果已经走过,表明该点不是第一次搜到,bfs是第一次搜到的点才是最短距离// 注意 x的边界为n,y的边界为mif(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1){//当前点的距离是之前点的距离加1d[x][y] = d[t.first][t.second] + 1;//Prev[x][y]中存储上一个点Prev[x][y] = t;//将点添加至队列中q[++tt] = {x,y};}}}//从结尾向前输出int x= n-1,y = m-1;//当x y不同时等于0的时候,前进。x、y同时为0,表明到达起点while(x||y){cout<<x<<' '<<y<<endl;//获取上一个点,二维点用auto较为方便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;}

DP问题与最短路问题是互通的,dp问题可以看作是一种特殊的最短路问题。

树和图的遍历

树和图的存储

树是一种特殊的图,即无环连通图,因此只需要考虑图的存储方式即可。

图可以分为有向图与无向图。无向图可以建立两个有向边来表示。因此无向图是一种特殊的有向图。只需要考虑有向图如何存储。

image-20220808101744791

存储方式空间场景
邻接矩阵n2适合存储稠密矩阵
邻接表[每个节点有一个单链表]

举例:四个点,开四个单链表。

每个链表存储直接可以到达的点。单链表内部次序是无关紧要的。

image-20220621093332358

当添加新的边时2->3,在链表头部进行插入节点

image-20220808102424543

添加后邻接表

image-20220808102528437

注意

  1. 邻接表使用数组而不使用vector : vector的效率不如数组快
  2. 区分使用cin、scanf的场景:当输入输出的规模在 100 0000[一百万]时 ,才必须用scanf,否则两者效率都差不多
#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;
const int N = 100010,M = N * 2;
/*
h--N个链表的链表头
e--存储链表的值,在邻接表中表示连接的节点编号,在图中表现为所有的边。
ne--每个节点的next值
*/
int h[N],e[M],ne[M],idx;//插入一条边:a->b:即在a节点对应的链表中插入节点b
void add(int a, int b)
{e[idx] = b;ne[idx] = h[a];h[a] = idx++;
}int main()
{//链表初始化:将所有的链表头初始化为-1即可memset(h,-1,sizeof h);
}

数和图的遍历

遍历时每个点只遍历一次

深度优先遍历

有向图

image-20220808104759188

遍历顺序

image-20220808104816708

image-20220808104850733

#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;
const int N = 100010,M = N * 2;
/*
h--N个链表的链表头
e--存储链表的值,在邻接表中表示连接的节点编号,在图中表现为所有的边。
ne--每个节点的next值
*/
int h[N],e[M],ne[M],idx;//每个点只需要遍历一次,需要存储bool数组表示哪些点已经遍历过了
bool st[N];
//插入一条边:a->b:即在a节点对应的链表中插入节点b
void add(int a, int b)
{e[idx] = b;ne[idx] = h[a];h[a] = idx++;
}//u表示当前已经dfs到的点
void dfs(int u)
{//首先更新状态,标记当前点已经被搜索过了st[u] = true;//遍历u的所有出边,与遍历单链表相同for(int i = h[u];i != -1; i = ne[i]){//获取节点值,即对应的图中节点编号int j = e[i];//判断条件,如果j没有被搜过,则继续搜,一条路走到黑if(!st[j]) dfs(j);}
}int main()
{//链表初始化:将所有的链表头初始化为-1即可memset(h,-1,sizeof h);//从第一个节点开始搜素dfs(1);
}
846 树的重心
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。输入格式
第一行包含整数 n,表示树的结点数。接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。数据范围
1≤n≤105
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4
  1. 无向图,建立相反方向的边
  2. 输出最小的最大值

举例:说明重心是什么

image-20220808110628526

依次枚举删除每个点后剩余部分的最大值

连通块最大值
14
26
45

image-20220808111002220

做法:

每个点删除后,剩余连通块的最大值,在所有点中找最小的

深度优先遍历可以快速算出每棵子树的大小

以去除4为例

image-20220808111505404

image-20220808111602458

时间复杂度:O(n+m)

#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;
const int N = 100010,M = N * 2;
/*
h--N个链表的链表头
e--存储链表的值,在邻接表中表示连接的节点编号,在图中表现为所有的边。
ne--每个节点的next值
*/
int h[N],e[M],ne[M],idx;
int n;
//记录全局的答案,表明最小的最大值
int ans = N;//每个点只需要遍历一次,需要存储bool数组表示哪些点已经遍历过了
bool st[N];
//插入一条边:a->b:即在a节点对应的链表中插入节点b
void add(int a, int b)
{e[idx] = b;ne[idx] = h[a];h[a] = idx++;
}//dfs返回以u为根的子树中点的数量
//u表示当前已经dfs到的点
int dfs(int u)
{//首先更新状态,标记当前点已经被搜索过了st[u] = true;//sum:记录当前以u为树根的子树的大小,用于返回值 //res:将该点删除后连通块的最大值,初始化为0//注:定义变量后一定要及时初始化int sum = 1,res = 0;//遍历u的所有出边,与遍历单链表相同for(int i = h[u];i != -1; i = ne[i]){//获取节点值,即对应的图中节点编号int j = e[i];//判断条件,如果j没有被搜过,则继续搜,一条路走到黑if(!st[j]){//s表示当前子树的大小int s = dfs(j);//当前子树也是一个连通块res = max(res,s);//当前子树是以u为根节点树的一部分sum += s;}}//计算剩余的连通块的数量 n - sumres = max(res, n - sum);//最后,res存储删除该点后最大的连通块点数ans = min(ans,res);return sum;}int main()
{//处理输入输出cin>>n;//链表初始化:将所有的链表头初始化为-1即可memset(h,-1,sizeof h);for(int i = 0;i < n;i++){int a,b;cin>>a>>b;add(a,b),add(b,a);}//从第一个节点开始搜素//为什么不是0:idx存放的是边,也就是下标,节点为对应的值。图的节点由输入决定,输入的节点最小为1。以那个点开始搜索都是一样的,以哪个点为根节点均可以dfs(1);cout<<ans<<endl;return 0;
}

宽度优先遍历

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTfsenij-1659948153547)(https://gitee.com/jgyong/blogimg/raw/master/img/image-20220808105008157.png)]

847 图中点的层次
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环。所有边的长度都是 1,点的编号为 1∼n。 //可以用宽搜求最短路请你求出 1 号点到 n 号点的最短距离,如果从 1 号点无法走到 n 号点,输出 −1。输入格式
第一行包含两个整数 n 和 m。接下来 m 行,每行包含两个整数 a 和 b,表示存在一条从 a 走到 b 的长度为 1 的边。输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。数据范围
1≤n,m≤105
输入样例:
4 5
1 2
2 3
3 4
1 3
1 4
输出样例:
1

image-20220808115152414

image-20220808115418666

宽搜图的基本框架:

  1. 将初始状态放到队列中 -->将1号点放至队列中
  2. 初始化:距离[其他点的距离为-1,头结点的距离为0]
  3. 写while循环:队列不为空
    1. 每次取得队头元素
    2. 扩展队头:扩展所有能到的点
      1. 如果x没有被遍历过[因为只有第一次遍历才是最短路径,以后的遍历都不是了]
        1. x入队
        2. 更新x的距离
  4. 结束

image-20220808120851921

最重要的是:关注思想

#include <iostream>
#include <string.h>using namespace std;
//有向图,节点与边的上限可以都设置为N
const int N = 100010;
int n,m;int h[N],e[N],ne[N],idx;
int d[N],q[N];void add(int a,int b)
{e[idx] = b;ne[idx] = h[a];h[a] = idx++;
}int bfs()
{//将0节点放置在队列中int hh = 0,tt=0;//将1号节点放在队列的0号位置q[0] = 1;//初始化距离memset(d,-1,sizeof(d));//d[节点]:初始化1号节点距离为0d[1] = 0;while(hh <= tt){//取头结点int t = q[hh++];//拓展每个点的临边for(int i = h[t];i != -1;i = ne[i]){//获取节点,进行判断int j = e[i];if(d[j] == -1){d[j] = d[t] + 1;q[++tt] = j;}}}return d[n];}int main()
{cin>>n>>m;memset(h,-1,sizeof(h));for(int i = 0 ;i < m;i++){int a,b;cin>>a>>b;add(a,b);}cout<<bfs()<<endl;return 0;
}

图的宽搜应用

最经典应用为求拓扑距

848. 有向图的拓扑序列
给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。输入格式
第一行包含两个整数 n 和 m。接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。输出格式
共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。否则输出 −1。数据范围
1≤n,m≤105
输入样例:
3 3
1 2
2 3
1 3
输出样例:
1 2 3

拓扑序列针对有向图而言,无向图没有拓扑序列

举例

image-20220808144738487

对于每条边,起点都在终点前面,它就是个拓扑序列。

拓扑序列对应图中每个边都是由前指向后的。

image-20220808144940507

并不是所有的图都有拓扑序列。有环的图没有拓扑序列。

有向无环图一定存在拓扑序列的,因此有向无环图又被称为拓扑图。

image-20220808145057939

度数:

有向图中每个点有两个度

入度:一个点有几条边进来,边数叫做入度

出度:一个点有几条边出去,边数叫做出度

image-20220808145528959

如何求拓扑序列?

拓扑序列都是由前指向后,入度为0的点都可以作为起点

入度为0,没有一条边指向我,没有任何一个节点在我前面,所有点都可以排在最前面的位置

如果有环的话,所有点的入度都不是0

image-20220808151317497

框架:

  1. 将所有入度为0的点入队
  2. 宽搜
    1. 队列不空
      1. 取队头 t
      2. 枚举t的所有出边 t->j
      3. 删掉边,使得后面节点j的入度–
      4. 如果j的入度为0[j前面的点都拍好序 放好了]
      5. j没有任何限制,j入队

image-20220808151215873

没有环的图一定可以依次解决每一个点。

image-20220808151354944

一个有向无环图,一定至少存在一个入度为0的点

image-20220808152326143

有向无环图,删除一个点后还是有向无环图。

#include <cstring>
#include <iostream>
using namespace std;const int N = 100010;
int n,m;
int h[N],e[N],ne[N],idx;
//q存储队列
int q[N];
//d存储入度
int d[N];void add(int a,int b)
{e[idx] = b,ne[idx]= h[a],h[a] = idx++;
}bool topsort()
{int hh = 0,tt = -1;//遍历所有点,将所有入度为0的点插入到队列中去for(int i = 1;i <= n;i++)if(!d[i])//从队尾插入q[++tt] = i;//while队列不空while(hh <= tt){//取出队头元素int t =q[hh++];for(int i = h[t];i != -1;i = ne[i]){//找到出边int j = e[i];//因为弹出队头,所以之后点的入度减一d[j]--;//如果入度为0,添加至队列if(d[j] == 0) q[++tt] = j;}}//如果所有点都进入队列,表明是有向无环图,n个点,一开始tt = -1//队列中次序就是拓扑序。出队的顺序是拓扑序。出队只是将指针从前向后移动一位,前面的顺序都是不变的。因此遍历完成后q中顺序就是拓扑序return tt == n-1;
}int main()
{cin>>n>>m;memset(h,-1,sizeof h);for(int i = 0;i < m;i++){int a,b;cin>>a>>b;add(a,b);//更新入度d[b]++;}if(topsort()){for(int i = 0;i < n;i++) printf("%d ",q[i]);puts("");}else puts("-1");return 0;
}

题目的答案并不是唯一的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/341101.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第三章 搜索与图论(二)

文章目录最短路朴素Dijkstra算法堆优化版的Dijkstra算法Bellman-Ford算法SPFA算法求距离判负环Floyd最短路 并不区分有向图和无向图&#xff0c;因为无向图是一种特殊的有向图。直接用有向图的算法&#xff0c;解决无向图的问题。 常见情况可以分为两大类 在图论中&#xff0…

第三章 搜索与图论(三)

文章目录朴素版PrimKruskal算法染色法匈牙利算法朴素版Prim 给定一个 n 个点 m 条边的无向图&#xff0c;图中可能存在重边和自环&#xff0c;边权可能为负数。求最小生成树的树边权重之和&#xff0c;如果最小生成树不存在则输出 impossible。给定一张边带权的无向图 G(V,E)&a…

CDF 图的含义

CDF 图用于创建经验累积分布函数图。 使用 CDF 图可以确定等于或小于 x 轴上的给定值的数据的百分比。 例如&#xff0c;在该 CDF 图中&#xff0c;大约 34% 的数据小于总脂肪值 10 克。 参考链接 1. https://www.jmp.com/support/help/zh/14-2/ba-distribution-22.shtml

rome rss_RSS阅读器使用:ROME,Spring MVC,嵌入式Jetty

rome rss在这篇文章中&#xff0c;我将展示一些创建Spring Web应用程序的指南&#xff0c;这些应用程序使用Jetty并使用名为ROME的外部库运行RSS来运行它。 一般 我最近创建了一个示例Web应用程序&#xff0c;充当RSS阅读器。 我想检查ROME以阅读RSS。 我还想使用Spring容器和…

Ubuntu系统输入中文方式

我目前知道Ubuntu有两个还算好用的中文输入法&#xff1a; Fcitx&#xff1a;它是Linux世界开源的输入法框架&#xff0c;提供 Google PinYin、ShuangPin、SunPinYin、Wubi、ZhengMa、Hong Kong 和 TaiWan繁体等输入法。 1 安装Fcitx sudo apt install fcitx-pinyin fcit…

VMWare建立于W10的共享文件夹

一、共享文件夹建立 在虚拟机设置 -> 文件夹共享&#xff0c;选择总是启用&#xff0c;点击添加&#xff1a; 直接点击下一步&#xff1a; 选择原系统共享文件夹位置&#xff0c;并命名&#xff1a; 选择启用此共享&#xff0c;并继续&#xff1a; 二、VMtools安装 虚拟机…

VSCode如何去掉Monokai主题下的绿色下划线

VScode中类似sublime的主题为Monokai&#xff0c;但是自带主题Monokai中绿色下划线令人不舒服。 在网上寻找多种方式去除下划线。终于找到一种合适的处理方式。 1 安装主题插件 在主题插件中搜索One Monokai Theme&#xff0c;下载并安装 2 配置全局主题 通过快捷键“Ctr…

解决 ZLibrary 登录/注册不了的问题

一 文章转载链接内容 转载链接&#xff1a;解决 ZLibrary 登录/注册不了的问题 - 知乎 很多小伙伴反馈说 Z-Library 能打开&#xff0c;但是不能登录。这实际上是由于官方登录入口受限导致的。话虽如此&#xff0c;我们仍然可以通过某些方法绕过这个限制。 >虽然我们注册时…

gradle入门_Gradle入门:简介

gradle入门Gradle是一种构建工具&#xff0c;可以用基于Groovy编程语言的内部DSL替换基于XML的构建脚本。 最近它吸引了很多关注&#xff0c;这就是为什么我决定仔细研究一下。 这篇博客文章是我的Gradle教程的第一部分&#xff0c;它有两个目标&#xff1a; 帮助我们安装Gr…

排队论游乐场的游乐项目_外汇游乐场

排队论游乐场的游乐项目介绍 F X Playground是基于JavaFX的原型制作工具或实时编辑器&#xff0c;它消除了编译Java代码的步骤。 这个概念并不新鲜&#xff0c;例如在网络世界中&#xff0c;有许多HTML5 游乐场提供在线编辑器&#xff0c;使开发人员可以快速原型化或尝试各种Ja…

Node.js安装及环境配置之Windows篇

原博文链接&#xff1a;Node.js安装及环境配置之Windows篇 - 刘奇云 - 博客园 from:https://www.cnblogs.com/zhouyu2017/p/6485265.html 一、安装环境 1、本机系统&#xff1a;Windows 10 Pro&#xff08;64位&#xff09; 2、Node.js&#xff1a;v6.9.2LTS&#xff08;64位…

npm WARN logfile could not create logs-dir: Error: EPERM: operation not permitted, mkdir ‘地址

场景&#xff1a;在windows系统下&#xff0c;安装node之后&#xff0c;查看npm版本&#xff0c;报错如图所示&#xff1a; 原因&#xff1a;是node目录权限不够&#xff1b; 解决方法&#xff1a;找到node目录&#xff0c;右键属性 > 安全 > 设置users用户完全控制权限…

javafx 自定义控件_JavaFX技巧10:自定义复合控件

javafx 自定义控件用JavaFX编写自定义控件是一个简单直接的过程。 需要一个控件类来控制控件的状态&#xff08;因此命名&#xff09;。 外观需要控件的外观。 而且通常不是用于自定义外观CSS文件。 控件的一种常见方法是将其正在使用的节点隐藏在其外观类中。 例如&#xff0…

虚拟机与容器 的 区别

VM和容器都可以帮助您充分利用可用的计算机硬件和软件资源。容器是块中的新孩子&#xff0c;但VM已经并且将继续在各种规模的数据中心中非常受欢迎。 如果您正在寻找在云中运行自己的服务的最佳解决方案&#xff0c;您需要了解这些虚拟化技术&#xff0c;它们如何相互比较&…

经典 Linux 协议栈——网络子系统

目录&#xff1a; 1.Linux网络子系统的分层 2.TCP/IP分层模型 3.Linux 网络协议栈 4.Linux 网卡收包时的中断处理问题 5.Linux 网络启动的准备工作 6.Linux网络包&#xff1a;中断到网络层接收 7.总结 Linux网络子系统的分层 Linux网络子系统实现需要&#xff1a; l …

Java和JavaScript之间的区别

1.简介 我们将在本文中比较Java语言和JavaScript语言。 JavaScript由Netscape开发。 它最初是用于客户端的脚本语言&#xff0c;后来又用作客户端和服务器脚本的语言。 Java由James Gosling由Sun Microsystems开发。 这些天来&#xff0c;JavaScript在服务器中以node.js的形式使…

《汇编语言》王爽实验DOS 环境 Win10 配置

下载这两个软件。 软件链接百度网盘 请输入提取码 提取码: y1j4 1. 将debug.exe放入一个文件夹中&#xff0c;用英文名&#xff0c;不要用中文。 我这里放在E盘下的Debug文件夹。 2 然后安装DOSBox软件。 安装好后在其文件目录下找到DOSBox 0.74-3 Options.bat 打开这个文件&…

硒4 Alpha –期望什么?

硒4 Alpha-期望什么&#xff1f; 早在2018年8月&#xff0c;整个测试自动化社区就受到了一个重大新闻的打击&#xff1a;Selenium的创始成员Simon Stewart在班加罗尔Selenium会议上正式确认了Selenium 4的发布日期和一些重大更新。 世界最受欢迎的Web测试自动化框架的4.0版本计…

hibernate jpa_JPA / Hibernate实体状态转换的初学者指南

hibernate jpa介绍 Hibernate将开发人员的思维方式从SQL语句转移到实体状态转换。 一旦由Hibernate主动管理实体&#xff0c;所有更改将自动传播到数据库。 操作域模型实体&#xff08;及其关联&#xff09;比编写和维护SQL语句容易得多。 如果没有ORM工具&#xff0c;则添加新…

STL容器----map

一 基本概念 1. map/multimap map/multimap属于关联式容器&#xff0c;底层结构是用二叉树实现。 其中所有元素都是pair, pair中第一个元素为key&#xff08;键值&#xff09;&#xff0c;起到索引作用&#xff0c;第二个元素为value&#xff08;实值&#xff09;&#xff0…