AcWing算法基础课复习——(三)搜索与图论

目录

一、DFS

AcWing 842. 排列数字

AcWing 843. n-皇后问题

二、BFS

AcWing 844. 走迷宫

AcWing 845. 八数码

三、树与图的深度优先遍历

AcWing 846. 树的重心

四、树与图的广度优先遍历

AcWing 847. 图中点的层次

五、拓扑排序

AcWing 848. 有向图的拓扑序列

 六、Dijkstra

AcWing 849. Dijkstra求最短路 I

AcWing 850. Dijkstra求最短路 II

七、Bellman-ford

AcWing 853. 有边数限制的最短路

八、Spfa

AcWing 851. spfa求最短路

AcWing 852. spfa判断负环

九、Floyd

AcWing 854. Floyd求最短路

 十、Prim

AcWing 858. Prim算法求最小生成树

十一、Kruskal

AcWing 859. Kruskal算法求最小生成树

十二、染色法判定二分图

AcWing 860. 染色法判定二分图

十三、匈牙利算法

AcWing 861. 二分图的最大匹配


一、DFS

AcWing 842. 排列数字

思路:

DFS得到全排列,flag[i]表示i是否已经被用过,当选了n个数时,即是一种方案,并将该方案输出

代码:

import java.io.*;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n;
    static int N=10;
    static int a[]=new int[N];
    static boolean flag[]=new boolean[N];
    public static void dfs(int u){
        if(u==n){//已填满n个数
            for(int i=0;i<n;i++) pw.print(a[i]+" ");
            pw.println();
            return;
        }
        for(int i=1;i<=n;i++){
            if(!flag[i]){//当前数没有被使用过
                a[u]=i;
                flag[i]=true;
                dfs(u+1);
                flag[i]=false;
            }
        }
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        dfs(0);
        pw.close();
    }
}

AcWing 843. n-皇后问题

思路:

由于任意两个皇后不能处于同一行、同一列、同一斜线线上,因此开辟三个数组col,dg,udg数组,分别该列、对角线、反对角线是否能放置皇后,只有三个数组全为false时才可放皇后,利用dfs解决;
记绿色为反对角线udg,则每个点映射到y轴上为u-i,由于u-i可能为负数,因此同时加上n,因此用udg[u-i+n]表示第u行第i列所在反对角线是否已经放置过皇后
记蓝色为对角线dg,则每个点映射到y轴上为u+i,因此用dg[u+i]表示第u行第i列所在对角线是否已经放置过皇后

代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class Main {
    static BufferedReader bf=new BufferedReader(new InputStreamReader(System.in));
    static PrintWriter pw=new PrintWriter(System.out);
    static int n;
    static int N=20;
    static char g[][]=new char[N][N];
    static boolean col[]=new boolean[N];//看该列是否满足条件
    static boolean dg[]=new boolean[N];//看该对角线是否满足条件
    static boolean udg[]=new boolean[N];//看该反对角线是否满足条件
    public static void dfs(int u){
        if(u==n){
            for(int i=0;i<n;i++) {
                for(int j=0;j<n;j++){
                    pw.print(g[i][j]);
                }
                pw.println();
            }
            pw.println();
            return;
        }
        for(int i=0;i<n;i++){//u代表第几行,i代表第几列
            if(!col[i] && !dg[i+u] && !udg[u-i+n]){
                g[u][i]='Q';
                col[i]=dg[i+u]=udg[u-i+n]=true;
                dfs(u+1);
                col[i]=dg[i+u]=udg[u-i+n]=false;
                g[u][i]='.';
            }
        }
    }
    public static void main(String args[]) throws IOException{
        n=Integer.parseInt(bf.readLine());
        for(int i=0;i<n;i++){//初始化
            for(int j=0;j<n;j++){
                g[i][j]='.';
            }
        }
        dfs(0);
        pw.close();
    }
}

二、BFS

AcWing 844. 走迷宫

 思路:

利用bfs求最少次数

代码:

import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static class node{
        int x,y;
        public node(int x,int y){
            this.x=x;
            this.y=y;
        }
    }
    static int n,m;
    static int N=110;
    static int g[][]=new int[N][N];
    static int dist[][]=new int[N][N];
    static int dx[]={0,0,1,-1};
    static int dy[]={1,-1,0,0};
    public static int bfs(node start){
        Queue<node>queue=new LinkedList<>();
        dist[start.x][start.y]=0;
        queue.add(start);
        while (!queue.isEmpty()){
            node t=queue.poll();
            for(int i=0;i<4;i++){
                int x1=t.x+dx[i];
                int y1=t.y+dy[i];
                if(x1>=0 && x1<n && y1>=0 && y1<m && dist[x1][y1]==-1 && g[x1][y1]==0){
                    dist[x1][y1]=dist[t.x][t.y]+1;
                    queue.add(new node(x1,y1));
                }
            }
        }
        return dist[n-1][m-1];
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                g[i][j]=nextInt();
            }
        }
        node start=new node(0,0);
        for(int i=0;i<n;i++) Arrays.fill(dist[i],-1);
        int res=bfs(start);
        pw.println(res);
        pw.close();
    }
}

AcWing 845. 八数码

 思路:

用一个字符串string表示每个状态,则结束状态对应的字符串为12345678x,用map来存储到当前字符串对应的状态所需要的步数,利用bfs求解
一维坐标k转二维坐标:x=k/3,y=k%3   二维坐标(x,y)转一维坐标:k=x*3+y

代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

public class Main{
    static BufferedReader bf=new BufferedReader(new InputStreamReader(System.in));
    static PrintWriter pw=new PrintWriter(System.out);
    static int dx[]={0,0,1,-1};
    static int dy[]={1,-1,0,0};
    static Map<String,Integer>map=new HashMap<>();
    public static void swap(char c[],int x,int y){
        char temp=c[x];
        c[x]=c[y];
        c[y]=temp;
    }
    public static int bfs(String start,String end){
        Queue<String>queue=new LinkedList<>();
        queue.add(start);
        map.put(start,0);
        while (!queue.isEmpty()){
            String t=queue.poll();
            if(t.equals(end)) return map.get(t);
            int k=t.indexOf('x');
            int x=k/3,y=k%3;
            for(int i=0;i<4;i++){
                int x1=x+dx[i];
                int y1=y+dy[i];
                if(x1>=0 && x1<3 && y1>=0 && y1<3){
                    char c[]=t.toCharArray();
                    swap(c,k,x1*3+y1);
                    String s=new String(c);
                    if(!map.containsKey(s)){
                        map.put(s,map.get(t)+1);
                        queue.add(s);
                    }
                }
            }
        }
        return -1;
    }
    public static void main(String args[]) throws IOException{
        String s[]=bf.readLine().split(" ");
        String start="";
        String end="12345678x";
        for(int i=0;i<s.length;i++) start+=s[i];
        int res=bfs(start,end);
        pw.println(res);
        pw.close();
    }
}

三、树与图的深度优先遍历

AcWing 846. 树的重心

 思路:

res是最终结果,初始化为很大的数,dfs(u,fa)求得以u为根节点的树的点数,则删掉该点后连通块有:u的所有孩子以及整棵树-u这棵树,利用ans记录删掉该点后连通块的最大值,递归求得删除每个节点后的连通块的最大值的最小值,res即是最小的ans;

 代码:

import java.io.*;
import java.util.Arrays;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n;
    static int N=100010;
    static int M=2*N;//由于是无向图,e,ne数组需要开两倍
    static int e[]=new int[M];
    static int ne[]=new int[M];
    static int h[]=new int[N];
    static int idx=0;
    static int res=N;
    public static void add(int a,int b){
        e[idx]=b;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static int dfs(int u,int fa){//返回以u为根的子树的总点数
        int sum=1,ans=0;//sum记录该树的点数个数,至少有1个节点即是自己本身,ans记录删掉该节点后,子树点数中的最大值
        for(int i=h[u];i!=-1;i=ne[i]){
            int j=e[i];
            if(j==fa) continue;
            int s=dfs(j,u);//每个子树的点数
            ans= Math.max(ans,s);//更新ans
            sum+=s;//sum加上子树的点数
        }
        //经上述操作,ans为删除该点后子树(子树连通块)点数的最大值
        ans= Math.max(ans,n-sum);//比较ans和删除该树后剩余连通块的大小
        res= Math.min(res,ans);//更新res
        return sum;
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        Arrays.fill(h,-1);
        for(int i=1;i<=n-1;i++){
            int a=nextInt();
            int b=nextInt();
            add(a,b);
            add(b,a);
        }
        dfs(1,-1);
        pw.println(res);
        pw.close();
    }
}

四、树与图的广度优先遍历

AcWing 847. 图中点的层次

 思路:

由于所有边的长度都是1,因此可利用bfs求到某一点的距离

代码:

import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n,m;
    static int N=100010;
    static int M=2*N;
    static int h[]=new int[N];
    static int e[]=new int[M];
    static int ne[]=new int[M];
    static int idx=0;
    static int dist[]=new int[N];
    public static void add(int a,int b){
        e[idx]=b;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static int bfs(){
        Queue<Integer>queue=new LinkedList<>();
        queue.add(1);
        dist[1]=0;
        while (!queue.isEmpty()){
            int t=queue.poll();
            for(int i=h[t];i!=-1;i=ne[i]){
                int j=e[i];
                if(dist[j]==-1){//j未被访问过
                    dist[j]=dist[t]+1;
                    queue.add(j);
                }
            }
        }
        return dist[n];
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        Arrays.fill(h,-1);
        while (m--!=0){
            int a=nextInt();
            int b=nextInt();
            add(a,b);
        }
        Arrays.fill(dist,-1);
        int res=bfs();
        pw.println(res);
        pw.close();
    }
}

五、拓扑排序

AcWing 848. 有向图的拓扑序列

 思路:

思路 开辟一个from数组用来存储每个点的入度,当入度为0时将该点加入拓扑序列中,最后判断拓扑序列中元素个数是否等于所有节点个数,若相等,说明存在拓扑排序,否则不存在

代码: 

import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n,m;
    static int N=100010;
    static int h[]=new int[N];
    static int e[]=new int[N];
    static int ne[]=new int[N];
    static int idx=0;
    static int from[]=new int[N];
    static int res[]=new int[N];
    static int cnt=0;
    public static void add(int a,int b){
        e[idx]=b;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static boolean topsort(){
        Queue<Integer>queue=new LinkedList<>();
        for(int i=1;i<=n;i++){
            if(from[i]==0) queue.add(i);
        }
        while (!queue.isEmpty()){
            int t=queue.poll();
            res[cnt++]=t;
            for(int i=h[t];i!=-1;i=ne[i]){
                int j=e[i];
                from[j]--;
                if(from[j]==0) queue.add(j);
            }
        }
        return cnt==n;
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        Arrays.fill(h,-1);
        while (m--!=0){
            int a=nextInt();
            int b=nextInt();
            add(a,b);
            from[b]++;
        }
        if(topsort()){
            for(int i=0;i<cnt;i++) pw.print(res[i]+" ");
        }
        else pw.println(-1);
        pw.close();
    }
}

 六、Dijkstra

AcWing 849. Dijkstra求最短路 I

说明

朴素版Dijkstra算法适用于稠密图,一般稠密图的e与n^2是一个数量级,使用邻接矩阵存储
时间复杂度O(n^2),且Dijkstra只适用于图的边权为正的情况

 代码 朴素版Dijkstra

import java.io.*;
import java.util.Arrays;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n,m;
    static int N=510;
    static int INF=0x3f3f3f3f;
    static int g[][]=new int[N][N];
    static int dist[]=new int[N];
    static boolean flag[]=new boolean[N];
    public static int dijkstra(){
        Arrays.fill(dist,INF);
        dist[1]=0;
        for(int i=0;i<n;i++){//一共循环n次
            int t=-1;
            for(int j=1;j<=n;j++){//找到距离起点最近的点t
                if(!flag[j] && (t==-1 || dist[j]<dist[t])) t=j;
            }
            flag[t]=true;//将t加入已选集合
            for(int j=1;j<=n;j++) dist[j]= Math.min(dist[j],dist[t]+g[t][j]);//用t来更新其他点到起点的距离
        }
        return dist[n];
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        for(int i=1;i<=n;i++){//初始化
            for(int j=1;j<=n;j++){
                if(i==j) g[i][j]=0;
                else g[i][j]=INF;
            }
        }
        while (m--!=0){
            int a=nextInt();
            int b=nextInt();
            int c=nextInt();
            g[a][b]= Math.min(g[a][b],c);//去除重边和自环,且重边只取最小值
        }
        int res=dijkstra();
        if(res==INF) pw.println(-1);
        else pw.println(res);
        pw.close();
    }
}

AcWing 850. Dijkstra求最短路 II

 思路:

n,m属于同一数量级,为稀疏图,使用邻接表存储图;
考虑堆优化版的Dijkstra,时间复杂度O(mlogn)

 代码:

import java.io.*;
import java.util.Arrays;
import java.util.PriorityQueue;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static class node implements Comparable<node>{
        int id,distance;
        public node(int id,int distance){
            this.id=id;
            this.distance=distance;
        }
        public int compareTo(node p){
            return Integer.compare(distance,p.distance);
        }
    }
    static int n,m;
    static int N=150010;
    static int INF=0x3f3f3f3f;
    static int dist[]=new int[N];
    static boolean flag[]=new boolean[N];
    static int h[]=new int[N];
    static int e[]=new int[N];
    static int w[]=new int[N];
    static int ne[]=new int[N];
    static int idx=0;
    public static void add(int a,int b,int c){
        e[idx]=b;
        w[idx]=c;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static int dijkstra(){
        Arrays.fill(dist,INF);//初始化距离
        dist[1]=0;
        PriorityQueue<node>heap=new PriorityQueue<>();//创建小根堆
        heap.add(new node(1,0));//表示到节点1的距离为0
        while (!heap.isEmpty()){
            node t=heap.poll();//得到最小值
            int id=t.id;
            int distance=t.distance;
            if(!flag[id]){//保证新加入的点不在集合S中
                flag[id]=true;//将其加入S
               for(int i=h[id];i!=-1;i=ne[i]){//遍历与其相连的边
                   int j=e[i];
                   if(dist[j]>distance+w[i]){//更新距离
                       dist[j]=distance+w[i];
                       heap.add(new node(j,dist[j]));
                   }
               }
            }
        }
        return dist[n];
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        Arrays.fill(h,-1);
        while (m--!=0){//读入图
            int a=nextInt();
            int b=nextInt();
            int c=nextInt();
            add(a,b,c);
        }
        int res=dijkstra();
        if(res==INF) pw.println(-1);
        else pw.println(res);
        pw.close();
    }
}

七、Bellman-ford

AcWing 853. 有边数限制的最短路

 思路:

Bellman-ford算法允许图的边权为负数
若有边数限制,则只能用bellman-ford算法  k为边数限制
每次操作即是进行一次松弛操作来更新dist数组,循环k次后dist数组意义是从1开始,经过不超过k条边时,能够走到其他点的最短距离,若dist[n]数组>INF/2,则不可达
时间复杂度O(nm)

 代码

import java.io.*;
import java.util.Arrays;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static class node{
        int a,b,c;
        public node(int a,int b,int c){
            this.a=a;
            this.b=b;
            this.c=c;
        }
    }
    static int n,m,k;
    static int N=510;
    static int M=100010;
    static int dist[]=new int[N];
    static node p[]=new node[M];
    static int INF=0x3f3f3f3f;
    static int backup[]=new int[N];
    public static void bellman_ford(){
        Arrays.fill(dist,INF);
        dist[1]=0;
        for(int i=0;i<k;i++){//边数限制
            backup=Arrays.copyOf(dist,n+1);
            for(int j=0;j<m;j++){//遍历所有边
                int a=p[j].a;
                int b=p[j].b;
                int c=p[j].c;
                dist[b]= Math.min(dist[b],backup[a]+c);
            }
        }
        if(dist[n]>INF/2) pw.println("impossible");
        else pw.println(dist[n]);
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        k=nextInt();
        for(int i=0;i<m;i++){
            int a=nextInt();
            int b=nextInt();
            int c=nextInt();
            p[i]=new node(a,b,c);
        }
        bellman_ford();
        pw.close();
    }
}

八、Spfa

AcWing 851. spfa求最短路

 思路:

SPFA:通常用于求含负权边的单源最短路径,以及判负权环
对Bellman_ford算法进行优化,对已经被更新的点连的所有边进行判断(只有我变小了,我连的边才有可能变小)
一般时间复杂度为O(m),最坏O(nm);

代码:

import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n,m;
    static int N=100010;
    static int INF=0x3f3f3f3f;
    static int dist[]=new int[N];
    static int h[]=new int[N];
    static int e[]=new int[N];
    static int ne[]=new int[N];
    static int w[]=new int[N];
    static boolean flag[]=new boolean[N];//判断该点是否在队列之中,防止队列中存储重复的点
    static int idx=0;
    public static void add(int a,int b,int c){
        e[idx]=b;
        w[idx]=c;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static void spfa(){
        Arrays.fill(dist,INF);
        Queue<Integer> queue=new LinkedList<>();//存储所有dist变小的节点
        queue.add(1);
        dist[1]=0;
        flag[1]=true;
        while (!queue.isEmpty()){
            int t=queue.poll();
            flag[t]=false;
            for(int i=h[t];i!=-1;i=ne[i]){
                int j=e[i];
                if(dist[j]>dist[t]+w[i]){//若能被更新,表示可以被加入队列
                    dist[j]=dist[t]+w[i];
                    if(!flag[j]){//判断该点是否在队列之中,若没有,则加入队列
                        flag[j]=true;
                        queue.add(j);
                    }
                }
            }
        }
        if(dist[n]==INF) pw.println("impossible");
        else pw.println(dist[n]);
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        Arrays.fill(h,-1);
        while (m--!=0){
            int a=nextInt();
            int b=nextInt();
            int c=nextInt();
            add(a,b,c);
        }
        spfa();
        pw.close();
    }
}

AcWing 852. spfa判断负环

 思路

dist[x]数组表示从起点到x点的最短路径的长度
cnt[x]数组,表示从起点到x点的最短路径的边数
更新dist数组时同时更新cnt数组,若cnt[x]>=n,则表示该路径一定经过了n+1个点,而一共有n个点,说明有两个点重复出现,即出现负环
注意是判断该图中是否存在负环,而不是判断从1开始的负环,因此队列初始化时需要将所有点加入!

 代码:

import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n,m;
    static int N=2010;
    static int M=10010;
    static int h[]=new int[N];
    static int e[]=new int[M];
    static int ne[]=new int[M];
    static int w[]=new int[M];
    static boolean flag[]=new boolean[N];
    static int dist[]=new int[N];
    static int cnt[]=new int[N];
    static int idx=0;
    public static void add(int a,int b,int c){
        e[idx]=b;
        w[idx]=c;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static boolean spfa(){
        Queue<Integer>queue=new LinkedList<>();
        for(int i=1;i<=n;i++) {//将全部点放入队列
            flag[i]=true;
            queue.add(i);
        }
        while (!queue.isEmpty()){
            int t=queue.poll();
            flag[t]=false;
            for(int i=h[t];i!=-1;i=ne[i]){
                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(!flag[j]){
                        queue.add(j);
                        flag[j]=true;
                    }
                }
            }
        }
        return false;
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        Arrays.fill(h,-1);
        while (m--!=0){
            int a=nextInt();
            int b=nextInt();
            int c=nextInt();
            add(a,b,c);
        }
        if(spfa()) pw.println("Yes");
        else pw.println("No");
        pw.close();
    }
}

九、Floyd

AcWing 854. Floyd求最短路

 思路:

Floyd算法可以解决多源汇最短路问题,基于动态规划的思想,f(k,i,j)表示从i开始只经过1~k这些点到达j的最短距离,状态转移方程f(k,i,j)=f(k-1,i,k)+f(k-1,k,j),去掉一维后得到f(i,j)=f(i,k)+f(k,j);
时间复杂度O(n^3)

代码:

import java.io.*;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n,m,q;
    static int N=210;
    static int INF=0x3f3f3f3f;
    static int d[][]=new int[N][N];
    public static 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]= Math.min(d[i][j],d[i][k]+d[k][j]);
                }
            }
        }
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        q=nextInt();
        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;
            }
        }
        while (m--!=0){
            int a=nextInt();
            int b=nextInt();
            int c=nextInt();
            d[a][b]= Math.min(d[a][b],c);
        }
        floyd();
        while (q--!=0){
            int a=nextInt();
            int b=nextInt();
            if(d[a][b]>INF/2) pw.println("impossible");
            else pw.println(d[a][b]);
        }
        pw.close();
    }
}

 十、Prim

AcWing 858. Prim算法求最小生成树

 思路:

朴素版Prim算法,适用于稠密图,时间复杂度O(n^2)
堆优化版Prim算法,适用于稀疏图,但由于写法复杂,因此求稀疏图的最小生成树常使用Kruskal算法

 代码:

import java.io.*;
import java.util.Arrays;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n,m;
    static int N=510;
    static int INF=0x3f3f3f3f;
    static int g[][]=new int[N][N];
    static boolean flag[]=new boolean[N];
    static int dist[]=new int[N];//dist[x]表示x到集合的最短距离
    public static int prim(){
        Arrays.fill(dist,INF);//初始化
        dist[1]=0;
        int res=0;//最小生成树的权值
        for(int i=1;i<=n;i++){
            int t=-1;
            for(int j=1;j<=n;j++){//找到距离集合最近的点t
                if(!flag[j] && (t==-1||dist[j]<dist[t])) t=j;
            }
            if(dist[t]==INF) return INF;//说明不连通,不存在最小生成树
            flag[t]=true;//将t加入集合
            res+=dist[t];
            for(int j=1;j<=n;j++) dist[j]= Math.min(dist[j],g[t][j]);//用t更新其他点到集合的距离
        }
        return res;
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j) g[i][j]=0;
                else g[i][j]=INF;
            }
        }
        while (m--!=0){//去除重边和自环
            int a=nextInt();
            int b=nextInt();
            int c=nextInt();
            g[a][b]=g[b][a]= Math.min(g[a][b],c);
        }
        int res=prim();
        if(res==INF) pw.println("impossible");
        else pw.println(res);
        pw.close();
    }
}

十一、Kruskal

AcWing 859. Kruskal算法求最小生成树

 思路:

Kruskal算法适用于求稀疏图的最小生成树,时间复杂度0(mlogm)
其中判断两个点是否连通可利用并查集

 代码:

import java.io.*;
import java.util.Arrays;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static class node implements Comparable<node>{
        int a,b,c;
        public node(int a,int b,int c){
            this.a=a;
            this.b=b;
            this.c=c;
        }
        public int compareTo(node p){
            return Integer.compare(c,p.c);
        }
    }
    static int n,m;
    static int N=100010;
    static int M=2*N;
    static node e[]=new node[M];
    static int p[]=new int[N];
    public static int find(int x){
        if(p[x]!=x) p[x]=find(p[x]);
        return p[x];
    }
    public static void main(String args[]) throws IOException {
        n=nextInt();
        m=nextInt();
        for(int i=0;i<m;i++){//读入每条边
            int a=nextInt();
            int b=nextInt();
            int c=nextInt();
            e[i]=new node(a,b,c);
        }
        int res=0,cnt=0;
        Arrays.sort(e,0,m);//排序
        for(int i=1;i<=n;i++) p[i]=i;//初始化
        for(int i=0;i<m;i++){//从小到大遍历每条边
            int a=e[i].a;
            int b=e[i].b;
            int c=e[i].c;
            if(find(a)!=find(b)){
                p[find(a)]=find(b);
                res+=c;//权重加入res
                cnt++;//权重加入res
            }
        }
        if(cnt==n-1) pw.println(res);//说明将所有点都加入集合
        else pw.println("impossible");//说明不连通
        pw.close();
    }
}

十二、染色法判定二分图

AcWing 860. 染色法判定二分图

 思路:

二分图:可以将图分为两部分,每一部分内部没有边相连 
若一个图是二分图 等价于 图中不含奇数环,利用染色法,若不发生矛盾,则说明不含奇数环,即是二分图;
时间复杂度O(n+m)

代码:

import java.io.*;
import java.util.Arrays;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n,m;
    static int N=100010;
    static int M=2*N;
    static int h[]=new int[N];
    static int e[]=new int[M];
    static int ne[]=new int[M];
    static int idx=0;
    static int color[]=new int[N];
    public static void add(int a,int b){
        e[idx]=b;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static boolean 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]==0){
                if(!dfs(j,3-c)) return false;//若染色失败,说明发送冲突,返回false,3-c:1变2,2变1
            }
            else if(color[j]==c) return false;//两个相邻点不能同色
        }
        return true;
    }
    public static void main(String args[]) throws IOException{
        n=nextInt();
        m=nextInt();
        Arrays.fill(h,-1);
        while (m--!=0){
            int a=nextInt();
            int b=nextInt();
            add(a,b);
            add(b,a);
        }
        boolean flag=true;//判断染色过程中是否成功
        for(int i=1;i<=n;i++){//进行染色
            if(color[i]==0){//该点未被染色
                if(!dfs(i,1)){
                    flag=false;
                    break;
                }
            }
        }
        if(flag) pw.println("Yes");
        else pw.println("No");
        pw.close();
    }
}

十三、匈牙利算法

AcWing 861. 二分图的最大匹配

 思路:

find(x)表示能否给x找到一个匹配,尝试了所有办法都无法找到匹配才返回false
时间复杂度O(nm),但实际运行时间远小于O(nm)

 代码一 邻接矩阵写法:

import java.io.*;
import java.util.Arrays;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n1,n2,m;
    static int N=510;
    static boolean g[][]=new boolean[N][N];
    static int match[]=new int[N];
    static boolean flag[]=new boolean[N];
    public static boolean find(int x){
        for(int i=1;i<=n2;i++){
            if(!flag[i] && g[x][i]){//能够访问i且i未被访问
                flag[i]=true;//已访问过i
                if(match[i]==0 || find(match[i])){//i还没有对象或者能为i的对象找到备胎
                    match[i]=x;//横刀夺爱
                    return true;
                }
            }
        }
        return false;
    }
    public static void main(String arsg[]) throws IOException{
        n1=nextInt();
        n2=nextInt();
        m=nextInt();
        while(m--!=0){
            int a=nextInt();
            int b=nextInt();
            g[a][b]=true;
        }
        int res=0;
        for(int i=1;i<=n1;i++){
            Arrays.fill(flag,false);
            if(find(i)) res++;
        }
        pw.println(res);
        pw.close();
    }
}

代码二:邻接表写法

import java.io.*;
import java.util.Arrays;

public class Main {
    static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter pw=new PrintWriter(System.out);
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
    static int n1,n2,m;
    static int N=510;
    static int M=100010;
    static int h[]=new int[N];
    static int e[]=new int[M];
    static int ne[]=new int[M];
    static int idx=0;
    static boolean flag[]=new boolean[N];
    static int match[]=new int[N];
    public static void add(int a,int b){
        e[idx]=b;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static boolean find(int x){//判断x是否能找到匹配的妹子
        for(int i=h[x];i!=-1;i=ne[i]){
            int j=e[i];
            if(!flag[j]){
                flag[j]=true;
                if(match[j]==0 || find(match[j])){//妹子未被匹配 或 已经被匹配,但可给对应男生找下家
                    match[j]=x;
                    return true;
                }
            }
        }
        return false;
    }
    public static void main(String args[]) throws IOException{
        n1=nextInt();
        n2=nextInt();
        m=nextInt();
        Arrays.fill(h,-1);;
        while (m--!=0){
            int a=nextInt();
            int b=nextInt();
            add(a,b);
        }
        int res=0;
        for(int i=1;i<=n1;i++){//遍历左半部分
            Arrays.fill(flag,false);
            if(find(i)) res++;
        }
        pw.println(res);
        pw.close();
    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值