算法图论篇

文章目录

    • 一、DFS
      • 1.排列数字(全排列)
      • 2.n皇后
      • 3.树的重心
    • 二、BFS
      • 1.走迷宫
      • 2.八数码
      • 3.图中点的层次
    • 三、拓扑排序
      • 1.有向图的拓扑序列
    • 四、最短路
      • 1.Dijkstra
      • 2. bellman-ford
      • 3.spfa
      • 4.floyd
    • 五、求最小生成树
      • 1.Prim算法
      • 2.Kruskal算法
    • 六、二分图
      • 1.染色法判定二分图
      • 2.二分图的最大匹配

一、DFS

1.排列数字(全排列)

输入:3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

import java.util.*;

public class Main {
	static final int N = 10;
	static int n;
	static int[] path = new int[N];
	static boolean[] vis = new boolean[N];

	static void dfs(int u) {
		if (u == n) {
			for (int i = 0; i < n; i++) {
				System.out.print(path[i] + " ");
			}
			System.out.println();
			return;
		}
		for (int i = 1; i <= n; i++) {
			if (!vis[i]) {
				path[u] = i;
				vis[i] = true;
				dfs(u + 1);
				vis[i] = false;
			}
		}
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		dfs(0);
	}
}

2.n皇后

输入:4
输出:
.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..
import java.util.*;

public class Main {
	static final int N = 20;
	static int n;
	static char[][] g = new char[N][N];
	static int[] col = new int[N];
	static int[] dg = new int[N];// 正对角线
	static int[] udg = new int[N];// 反对角线

	static void dfs(int u) {
		if (u == n) {
			for (int i = 0; i < n; i++) {
			    for(int j=0;j<n;j++){
			        System.out.print(g[i][j]);
			    }
			    System.out.println();
			}
			System.out.println();
			return;
		}
		for (int i = 0; i < n; i++) {
		    //正对角线:y=x+b--->b=y-x--->b非负,则b=y-x+n
		    //反对角线:y=-x+b-->b=y+x
			if (col[i] == 0 && dg[u + i] == 0 && udg[n - u + i] == 0) {
				g[u][i] = 'Q';
				col[i] = dg[u + i] = udg[n - u + i] = 1;
				dfs(u + 1);
				col[i] = dg[u + i] = udg[n - u + i] = 0;
				g[u][i] = '.';
			}
		}
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				g[i][j] = '.';
			}
		}
		dfs(0);
	}
}

3.树的重心

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心

输入:第一行包含整数 n,表示树的结点数。
接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。

输出:一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值

import java.util.*;

public class Main {
	static int N = 100010, M = 2 * N, idx, n;
	static int[] h = new int[N];
	static int[] e = new int[M];
	static int[] ne = new int[M];
	static boolean[] st = new boolean[N];
	static int ans = N;

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

	static int dfs(int u) {
		int res = 0;
		st[u] = true;
		int sum = 1;
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (!st[j]) {
				int s = dfs(j);
				res = Math.max(res, s);
				sum += s;
			}
		}
		res = Math.max(res, n - sum);
		ans = Math.min(res, ans);
		return sum;
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		for (int i = 1; i <= n; i++) {
			h[i] = -1;
		}
		for (int i = 0; i < n - 1; i++) {
			int a = in.nextInt();
			int b = in.nextInt();
			add(a, b);
			add(b, a);
		}
		dfs(1);
		System.out.println(ans);
	}
}

二、BFS

1.走迷宫

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
求从坐标(0,0)移动到(n-1,m-1)的最少次数,其图中1表示墙,0可走

import java.util.*;

class Pair {
	int x;
	int y;

	public Pair(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

public class Main {
	static final int N = 110;
	static int n, m;
	static int[][] g = new int[N][N];//记录原数组
	static int[][] dis = new int[N][N];//记录每个位置到起点的距离

	static int bfs() {
		Queue<Pair> queue = new LinkedList<>();
		queue.add(new Pair(0, 0));
		// 初始化dis
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				dis[i][j] = -1;
			}
		}
		dis[0][0] = 0;
		int[] dx = { -1, 0, 1, 0 }, dy = { 0, 1, 0, -1 };
		while (!queue.isEmpty()) {
			Pair t = queue.poll();
			for (int i = 0; i < 4; i++) {
				int x = t.x + dx[i], y = t.y + dy[i];
				if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && dis[x][y] == -1) {
					dis[x][y] = dis[t.x][t.y] + 1;
					queue.add(new Pair(x, y));
				}
			}
		}
		return dis[n - 1][m - 1];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		String[]nums=in.nextLine().split(" ");
		n = Integer.parseInt(nums[0]);
		m = Integer.parseInt(nums[1]);
		for (int i = 0; i < n; i++) {
		    nums=in.nextLine().split(" ");
			for (int j = 0; j < m; j++) {
				g[i][j] = Integer.parseInt(nums[j]);
			}
		}
		System.out.println(bfs());
	}
}

2.八数码

1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x
import java.util.*;

public class Main {

	static String swap(String s, int i, int j) {
		StringBuilder res = new StringBuilder(s);
		char tmp = res.charAt(i);
		res.setCharAt(i, res.charAt(j));
		res.setCharAt(j, tmp);
		return res.toString();
	}

	static int bfs(String start) {
		Queue<String> queue = new LinkedList<>();
		queue.add(start);
		HashMap<String, Integer> map = new HashMap<>();
		map.put(start, 0);
		String end = "12345678x";
		int dx[] = { -1, 0, 1, 0 }, dy[] = { 0, 1, 0, -1 };
		while (!queue.isEmpty()) {
			String tmp = queue.poll();
			int distance = map.get(tmp);
			if (tmp.equals(end))
				return distance;

			// 状态转移
			int k = tmp.indexOf('x');
			int sx = k / 3, sy = k % 3;
			for (int i = 0; i < 4; i++) {
				int x = sx + dx[i], y = sy + dy[i];
				if (x >= 0 && x < 3 && y >= 0 && y < 3) {
					tmp = swap(tmp, k, 3 * x + y);
					if (!map.containsKey(tmp)) {
						map.put(tmp, distance + 1);
						queue.add(tmp);
					}
					// 还原现场
					tmp = swap(tmp, k, 3 * x + y);
				}
			}
		}
		return -1;
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		String s = in.nextLine().replace(" ", "");
// 		System.out.println(s);
		System.out.println(bfs(s));
	}
}

3.图中点的层次

输出1号点到n号点的最短距离

import java.util.*;

public class Main {
	static int N = 100010, idx, n, m, hh, tt;
	static int[] h = new int[N],e = new int[N],ne = new int[N];
	static int[] d = new int[N],q = new int[N];

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

	static int bfs() {
		hh = 0;
		tt = -1;
		d[1] = 0;
		q[++tt] = 1;
		while (hh <= tt) {
			int t = q[hh++];
			for (int i = h[t]; i != -1; i = ne[i]) {
				int s = e[i];
				if (d[s] == -1) {
					d[s] = d[t] + 1;
					q[++tt] = s;
				}
			}
		}
		return d[n];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		for (int i = 1; i <= n; i++) {
			h[i] = -1;
			d[i] = -1;
		}
		for (int i = 0; i < m; i++) {
			int a = in.nextInt();
			int b = in.nextInt();
			add(a, b);
		}
		System.out.println(bfs());
	}
}

三、拓扑排序

1.有向图的拓扑序列

import java.util.*;

public class Main {
	static int N = 100010, idx, n, m;
	//链表模板
	static int[] e = new int[N], ne = new int[N], h = new int[N];
	//q表示队列,d表示结点的入度数
	static int[] q = new int[N], d = new int[N];

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

	static boolean tpsort() {
		int hh = 0, tt = -1;
		for (int i = 1; i <= n; i++) {
			//将所有入度为0的结点插入队列q中
			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 s = e[i];
				//将改边的入度数减1
				d[s]--;
				if (d[s] == 0) {
					q[++tt] = s;
				}
			}
		}
		return tt == n - 1;
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		for (int i = 1; i <= n; i++) {
			h[i] = -1;
		}
		for (int i = 0; i < m; i++) {
			int a = in.nextInt();
			int b = in.nextInt();
			d[b]++;
			add(a, b);
		}
		if (tpsort()) {
			for (int i = 0; i < n; i++) {
				System.out.print(q[i] + " ");
			}
		} else {
			System.out.println(-1);
		}
	}
}

四、最短路

算法图论篇_第1张图片

1.Dijkstra

不适用于带有负权边的图
稠密图(边多):使用邻接矩阵存储

//朴素
import java.util.*;

public class Main {
	static int N = 510, n, m, max = 0x3f3f3f3f;
	static int[][] g = new int[N][N];//每个点之间的距离
	static int[] dist = new int[N];//每个点到起点的距离
	static boolean[] st = new boolean[N];//已经确认最短距离的点

	static int dijkstra() {
		for (int i = 1; i <= n; i++) {
			dist[i] = max;
		}
		dist[1] = 0;
		for (int i = 0; i < n; i++) {
			//t用来中转的
			int t = -1;
			for (int j = 1; j <= n; j++) {
				if (!st[j] && (t == -1 || dist[j] < dist[t])) {
					t = j;
				}
			}
			st[t] = true;
			for (int j = 1; j <= n; j++) {
				dist[j] = Math.min(dist[j], dist[t] + g[t][j]);
			}
		}
		return dist[n] == max ? -1 : dist[n];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				g[i][j] = max;
			}
		}
		for (int i = 0; i < m; i++) {
			int a = in.nextInt();
			int b = in.nextInt();
			int c = in.nextInt();
			g[a][b] = Math.min(g[a][b], c);
		}
		System.out.println(dijkstra());
	}
}

稀疏图(边少):用邻接表存储

//堆优化
import java.util.*;

class Pair {
	// 距离
	int x;
	// 结点
	int y;

	public Pair(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

public class Main {
	static int N = 100010, n, m, max = 0x3f3f3f3f;
	static int[] h = new int[N], w = new int[N], e = new int[N], ne = new int[N];
	static int idx;
	static int[] dist = new int[N];// 每个点到起点的距离
	static boolean[] st = new boolean[N];// 已经确认最短距离的点

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

	static int dijkstra() {
		//维护当前未在st中标记过且离源点最近的点(小根堆)
		PriorityQueue<Pair> queue = new PriorityQueue<Pair>((a, b) -> {
			return a.x - b.x;
		});
		for (int i = 1; i <= n; i++) {
			dist[i] = max;
		}
		dist[1] = 0;
		queue.add(new Pair(0, 1));
		while (!queue.isEmpty()) {
			//1.找到当前未在st中出现过且离源点最近的点
			Pair p = queue.poll();
			int distance = p.x;
			int t = p.y;
			if (st[t])
				continue;
			//2.标记该点
			st[t] = true;
			//3.用t更新其他点的距离
			for (int i = h[t]; i != -1; i = ne[i]) {
				int j = e[i];
				if (dist[j] > distance + w[i]) {
					dist[j] = distance + w[i];
					queue.add(new Pair(dist[j], j));
				}
			}
		}
		return dist[n] == max ? -1 : dist[n];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		for (int i = 1; i <= n; i++) {
			h[i] = -1;
		}
		for (int i = 0; i < m; i++) {
			int a = in.nextInt();
			int b = in.nextInt();
			int c = in.nextInt();
			add(a, b, c);
		}
		System.out.println(dijkstra());
	}
}

2. bellman-ford

【有边数限制的最短路】: 请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible,图中可能存在重边和自环, 边权可能为负数

import java.util.*;

class Node {
	int a, b, c;

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

public class Main {
	static int n, m, k, N = 510, M = 10010, max = 0x3f3f3f3f;
	static int[] dist = new int[N];
	static int[] back = new int[N];
	static Node[] edgs = new Node[M];

	static int bellman_ford() {
		Arrays.fill(dist, max);
		dist[1] = 0;
		for (int i = 0; i < k; i++) {
			back = Arrays.copyOf(dist, n + 1);
			for (int j = 0; j < m; j++) {
				Node edg = edgs[j];
				int a = edg.a;
				int b = edg.b;
				int c = edg.c;
				dist[b] = Math.min(dist[b], back[a] + c);
			}
		}

		return dist[n];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		k = in.nextInt();
		for (int i = 0; i < m; i++) {
			int a = in.nextInt();
			int b = in.nextInt();
			int c = in.nextInt();
			edgs[i] = new Node(a, b, c);
		}
		int t = bellman_ford();
		if (t > max / 2) {
			System.out.println("impossible");
		} else {
			System.out.println(t);
		}
	}
}

3.spfa

改进bellman-Ford,最好可以O(m),最坏还是O(nm),数据保证不存在负权回路

import java.util.*;

public class Main {
	static int N = 100010, n, m, max = 0x3f3f3f3f;
	static int[] h = new int[N], w = new int[N], e = new int[N], ne = new int[N];
	static int idx;
	static int[] dist = new int[N];// 每个点到起点的距离
	static boolean[] st = new boolean[N];// 已经确认最短距离的点
	static int[] q = new int[N];

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

	static int spfa() {
		int hh = 0, tt = -1;
		for (int i = 1; i <= n; i++) {
			dist[i] = max;
		}
		dist[1] = 0;
		q[++tt] = 1;
		st[1] = true;
		while (hh <= tt) {
			int t = q[hh++];
			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]) {
						q[++tt] = j;
						st[j] = true;
					}
				}
			}
		}
		return dist[n];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		for (int i = 1; i <= n; i++) {
			h[i] = -1;
		}
		for (int i = 0; i < m; i++) {
			int a = in.nextInt();
			int b = in.nextInt();
			int c = in.nextInt();
			add(a, b, c);
		}
		int t = spfa();
		if (t > max / 2) {
			System.out.println("impossible");
		} else {
			System.out.println(t);
		}
	}
}

Dijkstra和spfa的区别:

  1. Dijkstra:在Dijkstra算法中,我们重点维护的对象是距离起点最近的点。通过每一次循环来每次确定一个距离起点最近的点,然后用这个点去更新所有未确定的点。每次确定一个点我们都要做好标记让它不再被更新。由于图中可能出现环,所以已经被确定的点也有可能被push进我们的优先队列中,因此:当我们每次从优先队列中取出元素后,需要判断一下该点是否已经被确定,如果已经被确定,我们当然不能用它来更新其它点,因此把它continue掉。
  2. spfa:和Dijkstra算法不同的是,Spfa算法相比与点,它所维护的重点是与点相连接的边。也就是说虽然是用队列来储存点,但是算法的重点还是在取出点后遍历点的所有边,而边的遍历在Spfa算法中可能不止一次。这就导致了一点:在Dijkstra算法中,bool数组ch[N]只能修改一次,但是Spfa算法却允许ch[N]被反复修改,原因如下:
    • 在Dijkstra算法中,ch数组所记录的是已经被确定的点,确定的点是不能回归到不确定的状态的,因此当然不能被修改ch数组。
    • 而在Spfa算法中,ch数组所记录的是距离被更新过的点,它的定义就决定了ch数组是可以被多次修改的,原因是一个点的dis可以被多次修改。如果一个点已经在队列中等待用来更新其它的距离了,那么我们就没有再让一个点入队的必要了。

【判断是否存在负环】

import java.util.*;
public class Main{
    static int N = 2010,M = 10010,n,m,idx;
    static int[] h = new int[N],e = new int[M],ne = new int[M],w = new int[M];
    static int[] cnt = new int[N]; //存多少边
    static int[] dist = new int[N];//存点到起点的距离
    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<Integer> queue = new LinkedList<>();
        //Arrays.fill(dist,0x3f3f3f3f);
        //可能起点到不了负环,所以将所有的点都加入到对列中去
        for(int i = 1 ; i <= n ; i++ ){
            queue.offer(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];
                //如果j到起点的距离 > 上个点t到起点的距离 +  t到j的距离,那么就更新dist[j]
                if(dist[j] > dist[t] + w[i]){   
                    dist[j] =  dist[t] + w[i];
                    cnt[j] = cnt[t] + 1; // 每一个次更新就将边加上1

                    //如果边大于n点数,n个点最多只有n-1条边,如果>= n的话,就至少有一个点出现了两次,则说明出线负环
                    if(cnt[j] >= n) return true; 
                    //然后判断对列中有没有点j,有则插并标记,无则不插
                    if(!st[j]){
                        queue.offer(j);
                        st[j] = true;
                    }
                }
            }
        }
        return false;
    }
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        Arrays.fill(h,-1);
        while(m -- > 0){
            int a = scan.nextInt();
            int b = scan.nextInt();
            int c = scan.nextInt();
            add(a,b,c);
        }
        if(spfa()) System.out.println("Yes");
        else System.out.println("No");
    }
}

4.floyd

解决多源最短路问题,用于求任意两点之间的最短距离

import java.util.*;
public class Main{
    static int N = 210,n,m,k,INF = 0x3f3f3f3f;
    static int[][] g = 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 ++ ){
                    g[i][j] = Math.min(g[i][j],g[i][k] + g[k][j]);
                }
            }
        }
    }
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        k = scan.nextInt();
        for(int i = 1 ; i <= n ; i ++ ){
            for(int j = 1 ; j <= n ; j ++ ){
                if(i == j) g[i][j] = 0; //可能存在询问自身到自身的距离,所以需要存0
                else g[i][j] = INF; //然后其他都可以存成INF最大值
            }    
        }
        while(m -- > 0 ){
            int a = scan.nextInt();
            int b = scan.nextInt();
            int c = scan.nextInt();
            g[a][b] = Math.min(g[a][b],c); //这里可能存在重边,取最小的边
        }

        floyd();

        while(k -- > 0){
            int x = scan.nextInt();
            int y = scan.nextInt(); 
            int t  = g[x][y]; 
            //这里可能最后到不了目标点,但是可能路径上面存在负权边,然后将目标点更新了,所以不是到底== INF
            if(t > INF / 2) System.out.println("impossible");
            else System.out.println(t);

        }
    }
}

五、求最小生成树

1.Prim算法

prim算法(普利姆算法):对图G(V,E)设置集合S,存放已访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化所有从u能到达的顶点v与集合S之间的最短距离。执行n次(n为顶点个数),直到集合S已包含所有顶点。

import java.util.*;

public class Main {
	static int N = 510, n, m, max = 0x3f3f3f3f;
	static int[][] g = new int[N][N];// 每个点之间的距离
	static int[] dist = new int[N];// 每个点到起点的距离
	static boolean[] st = new boolean[N];// 已经确认最短距离的点

	static int prim() {
		for (int i = 1; i <= n; i++) {
			dist[i] = max;
		}
		dist[1] = 0;
		int res = 0;
		for (int i = 0; i < n; i++) {
			//t表示还没有找到数
			int t = -1;
			for (int j = 1; j <= n; j++) {
				if (!st[j] && (t == -1 || dist[j] < dist[t])) {
					t = j;
				}
			}
			//表示未连通,结束返回
			if (dist[t] == max)
				return max;
			res += dist[t];
			st[t] = true;
			for (int j = 1; j <= n; j++) {
				if (!st[j])
					//区别于dijkstra算法dist[j]+g[t][j],prim是g[t][j],表示的是j点到集合的距离,而不是到起点的距离
					dist[j] = Math.min(dist[j], g[t][j]);
			}
		}
		return res;
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				g[i][j] = max;
			}
		}
		for (int i = 0; i < m; i++) {
			int a = in.nextInt();
			int b = in.nextInt();
			int c = in.nextInt();
			g[a][b] = g[b][a] = Math.min(g[a][b], c);
		}
		int t = prim();
		if (t > max / 2) {
			System.out.println("impossible");
		} else {
			System.out.println(t);
		}
	}
}

2.Kruskal算法

克鲁斯卡尔算法思想包括两个部分:首先是带权图G中e条边的权值的排序;其次是判断新选取的边的两个顶点是否属于同一个连通分量,由于初始将所有顶点看成独立的结点,使用并查集进行处理,入边等操作

import java.util.*;

class Edgs implements Comparable<Edgs> {
	int a, b, w;

	public Edgs(int a, int b, int w) {
		this.a = a;
		this.b = b;
		this.w = w;
	}

	public int compareTo(Edgs e) {
		return Integer.compare(w, e.w);
	}
}

public class Main {
	static int N = 100010,M=2*N;
	static int[] p = new int[N];
	static Edgs[] edgs = new Edgs[M];

	static int find(int x) {
		if (p[x] != x) {
			p[x] = find(p[x]);
		}
		return p[x];
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		String[]strs=in.nextLine().split(" ");
		int n = Integer.parseInt(strs[0]);
		int m = Integer.parseInt(strs[1]);
		for (int i = 1; i <= n; i++) {
			p[i] = i;
		}
		for (int i = 0; i < m; i++) {
		    strs=in.nextLine().split(" ");
			int a = Integer.parseInt(strs[0]);
			int b = Integer.parseInt(strs[1]);
			int w = Integer.parseInt(strs[2]);
			edgs[i] = new Edgs(a, b, w);
		}
		Arrays.sort(edgs, 0, m);
		int res = 0, cnt = 0;
		for (int i = 0; i < m; i++) {
			int a = edgs[i].a, b = edgs[i].b, w = edgs[i].w;
			a = find(a);
			b = find(b);
			//a,b结点还未连通上,进行连通操作
			if (a != b) {
				p[a] = b;
				res += w;
				cnt++;
			}
		}
		if (cnt < n - 1) {
			System.out.println("impossible");
		} else {
			System.out.println(res);
		}
	}
}

六、二分图

二分图也叫二部图,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。

无向图G为二分图的充分必要条件是,G至少有两个顶点,且其所有回路的长度均为偶数,无奇数环

1.染色法判定二分图

import java.util.*;
public class Main{
    static int N = 100010,M = N*2,n,m,idx;
    static int[] h = new int[N],e = new int[M],ne = new int[M];
    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){
        color[u] = c; //首先将点u染色成c

        //然后遍历一下该点的所有边、
        for(int i = h[u]; i != -1; i = ne[i]){
            int j = e[i];
            //如果该点还没有染色
            if(color[j] == 0){
                //那就将该点进行染色,3-c是因为我们是将染色成1和2,如果是1,那就将对应的染成2,就用3来减法得出
                if(!dfs(j,3-c)) return false; //如果染完色之后返回false,那就说明里面含有奇数环,那就返回false
            }   
                //如果该点已经染过颜色吗,然后点的颜色跟我c的颜色是一样的,那就说明存在奇数环,返回false 
            else if(color[j] == c) return false; 
        }
        //最后如果很顺利的染完色,那就说明没有奇数环,那就返回true;
        return true;
    }
    public static void main(String[] args){
        Scanner scan =  new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        Arrays.fill(h,-1);
        while(m -- > 0){
            int a = scan.nextInt();
            int b = scan.nextInt();
            add(a,b);add(b,a);//因为是无向边,所以双方指向双方的两条边
        }
        boolean flag = true;
        for(int i = 1 ; i <= n ; i ++ ){
            //如果该点还没有染色
            if(color[i] == 0){
                //那就进行染色操作,第一个点可以自行定义1或者2,表示黑白
                if(!dfs(i,1)){
                    flag = false; //如果返回了false,说明有奇数环就将结束,输出No,否则输出Yes
                    break;
                }    
            }

        }
        if(flag) System.out.println("Yes");
        else System.out.println("No");
    }
}

2.二分图的最大匹配

给定一个二分图,左半部分有n1个点(1~n1),右半部分n2个点,二分图共包含m条边,求出二分图的最大匹配数

import java.util.*;

public class Main {

	static final int N = 510, M = 100010;
	//邻接表存储:h链表头;e结点值;ne结点的next值
	static int[] h = new int[N], e = new int[M], ne = new int[M];
	static int[] match = new int[N];
	static boolean st[] = new boolean[N];
	static int idx;

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

	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) {
		Scanner in = new Scanner(System.in);
		int n1 = in.nextInt();
		int n2 = in.nextInt();
		int m = in.nextInt();
		for (int i = 0; i < N; i++) {
			h[i] = -1;
		}
		while ((m--) > 0) {
			int a = in.nextInt();
			int b = in.nextInt();
			add(a, b);
		}
		int res = 0;
		for (int i = 1; i <= n1; i++) {
			for (int j = 0; j < st.length; j++) {
				st[j] = false;
			}
			if (find(i))
				res++;
		}
		System.out.println(res);
	}
}

你可能感兴趣的:(算法,算法,图论)