算法导论(七)之图算法

版权声明:欢迎大家转载,指正。 https://blog.csdn.net/yin__ren/article/details/83421418

1. 基本图算法

1. 广度优先搜索(BFS)

从临近源顶点s最近的顶点开始,通过对图G的边的探索发现从源顶点s能够抵达的每个顶点

在这里插入图片描述

1. 伪代码

BFS(G,s)
	for each vertex u 属于 G.V - {S}
		u.color = WHITE
		u.d = 无穷
		u.pi = NIL
	s.color = GRAY
	s.d = 0
	s.pi = NIL
	Q = null
	ENQUEUE(Q,s) 
	while Q != null
		u = DEQUEUE(Q)
		for each v 属于 G.Adj[u]
			if v.color == WHITE
				v.color = GRAY
				v.d = u.d + 1
				v.pi = u
				ENQUEUE(Q,v)
		u.color = BLACK

2. 时间复杂度

T ( n ) = O ( V + E ) T(n) = O(V + E)

3. java 代码实现

	/**
     * Broadth First Search
     *
     * @param graph 用于存放图中每个结点的邻接表
     *              key:Character
     *              value:该结点的邻接表 LinkedList<Character>
     * @param map   用于存放每个结点与顶点的距离
     *              key:Character
     *              value:距离
     * @param start 起始顶点
     */
    public static void BFS(HashMap<Character, LinkedList<Character>> graph, HashMap<Character, Integer> map, char start) {
        Queue<Character> q = new LinkedList<>();
        q.add(start); //将起始顶点加入队列
        map.put(start, 0);
        int i = 0;
        while (!q.isEmpty()) {
            //取出队首元素
            char top = q.poll();
            i++;
            System.out.println("The" + i + "th element: " + top + "Distance from start is: " + map.get(top));
            //计算周边未访问过的结点的距离
            int distance = map.get(top) + 1;
            //访问队首元素结点的邻接表
            for (Character c : graph.get(top)) {
                //在该邻接表中,如果某元素还没被访问到,说明还未遍历,则访问这个结点
                if (!map.containsKey(c)) {
                    map.put(c, distance);
                    q.add(c);
                }
            }
        }
    }


//测试
	//构造图
    private static HashMap<Character, LinkedList<Character>> initGraph(){
        //构造各顶点
        LinkedList<Character> list_s = new LinkedList<Character>();
        list_s.add('w');
        list_s.add('r');
        LinkedList<Character> list_w = new LinkedList<Character>();
        list_w.add('s');
        list_w.add('i');
        list_w.add('x');
        LinkedList<Character> list_r = new LinkedList<Character>();
        list_r.add('s');
        list_r.add('v');
        LinkedList<Character> list_x = new LinkedList<Character>();
        list_x.add('w');
        list_x.add('i');
        list_x.add('u');
        list_x.add('y');
        LinkedList<Character> list_v = new LinkedList<Character>();
        list_v.add('r');
        LinkedList<Character> list_i = new LinkedList<Character>();
        list_i.add('u');
        list_i.add('x');
        list_i.add('w');
        LinkedList<Character> list_u = new LinkedList<Character>();
        list_u.add('i');
        list_u.add('x');
        list_u.add('y');
        LinkedList<Character> list_y = new LinkedList<Character>();
        list_y.add('u');
        list_y.add('x');

        //构造图
        HashMap<Character, LinkedList<Character>> graph = new HashMap<Character, LinkedList<Character>>();
        graph.put('s', list_s);
        graph.put('w', list_w);
        graph.put('r', list_r);
        graph.put('x', list_x);
        graph.put('v', list_v);
        graph.put('i', list_i);
        graph.put('y', list_y);
        graph.put('u', list_u);

        return graph;
    }

    public static void main(String[] args) {
        //构造图
        HashMap<Character, LinkedList<Character>> graph = initGraph();
        //记录每个顶点离起始点的距离,也即最短距离
        HashMap<Character, Integer> dist = new HashMap<Character, Integer>();
        //遍历的起始点
        char start = 's';
        //调用广度优先方法
        BFS(graph, dist, start);
    }

2. 深度优先搜索(DFS)

从当前访问顶点开始,探索图的边以发现图中的每个顶点

在这里插入图片描述

1. 伪代码

DFS(G)
	for each vertex u 属于 G.V
		u.color = WHITE
		u.pi = NIL
	time = 0
	for each vertex u 属于 G.V
		if u.color == WHITE
			DFS-VISIT(G,u)

DFS-VISIT(G,u)
	time = time + 1	//white vertex u has just been discovered
	u.d = time
	u.color = GRAY
	for each v 属于 G:Adj[u]
		if v.color == WHITE 
			v.pi = u
			DFS-VISIT(G,v)
	u.color = BLACK		//blacken u;it is finished
	time = time + 1
	u.f = time

2. 时间复杂度

T ( n ) = θ ( V + E ) T(n) = \theta(V + E)

3. java 代码实现

	/**
     * Depth First Search
     *
     * @param graph   用于存放图中每个结点的邻接表
     * @param visited 用于存放每个结点与顶点的距离
     */
    public static void DFS(HashMap<Character, LinkedList<Character>> graph, HashMap<Character, Boolean> visited) {
        visit(graph, visited, 'u');
        visit(graph, visited, 'w');
    }

    static int count = 0;//通过一个全局变量count记录了进入每个节点和离开每个节点的时间
    /**
     * @param graph   用于存放图中每个结点的邻接表
     *                key:Character
     *                value:该结点的邻接表 LinkedList<Character>
     * @param visited 用于存放每个结点与顶点的距离
     *                key:Character
     *                value:距离
     * @param start   起始节点
     */
    private static void visit(HashMap<Character, LinkedList<Character>> graph, HashMap<Character, Boolean> visited, char start) {
        if (!visited.containsKey(start)) {
            count++;
            System.out.println("The time into element: " + start + ":" + count);//记录进入该结点的时间
            //将该结点标志为已访问
            visited.put(start, true);
            //访问队首元素结点的邻接表
            for (char c : graph.get(start)) {
                //递归访问其他邻近结点
                if (!visited.containsKey(c)) {
                    visit(graph, visited, c);
                }
            }
            count++;
            System.out.println("The time out element: " + start + ":" + count);// 记录离开该节点的时间
        }
    }

    //构造图
    private static HashMap<Character, LinkedList<Character>> initDFSGraph() {
        //构造各顶点
        LinkedList<Character> list_u = new LinkedList<Character>();
        list_u.add('v');
        list_u.add('x');
        LinkedList<Character> list_v = new LinkedList<Character>();
        list_v.add('y');
        LinkedList<Character> list_y = new LinkedList<Character>();
        list_y.add('x');
        LinkedList<Character> list_x = new LinkedList<Character>();
        list_x.add('v');
        LinkedList<Character> list_w = new LinkedList<Character>();
        list_w.add('y');
        list_w.add('z');
        LinkedList<Character> list_z = new LinkedList<Character>();
        //构造图
        HashMap<Character, LinkedList<Character>> graph = new HashMap<Character, LinkedList<Character>>();
        graph.put('u', list_u);
        graph.put('v', list_v);
        graph.put('y', list_y);
        graph.put('x', list_x);
        graph.put('w', list_w);
        graph.put('z', list_z);

        return graph;
    }

    public static void main(String[] args) {
        HashMap<Character, LinkedList<Character>> graph = initDFSGraph();
        HashMap<Character, Boolean> visited = new HashMap<Character, Boolean>();
        //调用深度优先遍历方法
        DFS(graph, visited);
    }

3. 拓扑排序

拓扑排序:找出有向无回路图G = (V, E)中顶点的一个线性序,使得(u, v)如果是图中的一条边,那么在这个线性序中u在v前出现

1. 伪代码

//对有向无环图
TOPOLOGICAL-SORT(G)
	call DFS(G) to compute finish times v. f for each vertex v
	as each vertex is finished,insert it onto the front of a linked list
	return the link list of vertices

2. 时间复杂度

T ( n ) = θ ( V + E ) T(n) = \theta(V + E)

3. Java 代码实现

	public void topoSort() throws Exception{
        int count = 0;
        
        Queue<Vertex> queue = new LinkedList<>();// 拓扑排序中用到的栈,也可用队列.
        //扫描所有的顶点,将入度为0的顶点入队列
        Collection<Vertex> vertexs = directedGraph.values();
        for (Vertex vertex : vertexs)
            if(vertex.inDegree == 0)
                queue.offer(vertex);
        
        while(!queue.isEmpty()){
            Vertex v = queue.poll();
            System.out.print(v.vertexLabel + " ");
            count++;
            for (Edge e : v.adjEdges) 
                if(--e.endVertex.inDegree == 0)
                    queue.offer(e.endVertex);
        }
        if(count != directedGraph.size())
            throw new Exception("Graph has circle");
    }

4. 强连通分支

1. 伪代码

STRONGLY-CONNECTED-COMPONENTS(G)
	call DFS(G) to compute finishing times u. f for each vertex u
	compute G^T
	call DFS(G^T),but in the main loop of DFS,consider the vertices
		in order of decreasing u. f(as computed in line 1)
	output the vertices of each tree in the depth-first forest formed in line 3 as a separate strongly connected component

2. 最小生成树

1. Kruskal算法(加边法)

1. 伪代码

MST-KRUSKAL(G,w)
	A = null
	for each vertex v 属于G.V
		MAKE-SET(v)
	sort the edges of G.E into nondecreasing order by weight w
	for each edges(u,v)属于G.E,taken in nondecreasing order by weight
		if FIND-SET(v) != FIND-SET(v)
			A = A and {(u,v)}
			UNION(u,v)
	return A

2. 时间复杂度

T ( n ) = E l o g V T(n) = E * logV

3. Java 代码实现

	/**
     * 求最小树的Kruskal算法
     * 算法思想:克鲁斯卡尔算法从另一个途径求网中的最小生成树。假设联通网N=(V,{E}),则令
     * 最小生成树的厨师状态为只有n个顶点而无边的非连通图T=(V,{}),途中每个顶点自称一个连通分量。
     * 在E中选择代价最小的边,若该边衣服的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边
     * 而选择下一条最小的边。以此类推,直至T中所有的顶点都在同一连通分量上为止。
     *
     * @param V 图中的节点集合
     * @param E 图中边的集合
     */
    public static void KRUSKAL(int[] V, Edge[] E) {
        Arrays.sort(E);//将边按照权重w升序排序
        ArrayList<HashSet> sets = new ArrayList<HashSet>();
        for (int i = 0; i < V.length; i++) {
            HashSet set = new HashSet();
            set.add(V[i]);
            sets.add(set);
        }
        for (int i = 0; i < E.length; i++) {//遍历边
            int start = E[i].i, end = E[i].j;
            int counti = -1, countj = -2;
            for (int j = 0; j < sets.size(); j++) {//遍历节点
                HashSet set = sets.get(j);
                if (set.contains(start)) {
                    counti = j;
                }
                if (set.contains(end)) {
                    countj = j;
                }
            }
            if (counti < 0 || countj < 0) {
                System.err.println("没有在子树中找到节点,错误");
            }
            if (counti != countj) {
                System.out.println("输出start=" + start + "||end=" + end + "||w=" + E[i].w);
                HashSet setj = sets.get(countj);
                sets.remove(countj);
                HashSet seti = sets.get(counti);
                sets.remove(counti);
                seti.addAll(setj);
                sets.add(seti);
            } else {
                System.out.println("他们在一棵子树中,不能输出start=" + start + "||end=" + end + "||w=" + E[i].w);
            }
        }
    }

    public static void main(String[] args) {
        int[] V = {1, 2, 3, 4, 5, 6};
        Edge[] E = new Edge[10];
        E[0] = new Edge(1, 2, 6);
        E[1] = new Edge(1, 3, 1);
        E[2] = new Edge(1, 4, 5);
        E[3] = new Edge(2, 3, 5);
        E[4] = new Edge(2, 5, 3);
        E[5] = new Edge(3, 4, 5);
        E[6] = new Edge(3, 5, 6);
        E[7] = new Edge(3, 6, 4);
        E[8] = new Edge(4, 6, 2);
        E[9] = new Edge(5, 6, 6);
        MiniSpanTree.KRUSKAL(V, E);
    }

2. Prim算法(加点法)

1. 伪代码

MST-PRIM(G,w,r)
	for each u 属于 G.V
		v:key = 无穷
		v:pi = NIL
	r:key = 0
	Q = G.V
	while Q != null
		u = EXTRACT-MIN(Q)
		for each v属于G.Adj[u]
			if v 属于 Q and w(u,v) < v.key
				v.pi = u
				v.key = w(u,v)

2. 时间复杂度

T ( n ) = E l o g V T(n) = E * logV

3. Java 代码实现

/**
     * 求图最小生成树的PRIM算法
     * 基本思想:假设N=(V,{E})是联通网,TE是N上的最想生成树中的变得集合。算法从U={u0}(u0属于V),
     * TE={}开始,重复执行下述操作:在所有的u属于U,v属于V-U的边(u,v)属于E中找到一条代价最小
     * 的边(u0,v0)并入集合TE,同事v0并入U,直至U=V为止。此时TE中必有n-1条边,则T=(V,{TE})
     * 为N的最小生成树。
     *
     * @param graph 图
     * @param start 开始节点
     * @param n     图中节点数
     */
    public static void PRIM(int[][] graph, int start, int n) {
        int[][] mins = new int[n][2];//用于保存集合U到V-U之间的最小边和它的值,mins[i][0]值表示到该节点i边的起始节点
        //值为-1表示没有到它的起始点,mins[i][1]值表示到该边的最小值,
        //mins[i][1]=0表示该节点已将在集合U中
        for (int i = 0; i < n; i++) {//初始化mins

            if (i == start) {
                mins[i][0] = -1;
                mins[i][1] = 0;
            } else if (graph[start][i] != -1) {//说明存在(start,i)的边
                mins[i][0] = start;
                mins[i][1] = graph[start][i];
            } else {
                mins[i][0] = -1;
                mins[i][1] = Integer.MAX_VALUE;
            }
        }
        for (int i = 0; i < n - 1; i++) {
            int minV = -1, minW = Integer.MAX_VALUE;
            for (int j = 0; j < n; j++) {//找到mins中最小值,使用O(n^2)时间

                if (mins[j][1] != 0 && minW > mins[j][1]) {
                    minW = mins[j][1];
                    minV = j;
                }
            }
            mins[minV][1] = 0;
            System.out.println("最小生成树的第" + i + "条最小边=<" + (mins[minV][0] + 1) + "," + (minV + 1) + ">,权重=" + minW);
            for (int j = 0; j < n; j++) {//更新mins数组
                if (mins[j][1] != 0) {
                    if (graph[minV][j] != -1 && graph[minV][j] < mins[j][1]) {
                        mins[j][0] = minV;
                        mins[j][1] = graph[minV][j];
                    }
                }
            }
        }
    }

	 public static void main(String[] args) {
        int[][] tree = {
                {-1, 6, 1, 5, -1, -1},
                {6, -1, 5, -1, 3, -1},
                {1, 5, -1, 5, 6, 4},
                {5, -1, 5, -1, -1, 2},
                {-1, 3, 6, -1, -1, 6},
                {-1, -1, 4, 2, 6, -1}
        };
        MiniSpanTree.PRIM(tree, 0, 6);
     }

3. 单源最短路径

1. Bellman-Ford 算法

1. 伪代码

BELLMAN-FORD(G, w, s)
   INITIALIZE-SINGLE-SOURCE(G, s)
   for i  1 to |V[G]| - 1
        do for each edge (u, v)  E[G]
             do RELAX(u, v, w)
   // 检查是否存在权值为负的环
   for each edge (u, v)  E[G]
        do if d[v] > d[u] + w(u, v)
             then return FALSE
   return TRUE

2. 时间复杂度

T ( n ) = O ( E V ) T(n) = O(E * V)

3. java 代码实现

public class BellmanFord {
    public static void main(String[] args) {
        //创建图
        Edge[] edges = initEdge();
        bellmanFord(edges);
    }

    //bellmanFord 算法
    private static void bellmanFord(Edge[] edges) {
        //存放到各个节点所需要消耗的时间
        HashMap<String, Integer> costMap = new HashMap<String, Integer>();
        //到各个节点对应的父节点
        HashMap<String, String> parentMap = new HashMap<String, String>();

        //初始化各个节点所消费的,当然也可以再遍历的时候判断下是否为Null
        //i=0的时候
        costMap.put("A", 0); //源点
        costMap.put("B", Integer.MAX_VALUE);
        costMap.put("C", Integer.MAX_VALUE);
        costMap.put("D", Integer.MAX_VALUE);
        costMap.put("E", Integer.MAX_VALUE);

        //进行节点数n-1次循环
        for (int i = 1; i < costMap.size(); i++) {//遍历各个点
            for (int j = 0; j < edges.length; j++) {//遍历每条边
                Edge edge = edges[j];
                //该边起点目前总的路径大小
                int startPointCost = costMap.get(edge.getStartPoint()) == null ? 0 : costMap.get(edge.getStartPoint());
                //该边终点目前总的路径大小
                int endPointCost = costMap.get(edge.getEndPoint()) == null ? Integer.MAX_VALUE : costMap.get(edge.getEndPoint());
                //如果该边终点目前的路径大小 > 该边起点的路径大小 + 该边权重 ,说明有更短的路径了
                if (endPointCost > (startPointCost + edge.getWeight())) {
                    costMap.put(edge.getEndPoint(), startPointCost + edge.getWeight());
                    parentMap.put(edge.getEndPoint(), edge.getStartPoint());
                }
            }
        }
        //在进行一次判断是否存在负环路
        boolean hasRing = false;
        for (int j = 0; j < edges.length; j++) {
            Edge edge = edges[j];
            int startPointCost = costMap.get(edge.getStartPoint()) == null ? 0 : costMap.get(edge.getStartPoint());
            int endPointCost = costMap.get(edge.getEndPoint()) == null ? Integer.MAX_VALUE : costMap.get(edge.getEndPoint());
            if (endPointCost > (startPointCost + edge.getWeight())) {
                System.out.print("\n图中存在负环路,无法求解\n");
                hasRing = true;
                break;
            }
        }
        //结果打印
        if (!hasRing) {
            //打印出到各个节点的最短路径
            for (String key : costMap.keySet()) {
                System.out.print("\n到目标节点" + key + "最低耗费:" + costMap.get(key));
                if (parentMap.containsKey(key)) {
                    List<String> pathList = new ArrayList<String>();
                    String parentKey = parentMap.get(key);
                    while (parentKey != null) {
                        pathList.add(0, parentKey);
                        parentKey = parentMap.get(parentKey);
                    }
                    pathList.add(key);
                    String path = "";
                    for (String k : pathList) {
                        path = path.equals("") ? path : path + " --> ";
                        path = path + k;
                    }
                    System.out.print(",路线为" + path);
                }
            }
        }
    }
    //初始化图
    private static Edge[] initEdge() {
        Edge ab = new Edge("A", "B", -1);
        Edge ac = new Edge("A", "C", 4);
        Edge bc = new Edge("B", "C", 3);
        Edge be = new Edge("B", "E", 2);
        Edge ed = new Edge("E", "D", -3);
        Edge dc = new Edge("D", "C", 5);
        Edge bd = new Edge("B", "D", 2);
        Edge db = new Edge("D", "B", 1);
        //从起点A出发,步骤少的排前面
        Edge[] edges = new Edge[]{ab, ac, bc, be, bd, ed, dc, db};

        return edges;
    }
    //邻接表来实现图
    static class Edge {
        //起点id
        private String startPoint;
        //结束点id
        private String endPoint;
        //该边的权重
        private int weight;

        public Edge(String startPoint, String endPoint, int weight) {
            this.startPoint = startPoint;
            this.endPoint = endPoint;
            this.weight = weight;
        }

        public String getStartPoint() {
            return startPoint;
        }

        public String getEndPoint() {
            return endPoint;
        }

        public int getWeight() {
            return weight;
        }
    }
}

2. Dijkstra 算法

1. 伪代码

DIJKSTRA(G,w,s)
	INITIALIZE-SINGLE-SORT(G,s)
	S = null
	Q = G.V
	while Q != null
		u = EXTRACT-MIN(Q)
		S = S + {u}
		for each vertex v 属于G.Adj[u]
			RELAX(u,v,w)

2. 时间复杂度

T ( n ) = O ( E + V l o g V ) T(n) = O(E + VlogV)

3. java 代码实现

	public static void main(String[] args) {
        //final int No = Integer.MAX_VALUE;//不能使用 Integer.MAX_VALUE,避免两个 No 相加造成溢出
        final int No = 10000;
        int[][] weight = {
                {0, 6, No, No, 7},
                {No, 0, 5, -4, 8},
                {No, -2, 0, No, No},
                {2, No, 7, 0, No},
                {No, No, -3, 9, 0}
        };
        dijkstra(weight, 0);
    }

    /**
     * dijkstra 算法实现
     *
     * @param weight 有向图的权重矩阵
     * @param start  起点编号
     */
    public static int[] dijkstra(int[][] weight, int start) {
        int num = weight.length;//顶点个数
        int[] shortPath = new int[num];//存放从 start 到其他各点的最短路径
        String[] path = new String[num];//存放从start到其他各点的最短路径的字符串表示
        for (int i = 0; i < num; i++) {
            path[i] = new String(start + "-->" + i);
        }
        int[] visited = new int[num];//标记当前该顶点的最短路径是否已经求出,1表示已求出

        //初始化
        shortPath[start] = 0;
        visited[start] = 1;

        for (int i = 1; i < num; i++) {//要遍历 n-1 次
            int k = -1;//选出距 start 点最近的未标记点
            int dmin = Integer.MAX_VALUE;
            for (int j = 0; j < num; j++) {//遍历当前节点附近的其余节点
                if (visited[j] == 0 && weight[start][j] < dmin) {
                    dmin = weight[start][j];//修正最短距离
                    k = j;//记录下最近点标记
                }
            }
            shortPath[k] = dmin;//将新选出的顶点标记为已求出最短路径,且到start的最短路径就是dmin
            visited[k] = 1;

            for (int j = 0; j < num; j++) {//以k为中间点,修正从start到未访问各点的距离
                if (visited[j] == 0 && weight[start][k] + weight[k][j] < weight[start][j]) {
                    weight[start][j] = weight[start][k] + weight[k][j];

                    path[i] = path[k] + "-->" + i;
                }
            }
        }

        //打印结果
        printPath(path, num, start);
        printResult(shortPath, num, start);

        return shortPath;
    }

    //打印所经过的各个点的记录
    public static void printPath(String[] path, int num, int start) {
        for (int i = 0; i < num; i++) {
            System.out.println("从" + start + "出发到" + i + "的最短路径为: " + path[i]);
        }
        System.out.println("========================================");
    }

    //打印结果
    public static void printResult(int[] shortPath, int num, int start) {
        for (int i = 0; i < num; i++) {
            System.out.println("从" + start + "出发到" + i + "的最短距离为: " + shortPath[i]);
        }
    }

4. 全源最短路径

1. Floyd-Warshall 算法

1. 伪代码

FLOYD-WARSHALL(W)
	n = W.rows
	D(0) = W
	for k = 1 to n
		let D(k) = d(k)_ij be a new nXn matrix
		for i = 1 to n
			for j = 1 to n
				d(k)_ij = min(d(k-1)_ij,d(k-1)_ik + d(k-1)_kj)
	return D(n)

2. 时间复杂度

T ( n ) = O ( n 3 ) T(n) = O(n^3)

3. java 代码实现

public class FloydWarShall {

    private static int INF = Integer.MAX_VALUE;

    public static void main(String[] args) {
        int[][] matrix = {
                {0, 3, 8, INF, -4},
                {INF, 0, INF, 1, 7},
                {INF, 4, 0, INF, INF},
                {2, INF, -5, 0, INF},
                {INF, INF, INF, 6, 0},
        };
        floyd(matrix);
        System.out.println("============最短路径长度============");
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                System.out.print(matrix[i][j] + "  ");
            }
            System.out.println();
        }
    }

    //FloydWarShall 算法
    public static void floyd(int[][] dist) {
        int size = dist.length;
        for (int k = 0; k < size; k++) {
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    if (dist[i][k] != INF && dist[k][j] != INF 
                    		&& dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }
    }
}

2. Johnson 算法

在这里插入图片描述
在这里插入图片描述

1. 伪代码

在这里插入图片描述

2. 时间复杂度

T ( n ) = O ( V 2 l o g V + V E ) T(n) = O(V^2 * logV + V * E)

3. java 代码实现


5. 最大流

1. Ford-Fulkerson 算法

1. 伪代码

FORK-FULKERSON(G,s,t)
	for each edge(u,v) 属于 G.E
		(u,v).f = 0
	while there exists a path p form s to t in the residual network G_f
		c_f(p) = min{c_f(u,v):(u,v) is in p}
		for each edge(u,v) in p
			if (u,v) 属于 E
				(u,v).f = (u,v).f + c_f(p)
			else (v,u).f = (v,u).f - c_f(p)

2. 时间复杂度

T ( n ) = O ( E f ) T(n) = O(E * |f|)

3. Java 代码实现

  • 计算残存网络
  • 计算增广路径
  • 沿着增广路径,重复增加流量,直到找到最大值为止

2. 最大二分匹配

public class MaxMatch {
    public static int n = 0, m = 0;  //二分图的左边和右边顶点数目

    /**
     * 如果能够找到已顶点start开始的增广路径返回true,否则返回false
     * 
     * @param map    给定的二分图,map[i][j]等于1表示i到j连通,为0则表示不连通
     * @param used
     * @param linked linked[i] = u表示顶点i与顶点u连接
     * @param start  当前start顶点出发,寻找增广路径
     * @return
     */
    public static boolean dfs(int[][] map, boolean[] used, int[] linked, int start) {
        for (int i = 0; i < m; i++) {
            if (used[i] == false && map[start][i] == 1) {
                used[i] = true;
                if (linked[i] == -1 || dfs(map, used, linked, linked[i])) {
                    linked[i] = start;
                    return true;
                }
            }
        }
        return false;
    }

    public static int getMaxNum(int[][] map) {
        int count = 0;
        int[] linked = new int[m];
        for (int i = 0; i < m; i++) {
            linked[i] = -1;
        }
        for (int i = 0; i < n; i++) {
            boolean[] used = new boolean[m];  //初始化m部分顶点均为被访问
            if (dfs(map, used, linked, i)) {//从顶点i出发能够得到一条增广路径
                count++;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        int[][] map = new int[n][m];
        int k = in.nextInt();     //二分图中边的数目
        for (int i = 0; i < k; i++) {
            int a = in.nextInt();   //n部分中的顶点
            int b = in.nextInt();   //m部分中顶点
            map[a][b] = 1;
        }
        System.out.println(getMaxNum(map));
    }
}

猜你喜欢

转载自blog.csdn.net/yin__ren/article/details/83421418