搜索与图论

第三章 搜索与图论

1.深度优先搜索DFS

一条路走到黑

数字全排列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g3u66CKm-1657019682316)(C:\Users\ZBY\Desktop\Snipaste_2022-06-22_18-43-39.png)]

#include <iostream>
using namespace std;
#include <vector>
#include <cstring>
int n;
const int N = 10;
int path[N];
bool st[N];
void dfs(int startindex,int path[],bool st[]){//startindex表示的是
    //当前所在的树的层数,也表示的是枚举到了哪一个位置
    if(startindex == n){
        for(int i = 0;i<n;i++){
            printf("%d ",path[i]);
        }
        printf("\n");
        return;
    }
    //枚举当前位置能插入的数的集合
    for(int i = 1;i<=n;i++){//横向遍历所有的数字
    //如果该数没有使用过,则此层中可以放下这个数
        if(!st[i]){
            path[startindex] = i;
            st[i] = true;
            dfs(startindex+1,path,st);
            //path[startindex] = 0;//在这里,这一个恢复现场的可以不写
            st[i] = false;
        }
    }
    return;
}

int main(){
    scanf("%d",&n);
    dfs(0,path,st);

    return 0;
}

n皇后问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jObvgjLF-1657019682318)(C:\Users\ZBY\Desktop\Snipaste_2022-06-22_19-21-23.png)]

#include <iostream>
using namespace std;
const int N =20;
char path[N][N];
//用来判断当前行,列,正对角线,反对角线中是否存在皇后
bool row[N],col[N],zhengduijiao[N],fanduijiao[N];
int n;

void dfs(int startindex,char path[][N]){
    if(startindex == n){
        for(int i= 0;i<n;i++){
            puts(path[i]);//输出路径
        }
        puts("");
        return;
    }
    //横向进行枚举,现在即在枚举列
    //注意:在横向枚举的时候,不需要判断当前行是否有皇后,因为是从第一行枚举到的最后一行的一次枚举
    //所以当枚举到第startindex行的时候,这一行一定没有皇后!!!
    for(int i = 0;i<n;i++){
    //	注意:反对角线的下标表征用横纵坐标相加,正对角线的下标表征是用横纵坐标相减(加上偏移量是为了防止下标为负)!
        if(!col[i] && !zhengduijiao[startindex+i] &&
         !fanduijiao[startindex - i + 10]){ //当前列,当前正对角线,反对角线都没有皇后
         //注意:正对角线中加上10这个偏移量是为了保证数组的下标为正
            path[i][startindex] = 'Q';
            col[i] = fanduijiao[startindex + i] =zhengduijiao[startindex - i + 10] = true;
            dfs(startindex+1,path);
            //回溯
            path[i][startindex] = '.';
            col[i] =fanduijiao[startindex + i]=zhengduijiao[startindex-i+10] = false;
         }
    }
    return;
}

int main(){
	//读入n×n的棋盘
    scanf("%d",&n);
    for(int i = 0;i<n;i++){
        for(int j = 0;j < n;j++){
            path[i][j] = '.';
        }
    }
    dfs(0,path);
    return 0;
}

2.宽度优先搜索BFS

一层一层的搜索

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aWdl8Wjg-1657019682319)(C:\Users\ZBY\Desktop\Snipaste_2022-06-22_18-41-02.png)]

走迷宫

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vAXVXdlS-1657019682319)(Snipaste_2022-06-22_20-27-33.png)]

宽度优先搜索的模板

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YVwH4Guv-1657019682320)(Snipaste_2022-06-22_20-21-41.png)]

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 110;
int m,n;
typedef pair<int,int> PII;//用来表示点
int g[N][N],d[N][N];//g用来表示点,
//d[i][j]表示的是i,j点到原点的距离,初始化距离为-1表示这个点还没有走过
//g[i][j]为1表示点有障碍,为0表示空地

int bfs(){
    queue<PII> q;
    //注意:初始化队列,初始化距离
    d[0][0] = 0;
    q.push({0,0});
    while(!q.empty()){
        //注意:每次取出队头的时候需要删除队头元素
        auto t = q.front();
        q.pop();
        //四个方向位移的技巧,肯定是这四个数:-1,1,0,0(顺序匹配即可)
        int dx[4] = {-1,1,0,0},dy[4] = {0,0,-1,1};//左右上下
        for(int i =0;i<4;i++){
            int x = t.first + dx[i],y =t.second + dy[i];
            //g[x][y] != 1没障碍     d[x][y] ==-1还没走过
            //没越界,没障碍,没走过
            if(x>=0 && x<m && y>=0 && y<n && g[x][y] != 1 && d[x][y] ==-1){
                    d[x][y] = d[t.first][t.second] + 1;
                    q.push({x,y});
                }
        }
    }
    return d[m-1][n-1];
}

int main(){
//	m行n列的迷宫
    scanf("%d%d",&m,&n);
    for(int i = 0;i<m;i++){
        for(int j= 0;j<n;j++){
            cin >> g[i][j];
        }
    }
    //初始化所有点到原点的距离为-1,表示点未走过
    memset(d,-1,sizeof(d));
    int res = bfs();
    printf("%d\",res);
    return 0;
}

3.树与图的存储

树是一种特殊的图:无环连通图

无向图是一种特殊的有向图

因此,只需要考虑有向图的存储

两种存储方式:

  1. 邻接矩阵
  2. 邻接表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6XNSaBcW-1657019682321)(Snipaste_2022-06-23_13-38-41.png)]

4.树与图的深度优先遍历

一般情况下,每一个点都只需要遍历一次

  • 因此需要开辟一个数组来存哪些点已经遍历过

深度优先遍历中可以求出每一个子树的点的数量

#include <iostream>
using namespace std;
#include <cstring>
const int N = 1000010,M = N * 2;
int h[N],e[M],ne[M],idx;
//邻接表的存储方法
//h表示的是所有点,e,ne存储所有的边
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]){
    //	j是i点能到达的所有点
        int j = e[i];
        if(!st[j]) dfs(j);
    }
}

int main(){
    memset(h,-1,sizeof(h));
    //从第一个点开始深搜
    dfs(1);
    return 0;
}

树的重心

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Yy9s4Ph-1657019682321)(Snipaste_2022-06-23_13-42-57.png)]

#include <iostream>
using namespace std;
#include <cstring>
const int N = 1000010,M = N * 2;
int h[N],e[M],ne[M],idx;
//邻接表的存储方法
//h表示的是所有点,e,ne存储所有的边
bool st[N];
int ans = N;//ans用来表示最后的结果
int n;
void add(int a,int b){//建立一条从a点到b点的边
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

int dfs(int u){//dfs返回u这个结点为根的树中点的数量
    //表示这个点已经被遍历
    st[u] = true;
    //res存储的是把当前这个点删除后所有连通块中点的数目的最大值
    //sum是用来作为dfs的返回值的
    int sum = 1,res=0;
    //遍历这个点能到的所有的点
    for(int i = h[u];i!=-1;i=ne[i]){
        int j = e[i];
        if(!st[j]){
            //s是以j这个结点为根的树中点的数量
            //s是u这个点能到的所有的下一个点j
            //j点为根结点的树的点的数量
            int s = dfs(j);
            res = max(s,res);
            sum += s;
        }
    }
    //n-sum是这个结点上面的那一个连通块的点的数量
    res = max(res,n-sum);
    //ans记录全局的答案
    ans = min(ans,res);
    //将以u为根节点的树的点的数量返回回去
    return sum;
}

int main(){
    //h数组为邻接表的头部
    memset(h,-1,sizeof(h));
    scanf("%d",&n);//n个点,n-1条边
    for(int i = 1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);//加入边
        add(b,a);//注意,树为无向边
    }
    //从几号点开始搜都可以
    dfs(1);
    cout << ans << endl;
    return 0;
}

5.树与图的宽度优先遍历

因为可以用距离来标记:距离为-1表示点还未走过,不为-1表示点走过

因此不需要额外再开辟一个数组来存储点是否被遍历过!

图中点的层次

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-deBmmKbg-1657019682322)(Snipaste_2022-06-23_18-15-22.png)]

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 100010;
int h[N],e[N],ne[N],idx;
int d[N];
int n,m;
int bfs(){
    //-1表示还没到这个点
    memset(d,-1,sizeof(d));
    queue<int> q;
    d[1] = 0;
    q.push(1);
    while(!q.empty()){
        int t = q.front();
        q.pop();
        for(int i = h[t];i!=-1;i=ne[i]){
            int j = e[i];
            if(d[j] == -1){
                d[j] = d[t] + 1;
                q.push(j);
            }
        }
    }
    return d[n];
}

void add(int a,int b){
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

int main(){
    memset(h,-1,sizeof(h));
    scanf("%d%d",&n,&m);
    for(int i =0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);//重边和自环不影响?
    }
    cout << bfs() <<endl;
    return 0;
}

有向图的宽搜的应用

————有向图的拓扑序列

  • 所有入度为0的点都能作为第一个点

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-py9qsCbM-1657019682322)(Snipaste_2022-06-23_18-55-16.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E24p2gnG-1657019682323)(Snipaste_2022-06-23_19-02-07.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trWyVAjT-1657019682323)(Snipaste_2022-06-23_19-05-41.png)]

#include <iostream>
using namespace std;
//#include <queue>
#include <cstring>
const int N = 100010;
//注意:本题需要手写队列,因为只有所有的点都入队了,才说明存在拓扑序列
int h[N],e[N],ne[N],idx;
int d[N];//d[i]表示的是i点的入度的数
int m,n;
int q[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] == 0){
            q[++tt] = i;
        }
    }
    while(hh <= tt){
        //出队的顺序就是拓扑序
        int t = q[hh++];
        for(int i =h[t];i!=-1;i=ne[i]){
            int j = e[i];
            d[j]--;
            if(d[j] == 0){
                q[++tt] = j;
            }
        }
    }
    //如果存在拓扑序的话,则每一个点都应该入队过
    //最后的拓扑序为进入队列的顺序
    return tt == n-1;
}

int main(){
    memset(h,-1,sizeof(h));
    scanf("%d%d",&n,&m);
    for(int i =0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        d[b]++;//b点的入度加1
    }
    if(!topsort()){
        puts("-1");
    }
    else{//说明存在拓扑序
        for(int i =0;i<n;i++){
            cout << q[i] <<" ";
        }
        printf("\n");
    }
    return 0;
}

6.最短路问题

源点,即起点

汇点,即终点

稠密图:m(边数)和n(点数)平方一个级别

稀疏图:m和n一个级别时

有向图和无向图的最短路问题,没什么区别,有向图连一条边,无向图连两条边

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNYdXljh-1657019682324)(Snipaste_2022-06-24_15-44-16.png)]

朴素版Dijkstra算法(稠密图)

思路:每一次利用已经确定最短路的点,来更新其他点到起点的距离

由于是稠密图,故利用邻接矩阵来存储

Dijkstra求最短路I

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5p8RHpX-1657019682325)(Snipaste_2022-06-24_16-07-20.png)]

对于重边和自环:

  • 自环显然不会出现在最短路的问题中 因此自环不需要处理
  • 重边出现时,保留一条最短的边即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R08EQu2P-1657019682325)(Snipaste_2022-06-24_15-58-06.png)]

#include <iostream>
using namespace std;
#include <cstring>
const int N = 510;
int g[N][N];//g[i][j]表示的是i点到j点的距离
int dist[N];//dist[i]表示i点到起点的距离
bool st[N];//st[i]为true表示i点已经确定了最短路
int m,n;

int dijkstra(){
    //初始化所有点到源点的距离为正无穷大
    memset(dist,0x3f,sizeof(dist));
    dist[1] = 0;
    //n次遍历,每一次找到当前还没确定最短路,
    //且距离源点最近的点t
    for(int i =0;i<n;i++){
        int t = -1;
        //遍历所有的点n,找到那个t点
        for(int j = 1;j<=n;j++){
            if(!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        }//到此,找到了对应的t点
        //此时的这个t点,已经找到了它到源点的最短距离,标记为true
        st[t] = true;
        //再利用t点去更新其他点到源点的距离
        for(int i = 1;i<=n;i++){
            dist[i] = min(dist[t] + g[t][i],dist[i]);
        }
    }//到此,n次遍历结束
    //距离依然是正无穷,说明到不了n号点
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main(){
    scanf("%d%d",&n,&m);
    //初始化所有的边距离为正无穷
    memset(g,0x3f,sizeof(g));
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        //更新a点和b点的距离
        //有多条边则取最短的那一条边即可
        g[a][b] = min(g[a][b],c);
    }
    //返回从1号点到n号点的最短路径
    int res = dijkstra();
    printf("%d\n",res);
    return 0;
}

堆优化版的Dijkstra算法(稀疏图)

优化的地方:朴素版的Dijkstra算法中,最慢的地方在于每一次找当前还没确定最短路的点中距离源点最近的点,这个过程利用堆来优化,那么每一次取出堆顶,堆顶即为距离最近的那一个点,就可以实现再O(1)的时间复杂度得到

稀疏图用邻接表来存储,而用邻接表来存储时,重边就无所谓了,算法会保证最短路

Dijkstra求最短路II

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rfLgRwB2-1657019682325)(Snipaste_2022-06-24_18-34-17.png)]

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N =100010;
int h[N],e[N],ne[N],w[N],idx;
int dist[N];
bool st[N];
int m,n;
typedef pair<int,int> PII;

void add(int a,int b,int c){//加一条从a点到b点的边,边权重为c
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}

int dijkstra(){
    memset(dist,0x3f,sizeof(dist));
    //初始化,1号点到源点的距离为0
    dist[1] = 0;
    //定义小根堆,注意小根堆里不支持对点进行修改,(但是可以删除)
    //因此会出现一些点已经找过最短路,还在小根堆中的现象
    //vector<PII>表示堆是用这个容器来实现的
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    //将当前还没确定最短路的且与源点距离最近的点加入到小根堆中
    heap.push({0,1});
    while(heap.size()){
        //每次取出的那一个点,即为当前还没确定最短路的且与源点距离最近的点
        auto t = heap.top();
        heap.pop();
        int distance = t.first,point = t.second;
        //当前这个点有可能已经更新过最短路径
        //if(st[point]) continue;//这一行不需要???
        //利用当前这一个点更新其他的点
        for(int i = h[point];i!= -1;i= ne[i]){
            int j = e[i];
            if(dist[j] > dist[point] + w[i]){
                //用point到源点的距离加上point到j点的距离更新j到源点的距离
                dist[j] = dist[point] + w[i];
                heap.push({dist[j],j});
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main(){
    //初始化临界表
    memset(h,-1,sizeof(h));
    scanf("%d%d",&n,&m);//n个点,m条有向边
    while(m--){
        int a,b,c;
        //加一条从a到b的权重为c的边
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    int res = dijkstra();
    printf("%d\n",res);
    return 0;
}

Bellman-Ford算法(负权图,限制边数)

用来求存在负权边的最短路问题

用途:用来解决题目中限制边数的最短路问题,且存在负权边的情形

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LIjr5EDg-1657019682326)(Snipaste_2022-06-24_18-45-48.png)]

如果图中有负权回路的话,且负权回路出现在从1号点到n号点的路径中,则不存在最短路!

如果存在负权回路,但不出现在路径中,也可用Bellman-Ford算法,此时也可以求出最短路

算法思路:每次枚举所有的边!

有边权限制的最短路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V1kunare-1657019682327)(Snipaste_2022-06-24_18-55-52.png)]

#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int dist[N];
int backup[N];
int m,n,k;
struct {
    int a,b,c;//从a点到b点的边权为c
}edge[N];

int bellman_ford(){
    //初始化
    memset(dist,0x3f,sizeof(dist));
    dist[1] =0;

    for(int i = 0;i<k;i++){
        //为了防止串连,即为了保证每一次更新都只更新一条边
        //当前的路径应该用上一次迭代的结果进行更新
        memcpy(backup,dist,sizeof(dist));
        //遍历每一条边
        for(int j = 0;j<m;j++){
            int a = edge[j].a,b=edge[j].b,c = edge[j].c;
            dist[b] = min(dist[b],backup[a] + c);
        }
    }
    //与n点相连的点可能会更新了dist[n],那么就算dist[n]就不再是0x3f3f3f3f了,会稍微比0x3f3f3f3f小一些
    if(dist[n] > 0x3f3f3f3f/2) return -1;
    return dist[n];
}

int main(){
    //从1号点到n号点的最短路,之间经过不超过k条边
    scanf("%d%d%d",&n,&m,&k);//k表示的是不经过k条边
    for(int i =0;i<m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        edge[i] = {a,b,c};//加入边
    }
    int res = bellman_ford();
    printf("%d\n",res);
    return 0;
}

SPFA算法(利用队列进行优化)

只有当前的点到源点的距离变点,其能到的下一个点的距离才有可能变短

只有距离可能变短的点才会被加入到队列中去[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ThdZCad5-1657019682328)(Snipaste_2022-06-24_19-32-08.png)]

SPFA求最短路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nZd3B4nL-1657019682336)(Snipaste_2022-06-24_19-32-27.png)]

#include <iostream>
using namespace std;
#include <cstring>
const int N = 100010;
#include <queue>
int h[N],dist[N],e[N],ne[N],w[N],idx;
bool st[N];//表示当前这个点是否已经在队列中,在则为true
int m,n;

void add(int a,int b,int c){
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}

int spfa(){
    queue<int> q;
    //队列q保存的点是需要更新距离的点
    //只有距离缩短了的点才会被加入队列中,才会被用来更新其他的点
    memset(dist,0x3f,sizeof(dist));
    dist[1] = 0;
    q.push(1);
    while(q.size()){
        int t =q.front();
        q.pop();
        st[t] = false;//t这个点已经不在队列中了
        for(int i =h[t];i!=-1;i = ne[i]){
            int j = e[i];
            //注意:只有距离变短的那些点,才会被放入到队列中去
            //这是SPFA算法的优化的地方!
            if(dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                //如果j这个点不在队列中,则放入队列中
                if(!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main(){
    memset(h,-1,sizeof(h));
    scanf("%d%d",&n,&m);
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    int res = spfa();
    printf("%d\n",res);
    return 0;
}
SPFA判断负环

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zHMXJWaf-1657019682336)(Snipaste_2022-06-25_11-04-12.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6yK0qma-1657019682337)(Snipaste_2022-06-25_11-04-24.png)]

#include <iostream>
using namespace std;
#include <queue>
#include <cstring>
const int N = 100010;
int e[N],ne[N],h[N],w[N],idx;
bool st[N];
int cnt[N],dist[N];//cnt[i]表示从1号点到i号点经过的边的数量
int m,n;

void add(int a,int b,int c){//加入一条从a到b的边,边权为c
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}

int spfa(){
    queue<int> q;
    for(int i =1;i<=n;i++){//将所有的点都加入队列中
        q.push(i);
        //如果i点在队列中,st[i]为true
        st[i] = true;
    }    
    while(q.size()){
        //注意:这里不需要初始化dist[]距离,默认开始都是0
        //然后因为所有点都会被加入到队列当中去,因此所有点都有更新其他点距离的机会
        //这里的dist[i]其实并非从1号到i点的距离,如果存在负权环,会不断循环
        //dist[i]中的值会一直变小到负无穷,而又因为点为n的图,若有大于等于n条边,说明有环
        //若有n条边,说明至少有n+1个点,由抽屉原理,说明有两个点是同一个点,说明一定有负权环
        int t = q.front();
        q.pop();
        //t点删除,不在队列中
        st[t] = false;
        //枚举t所有能到的点
        for(int i =h[t];i!=-1;i=ne[i]){
            //j点为t点能到达的点
            int j = e[i];
            if(dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if(cnt[j] >= n) return true;//存在负环
                if(!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return false;
}

int main(){
    //初始化多个链表的头
    memset(h,-1,sizeof(h));
    scanf("%d%d",&n,&m);
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    int res = spfa();
    if(spfa()) puts("Yes");
    else puts("No");
    return 0;
}

Floyd算法(多源最短路)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VLh1Pen2-1657019682338)(Snipaste_2022-06-25_13-09-27.png)]

Floyd求最短路

必须保证不能存在负权回路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WZoFRfLj-1657019682339)(Snipaste_2022-06-25_13-11-33.png)]

#include <iostream>
using namespace std;
const int N = 2010;
int d[N][N],INF = 0x3f3f3f3f;
int m,n,q;

void floyd(){
	//基于动态规划
    for(int k = 1;k<=n;k++){
        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=n;j++){
                d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}

int main(){
    scanf("%d%d%d",&n,&m,&q);
    //初始化l矩阵
    for(int i =1;i<=n;i++){
        for(int j = 1;j<=n;j++){
            if(i == j) d[i][j] =0;
            else d[i][j] = INF;
        }
    }
    //加入m条边,只保留短的那些边
    while(m--){
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        d[a][b] = min(d[a][b],w);
    }
    floyd();
    //q个询问
    while(q--){
        int i,j;
        scanf("%d%d",&i,&j);
        if(d[i][j] > INF/2) puts("impossible");
        else printf("%d\n",d[i][j]);
    }
    return 0;
}

7.最小生成树

最小生成树问题对应的图一般是无向图

最小生成树里边正边和负边都没有关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ftYZ7gJA-1657019682339)(Snipaste_2022-06-25_21-17-24.png)]

朴素版Prim算法

算法流程与朴素版的Dijkstra算法很相似

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dR22o5NH-1657019682340)(Snipaste_2022-06-25_21-25-33.png)]

Prim算法求最小生成树(稠密图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jHac1xcb-1657019682341)(Snipaste_2022-06-25_21-26-07.png)]

#include <iostream>
using namespace std;
#include <cstring>
const int N = 510,INF = 0x3f3f3f3f;
int g[N][N];
int dist[N];//dist[i]表示的是当前i点到集合中的距离
bool st[N];//st[i]为true表示i点已经在集合当中
//即i点已经在最小生成树当中
int m,n;

int prim(){
    //初始化距离
    memset(dist,0x3f,sizeof(dist));
    //n次循环
    int res = 0;//保存最小生成树的总长
    for(int i =0;i<n;i++){
        //每一次循环,找出还没到最小生成树当中的且离集合距离最小的点t
        int t = -1;
        for(int j= 1;j<=n;j++){//从n个点中找到一个点
            if(!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        }//到此找到这个点t
        //如果t这个点不是第一个点,且这个最小的距离还是正无穷,说明根本到不了集合
        if(i>0 && dist[t] == INF) return INF;
        //否则说明可以到集合中,因此,将这个点加入集合
        st[t] = true;
        //更新生成树的距离,注意:如果是第一个点就不需要更新距离
        if(i) res+=dist[t];
        //用t来更新其他点到集合当中的距离
        for(int j = 1;j<=n;j++){
            dist[j] = min(dist[j],g[j][t]);
        }
    }
    return res;
}

int main(){
    memset(g,0x3f,sizeof(g));
    scanf("%d%d",&n,&m);//n个点,m条边
    while(m--){
        int a,b,c;//a到b的边长为c
        scanf("%d%d%d",&a,&b,&c);
        g[a][b] = g[b][a] = min(g[a][b],c);
    }
    int res = prim();
    if(res == INF) puts("impossible");
    else printf("%d\n",res);
    return 0;
}

Kruskal算法(稀疏图)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d246zzvk-1657019682342)(Snipaste_2022-06-26_12-20-14.png)]

只用到了所有的边,因此直接所有的边保存在结构体里面即可

Kruskal算法求最小生成树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ipnJXR8z-1657019682343)(Snipaste_2022-06-26_12-23-47.png)]

#include <iostream>
using namespace std;
#include <algorithm>
const int N = 200010;
int m,n;
int p[N];//并查集里的p数组
struct edge{
    int a,b,w;
    //重载一下小于号,按照边从小到大进行排序
    bool operator<(const edge& e){
        return w < e.w;
    }
}edges[N];//结构体数组

int find(int x){//找到x结点的祖宗结点+路径压缩
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i =0;i<m;i++){
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        //读入所有的边
        //edges[i].a = a,edges[i].b = b,edges[i].w = w;(简单写法如下)
        edges[i] = {a,b,w};
    }
    for(int i =1;i<=n;i++) p[i] = i;//初始化并查集
    //将所有的边重小到大进行排序
    sort(edges,edges+m);
    //从小到大枚举所有的边
    int res = 0,cnt = 0;//res是总的边权重,cnt是边的个数
    for(int i =0;i<m;i++){
        int a = edges[i].a,b = edges[i].b,w = edges[i].w;
        //如果a和b不在同一个集合里面,则把边加入
        a = find(a),b = find(b);
        if(a != b){
            p[a] = b;
            res += w;
            cnt++;
        }
    }
    //最后看一下总共的边的数量
    if(cnt < n-1) puts("impossible");
    else printf("%d\n",res);
    return 0;
}

8.二分图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCS4NgiS-1657019682343)(Snipaste_2022-06-26_18-34-35.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CB9P3G1K-1657019682343)(Snipaste_2022-06-26_18-38-04.png)]

染色法

判断一个图是否为二分图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mbpeeI27-1657019682344)(Snipaste_2022-06-26_18-45-39.png)]

#include <iostream>
using namespace std;
#include <cstring>
const int N = 100010,M = 2*N;//N个点M条边
int h[N],e[M],ne[M],idx;//e[]和ne[]数组存储边数M
int color[N];//color[i]为0表示未染色,
//为1表示染其中一种颜色,为-1表示染了另外一种颜色
int m,n;

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

bool dfs(int u,int c){//将u号点,染色成c
    color[u] = c;
    for(int i = h[u];i!=-1;i = ne[i]){
        int j = e[i];
        if(!color[j]){//如果未染色,对其进行染色
            //u点染了c颜色,j点染-c颜色
            //如果染色失败,则返回false
            if(!dfs(j,-c)) return false;
        }
        else if(color[j] == color[u]){//如果染了色,但是染的颜色相同
            return false;
        }
    }
    //说明染色成功
    return true;
}

int main(){
    scanf("%d%d",&n,&m);//n个点,m条边
    memset(h,-1,sizeof(h));
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }  
    int flag = true;
    for(int i =1;i<=n;i++){
        //遍历所有的点
        if(!color[i]){//如果当前这个点还未染色
            //dfs进行染色,染成1,如果染色出现了冲突,返回false
            if(!dfs(i,1)){
                flag = false;
                break;
            }
        }
    }
    if(flag) puts("Yes");
    else puts("No");
    return 0;
}

匈牙利算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UsTtamWH-1657019682344)(Snipaste_2022-06-26_19-22-41.png)]

二分图的最大匹配

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6pE0bXsO-1657019682345)(Snipaste_2022-06-26_19-28-02.png)]

#include <iostream>
using namespace std;
#include <cstring>
const int N = 510,M = 100010;
int h[N],e[M],ne[M],idx;
int match[N];//match[i]表示的是女生i匹配的男生是谁,为0表示还未匹配
bool st[N];//某一个点i被搜过后,标记为true,防止重复搜某个点
//即为了保证每一个男得找女的的时候,不多次找同一个女的
int n1,n2,m;//左边n1个点,右边n2个点,m条边

void add(int a,int b){//加入一条从a到b的边
    e[idx] = b,ne[idx] = h[a],h[a] =idx++;
}

bool find(int x){//如果男x匹配到女的返回true
//  找到所有和男有关系的女
    for(int i = h[x];i!=-1;i = ne[i]){
        int j = e[i];
        //如果这个女没搜过,则标记为搜过
        if(!st[j]){
            st[j] = true;
            //如果这个女的没有匹配,或者这个女的匹配的男的可以匹配别人
            if(match[j] == 0 || find(match[j])){
            	match[j] = x;
            	return t
            }
        }
    }
    return false;
}

int main(){
    scanf("%d%d%d",&n1,&n2,&m);
    memset(h,-1,sizeof(h));//初始化邻接表
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);//注意:只需要存有向的边即可,从男指向女
    }
    int res = 0;
    for(int i = 1;i<=n1;i++){//遍历每一个点
        memset(st,false,sizeof(st));//每次都需要把所有女的初始化为未搜过
        if(find(i)) res++;
    }
    printf("%d\n",res);
    return 0;
}

第四章 数学知识

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WyPGkPPO-1657019682345)(Snipaste_2022-06-27_12-13-25.png)]

1.数论

质数的判定

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-So0Jqadx-1657019682346)(Snipaste_2022-06-27_12-20-16.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nVlAixcO-1657019682346)(Snipaste_2022-06-27_12-15-55.png)]

#include <iostream>
using namespace std;

bool is_prime(int x){
    if(x < 2) return false;//如果x小于2不是质数
    //枚举从2到根号x的所有数,看是否为x的约束,是的话说明x为合数,返回flase
    for(int i = 2;i<= x / i;i++){
        if(x % i == 0) return false;
    }
    //否则返回true
    return true;
}
分解质因数

n中最多只包含一个大于sprt(n)的质因子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZdYcTBHX-1657019682347)(Snipaste_2022-06-27_12-28-38.png)]

时间复杂度:O(sqrt(n))

#include <iostream>
using namespace std;

void divide(int n){//将n分解质因数
    for(int i = 2;i<=n / i;i++){
        //因为i是从最小的2开始枚举的,因此每一次枚举的合数一定不会被n整除
        //因为枚举到合数i就肯定枚举了2~i-1的质数
        if(n % i == 0){
            int cnt = 0;
            while(n % i == 0){
                //当前这个质因子i的个数为cnt
                cnt++,n/=i;
            }
            printf("%d %d\n",i,cnt);
        }
    }
    //最后判断是否有一个大于根号n的质因数
    if(n > 1 ) printf("%d %d\n",n,1);
    return;
}
筛质数

埃氏筛法

时间复杂度:近似于O(n)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DP9xUaPj-1657019682347)(Snipaste_2022-06-27_15-10-52.png)]

#include <iostream>
using namespace std;
const int N = 1e9;
int is_prime[N],cnt;
bool st[N];//st[i]为true表示i已经被筛选过了
//st[i]为false表示i为质数,st[i]为true表示i为合数
void get_prime(int n){//得到从2-n之间的所有质数
    for(int i = 2;i<=n;i++){
        //如果i点没有被筛掉,说明为质数
        if(!st[i]){
            is_prime[cnt++] = i;
            for(int j = i+i;j<=n;j+=i){
                //所有i的倍数j都是合数
                st[j] = true;
            }
        }
    }
    return;
}//所有的质数都被放到is_prime数组中
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值