模版:搜索与图论

模版:搜索与图论_第1张图片
image.png

1、朴素dijkstra

算法分析

模版:搜索与图论_第2张图片
image.png

模版:搜索与图论_第3张图片
image.png

注意:若要求任意点i到任意个点j的最短距离,只需修改dijkstra方法中的起源位置dist[i] = 0,以及返回为dist[j]

时间复杂度

Java 代码

public class Main{
    static int N = 510;
    static int n;
    
    static int[][] g = new int[N][N];// 存储每条边
    static int[] dist = new int[N];// 存储1号点到每个点的最短距离
    static boolean[] st = new boolean[N];
    static int INF = 0x3f3f3f3f;//设置无穷大
    // 求1号点到n号点的最短路,如果不存在则返回-1
    public static int dijkstra()
    {
        Arrays.fill(dist, INF);
        dist[1] = 0;
        for(int i = 0;i < n;i++)
        {
            //1、找到当前未标记过且离源点最近的点
            int t = -1;
            for(int j = 1;j <= n;j++)
            {
                if(!st[j] && (t == -1 || dist[t] > dist[j]))
                    t = j;
            }
            //2、标记该点已经确定最短距离
            st[t] = true;
            //3、用该点更新其他点的距离
            for(int j = 1;j <= n;j++)
            {
                dist[j] = Math.min(dist[j], dist[t] + g[t][j]);
            }
        }
        
        if(dist[n] == INF) return -1;
        return dist[n];
    }
    public static void main(String[] args) throws IOException{
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        int m = Integer.parseInt(str1[1]);
        for(int i = 1; i <= n; i++ )
             Arrays.fill(g[i], INF);
        while(m -- > 0)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            g[a][b] = Math.min(g[a][b], c);//若有重边,选择最短的
        }
        System.out.println(dijkstra());
    }
}

2、堆优化版的dijkstra

算法分析

模版:搜索与图论_第4张图片
image.png
模版:搜索与图论_第5张图片
image.png

注意:若要求任意点i到任意个点j的最短距离,只需修改dijkstra方法中的起源位置dist[i] = 0,以及返回为dist[j]

时间复杂度

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.PriorityQueue;

public class Main{
    static int N = 100010;
    static 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 int idx = 0;
    static int[] dist = new int[N];// 存储1号点到每个点的最短距离
    static boolean[] st = new boolean[N];
    static int INF = 0x3f3f3f3f;//设置无穷大
    public static void add(int a,int b,int c)
    {
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    // 求1号点到n号点的最短路,如果不存在则返回-1
    public static int dijkstra()
    {
        //维护当前未在st中标记过且离源点最近的点
        PriorityQueue queue = new PriorityQueue();
        Arrays.fill(dist, INF);
        dist[1] = 0;
        queue.add(new PIIs(0,1));
        while(!queue.isEmpty())
        {
            //1、找到当前未在s中出现过且离源点最近的点
            PIIs p = queue.poll();
            int t = p.getSecond();
            int distance = p.getFirst();
            if(st[t]) continue;
            //2、将该点进行标记
            st[t] = true;
            //3、用t更新其他点的距离
            for(int i = h[t];i != -1;i = ne[i])
            {
                int j = e[i];
                dist[j] = Math.min(dist[j], distance + w[i]);
                queue.add(new PIIs(dist[j],j));
                
            }
                
        }
        if(dist[n] == INF) return -1;
        return dist[n];
    }
    
    public static void main(String[] args) throws IOException{
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        int m = Integer.parseInt(str1[1]);
        Arrays.fill(h, -1);
        while(m -- > 0)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            add(a,b,c);
        }
        System.out.println(dijkstra());
    }
}

class PIIs implements Comparable{
    private int first;//距离值
    private int second;//点编号
    
    public int getFirst()
    {
        return this.first;
    }
    public int getSecond()
    {
        return this.second;
    }
    public PIIs(int first,int second)
    {
        this.first = first;
        this.second = second;
    }
    @Override
    public int compareTo(PIIs o) {
        // TODO 自动生成的方法存根
        return Integer.compare(first, o.first);
    }
}

3、Bellman_Fore算法

算法分析

1、问题:为什么Dijkstra不能使用在含负权的图中?

分析:如图所示:
若通过Dijkstra算法可以求出从1号点到达4号点所需的步数为3(每次选择离源点最短距离的点更新其他点)
但实际上从1号点到达4号点所需步数为1 (1 --> 2 --> 3),因此不能使用Dijkstra解决含负权图的问题

模版:搜索与图论_第6张图片
image.png

2、什么是bellman - ford算法?

Bellman - ford算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。
(通俗的来讲就是:假设1号点到n号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,通过循环n-1次操作,若图中不存在负环,则1号点一定会到达n号点,若图中存在负环,则在n-1次松弛后一定还会更新)

3、bellman - ford算法的具体步骤

for n次
for 所有边 a,b,w (松弛操作)
dist[b] = min(dist[b],back[a] + w)

注意:back[]数组是上一次迭代后dist[]数组的备份,由于是每个点同时向外出发,因此需要对dist[]数组进行备份,若不进行备份会因此发生串联效应,影响到下一个点

4、在下面代码中,是否能到达n号点的判断中需要进行if(dist[n] > INF/2)判断,而并非是if(dist[n] == INF)判断,原因是INF是一个确定的值,并非真正的无穷大,dist[n]大于某个与INF相同数量级的数即可

5、bellman - ford算法擅长解决有边数限制的最短路问题

时间复杂度

其中n为点数,m为边数

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {
    
    static int N = 510;
    static int M = 100010;
    static int n;//总点数
    static int m;//总边数
    static int k;//最多经过k条边
    static int[] dist = new int[N];//从1到点到n号点的距离
    static Node[] list = new Node[M];//结构体
    static int INF = 0x3f3f3f3f;
    static int[] back = new int[N];//备份dist数组
    public static void bellman_ford()
    {
        Arrays.fill(dist, INF);
        
        dist[1] = 0;
        for(int i = 0;i < k;i++)
        {
            back = Arrays.copyOf(dist, n + 1);//由于是从1开始存到n
            for(int j = 0;j < m;j++)
            {
                Node node = list[j];
                int a = node.a;
                int b = node.b;
                int c = node.c;
                dist[b] = Math.min(dist[b], back[a] + c);
            }
        }
        if(dist[n] > INF/2) System.out.println("impossible");
        else System.out.println(dist[n]);
    }
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        k = Integer.parseInt(str1[2]);
        for(int i = 0;i < m;i++)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            list[i] = new Node(a,b,c);
        }
        bellman_ford();
        
    }

}
class Node
{
    int a, b, c;
    public Node(int a,int b,int c)
    {
        this.a = a;
        this.b = b;
        this.c = c;
    }
}

4、spfa算法 最坏:

算法分析

1、什么是spfa算法?

SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA一般情况复杂度是 最坏情况下复杂度和朴素 Bellman-Ford 相同,为。

bellman-ford算法操作如下:

for n次
for 所有边 a,b,w (松弛操作)
dist[b] = min(dist[b],back[a] + w)

spfa算法对第二行中所有边进行松弛操作进行了优化,原因是在bellman—ford算法中,即使该点的最短距离尚未更新过,但还是需要用尚未更新过的值去更新其他点,由此可知,该操作是不必要的,我们只需要找到更新过的值去更新其他点即可。

2、spfa算法步骤

queue <-- 1
while queue 不为空
(1) t <-- 队头
queue.pop()
(2)用 t 更新所有出边 t --> b,权值为w
queue <-- b (若该点被更新过,则拿该点更新其他点)

时间复杂度 一般: 最坏:

n为点数,m为边数

3、spfa也能解决权值为正的图的最短距离问题,且一般情况下比Dijkstra算法还好

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class Main {
    static int N = 100010;
    static int n;
    static int m;
    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 int idx = 0;
    static int[] dist = new int[N];
    static boolean[] st = new boolean[N]; //标记是否在队列中
    static int INF = 0x3f3f3f3f;
    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 spfa()
    {
        Arrays.fill(dist, INF);
        Queue queue = new LinkedList();
        dist[1] = 0;
        queue.add(1);
        st[1] = true;//标记1号点在队列中
        while(!queue.isEmpty())
        {
            int t = queue.poll();
            st[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(!st[j])
                    {
                        queue.add(j);
                        st[j] = true;//标记已加入队列
                    }
                }
                
            }
        }
        return dist[n];
    }
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        Arrays.fill(h, -1);
        while(m -- > 0)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            add(a,b,c);
        }
        int t = spfa();
        if(t == 0x3f3f3f3f) System.out.println("impossible");
        else System.out.println(t);
        
    }

}

5、spfa算法解决负环路问题 最坏:

算法分析

使用spfa算法解决是否存在负环问题

  • 1、dist[x] 记录当前1到x的最短距离
  • 2、cnt[x] 记录当前最短路的边数,初始每个点到1号点的距离为0,只要他能再走n步,即cnt[x] >= n,则表示该图中一定存在负环,由于从1到x至少经过n条边时,则说明图中至少有n + 1个点,表示一定有点是重复使用
  • 3、若dist[j] > dist[t] + w[i],则表示从t点走到j点能够让权值变少,因此进行对该点j进行更新,并且对应cnt[j] = cnt[t] + 1,往前走一步

注意:该题是判断是否存在负环,并非判断是否存在从1开始的负环,因此需要将所有的点都加入队列中,更新周围的点


模版:搜索与图论_第7张图片
image.png

时间复杂度 一般: 最坏:

参考文献

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class Main {
    static int n;
    static int 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 int idx = 0;
    static int[] dist = new int[N];//记录当前1到x的最短距离
    static int[] cnt = new int[N];//从1到点到x经过的边数 
    static boolean[] st = new boolean[N];

    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 queue = new LinkedList();
        //将所有点进入队列
        for(int i = 1;i <= n;i++)
        {
            queue.add(i);
            st[i] = true;
        }
        while(!queue.isEmpty())
        {
            int t = queue.poll();
            st[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(!st[j])
                    {
                        queue.add(j);
                        st[j] = true;
                    }


                }
            }
        }
        return false;
    }
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        Arrays.fill(h, -1);
        while(m -- > 0)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            add(a,b,c);
        }
        if(spfa()) System.out.println("Yes");
        else System.out.println("No");
    }

}

6、folyd算法

算法分析

(y总真言,简单易懂)

  • f[i, j, k]表示从i走到j的路径上除了i, j以外不包含点k的所有路径的最短距离。那么f[i, j, k] = min(f[i, j, k - 1), f[i, k, k - 1] + f[k, j, k - 1]
    因此在计算第k层的f[i, j]的时候必须先将第k - 1层的所有状态计算出来,所以需要把k放在最外层。
  • 读入邻接矩阵,将次通过动态规划装换成从ij的最短距离矩阵
  • 在下面代码中,判断从a到b是否是无穷大距离时,需要进行if(t > INF/2)判断,而并非是if(t == INF)判断,原因是INF是一个确定的值,并非真正的无穷大,会随着其他数值而受到影响,t大于某个与INF相同数量级的数即可

时间复杂度

Java 代码

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


public class Main {
    static int n;
    static int m;
    static int 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 {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        q = Integer.parseInt(str1[2]);
        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)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            d[a][b] = Math.min(d[a][b], c);//若有重边选择短的边
        }
        floyd();
        while(q -- > 0)
        {
            String[] str3 = reader.readLine().split(" ");
            int a = Integer.parseInt(str3[0]);
            int b = Integer.parseInt(str3[1]);
            int t = d[a][b];
            if(t > INF / 2) System.out.println("impossible");
            else System.out.println(t);

        }

    }

}
![image.png](https://upload-images.jianshu.io/upload_images/17362169-1b01a48e634a967e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)



7、Prim算法

算法分析

模版:搜索与图论_第8张图片
a209dc1bdc0bf0a07abd792625e3d15.png

时间复杂度

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;


public class Main {
    static int n;
    static int m;
    static int q;
    static int N = 510;
    static int INF = 0x3f3f3f3f;
    static int[][] g = new int[N][N];
    static int[] dist = new int[N];//表示到集合的最短距离
    static boolean[] st = new boolean[N];
    public static int prim()
    {
        Arrays.fill(dist, INF);
        int res = 0;
        for(int i = 0;i < n;i++)
        {
            int t = -1;
            for(int j = 1;j <= n;j++)
            {
                if(!st[j] && (t == -1 || dist[t] > dist[j]))
                    t = j;
            }
            if(i != 0 && dist[t] == INF) return INF;
            //标记已加入集合
            st[t] = true;
            if(i != 0) res += dist[t];
            //用t更新其他点
            for(int j = 1;j <= n;j++) dist[j] = Math.min(dist[j], g[t][j]);
        }
        return res;
    }
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        for(int i = 1;i <= n;i++)
            for(int j = 1;j <= n;j++)
            {
                g[i][j] = INF;
            }
        while(m -- > 0)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            g[a][b] = Math.min(g[a][b], c);
            g[b][a] = Math.min(g[b][a], c);
        }
        int t = prim();
        if(t == INF) System.out.println("impossible");
        else System.out.println(t);
        
    }

}

8、Kruskal算法

算法分析

模版:搜索与图论_第9张图片
image.png

时间复杂度

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;


public class Main {
    static int n;
    static int m;
    static int q;
    static int N = 100010;
    static int M = 200010;
    static int INF = 0x3f3f3f3f;
    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 int idx = 0;
    static boolean[] st = new boolean[N];
    static List list = new ArrayList();
    static int[] p = new int[N];
    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 find(int x)
    {
        if(x != p[x]) p[x] = find(p[x]);
        return p[x];
    }
    public static int kruskal()
    {
        int res = 0;
        int cnt = 0;//计算走过的边数
        Collections.sort(list);
        for(int i = 1;i <= n;i++) p[i] = i;
        
        for(PIIs item : list)
        {
            int a = item.getA();
            int b = item.getB();
            int w = item.getW();
            a = find(a);
            b = find(b);
            if(a != b) 
            {
                p[a] = b;
                res += w;
                cnt ++;
            }
        }
        if(cnt < n - 1) return INF;
        return res;
    }
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        while(m -- > 0)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            add(a,b,c);
            list.add(new PIIs(a,b,c));
        }
        int t = kruskal();
        if(t == INF) System.out.println("impossible");
        else System.out.println(t);
        
    }

}
class PIIs implements Comparable
{
    private int a;
    private int b;
    private int w;
    public int getA()
    {
        return this.a;
    }
    public int getB()
    {
        return this.b;
    }
    public int getW()
    {
        return this.w;
    }
    public PIIs(int a,int b,int w)
    {
        this.a = a;
        this.b = b;
        this.w = w;
    }
    @Override
    public int compareTo(PIIs o) {
        // TODO 自动生成的方法存根
        return Integer.compare(w, o.w);
    }
    
}

9、染色法判定二分图

给定一个n个点m条边的无向图,图中可能存在重边和自环。

请你判断这个图是否是二分图。

  • 奇数环:由奇数条边形成的一个环
  • 二分图:当且仅当图中不含有奇数环,两个集合内部的内部没有边

算法1

使用bfs

  • 约定染的颜色有1颜色和2颜色
模版:搜索与图论_第10张图片
image.png

时间复杂度

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class Main {
    static int n;
    static int m;
    static int N = 100010;
    static int M = 2100010;
    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];//共1和2两种不同的颜色
    static boolean[] st = new boolean[N];
    public static void add(int a,int b)
    {
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    public static boolean bfs()
    {
        Queue queue = new LinkedList();
        for(int i = 1;i <= n;i++)
        {
            //若该点为染色
            if(color[i] == 0)
            {
                color[i] = 1;
                queue.add(i);
                while(!queue.isEmpty())
                {
                    int t = queue.poll();
                    for(int j = h[t] ;j != -1;j = ne[j])
                    {
                        int k = e[j];
                        if(color[k] == 0)
                        {
                            color[k] = 3 - color[t];
                            queue.add(k);
                        }
                        else if(color[k] == color[t]) return false;
                    }
                }
            }
        }
        return true;
        
        
    }
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        Arrays.fill(h, -1);
        while(m -- > 0)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            add(a,b);
            add(b,a);
        }
        
        if(bfs()) System.out.println("Yes");
        else System.out.println("No");
        
        
    }

}


算法2

使用dfs(注意:在这里使用java会直接爆栈hh)

  • dfs(u,c)表示把u号点染色成c颜色,并且判断从u号点开始染其他相连的点是否染成功

时间复杂度

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {
    static int n;
    static int m;
    static int N = 100010;
    static int M = 200010;
    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];//共1和2两种不同的颜色
    static boolean[] st = new boolean[N];
    public static void add(int a,int b)
    {
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    //dfs(u,c)表示把u号点染色成c颜色,并且判断从u号点开始染其他相连的点是否成功
    public static boolean dfs(int u,int 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;
            }
            else if(color[j] == c) return false;//颜色重复
        }
        return true;
    }
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        Arrays.fill(h, -1);
        while(m -- > 0)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            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) System.out.println("Yes");
        else System.out.println("No");
    }
}

10、匈牙利算法

解决二分图的最大匹配问题

时间复杂度: ,实际运行时间远小于

典型题目

给定一个二分图,其中左半部包含n1个点(编号1~n1),右半部包含n2个点(编号1~n2),二分图共包含m条边。

数据保证任意一条边的两个端点都不可能在同一部分中。

请你求出二分图的最大匹配数。

二分图的匹配:给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。

二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

输入格式
第一行包含三个整数 n1、 n2 和 m。

接下来m行,每行包含两个整数u和v,表示左半部点集中的点u和右半部点集中的点v之间存在一条边。

输出格式
输出一个整数,表示二分图的最大匹配数。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {
    static int n1;
    static int n2;
    static int 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[] st = 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)
    {
        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 true;
                }
            }
        }
        return false;
    }
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");
        n1 = Integer.parseInt(str1[0]);
        n2 = Integer.parseInt(str1[2]);
        m = Integer.parseInt(str1[2]);
        Arrays.fill(h, -1);
        while(m -- > 0)
        {
            String[] str2 = reader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            add(a,b);
        }
        int res = 0;
        for(int i = 1;i <= n1;i++)
        {
            Arrays.fill(st, false);
            if(find(i)) res ++;
        }
        System.out.println(res);
    }
}

你可能感兴趣的:(模版:搜索与图论)