第十一章 图论

树与图的关系

1、没有圈的连通图,就是树(连通图:任意两点相连,强连通图:有向图任意两点相连)

2、没有圈的非联通图,就是森林

3、一棵树边数等于顶点数-1

4、边数等于顶点数-1的连通图就是树

有向图的术语:

没有圈的有向图叫做有向无环图,(DAG  directed acyclic graph)

邻接表的表示:

package org.lanqiao.algo.elementary._12_graph;

import java.util.ArrayList;
import java.util.List;

/*邻接表*/
public class GraphNode_AL {
  public int val;  //每一个顶点的意思
  private List<GraphNode_AL> neighbors;//邻居

  public boolean checked = false;

  public GraphNode_AL(int val) { //构造器
    this.val = val;
  }

  public GraphNode_AL getNeighbor(int i) {  //访问器,给他一个邻居的编号,返回邻居
    return neighbors.get(i);
  }

  public void add(GraphNode_AL node) {  //更改器,传一个邻居进来
    if (this.neighbors == null)   this.neighbors = new ArrayList<>();
    
    this.neighbors.add(node);
  }

  public int size() {
    return this.neighbors.size();
  }
}
package org.lanqiao.algo.elementary._12_graph.dfs;

import org.lanqiao.algo.elementary._12_graph.GraphNode_AL;

/*邻接表来表示图*/
public class Graph {
  public static void main(String[] args) {
    GraphNode_AL n1 = new GraphNode_AL(1);
    GraphNode_AL n2 = new GraphNode_AL(2);
    GraphNode_AL n3 = new GraphNode_AL(3);
    GraphNode_AL n4 = new GraphNode_AL(4);
    GraphNode_AL n5 = new GraphNode_AL(5);
    GraphNode_AL n6 = new GraphNode_AL(6);
    GraphNode_AL n7 = new GraphNode_AL(7);
    GraphNode_AL n8 = new GraphNode_AL(8);
    GraphNode_AL n9 = new GraphNode_AL(9);

    n1.add(n2);
    n1.add(n3);
    n1.add(n7);
    n1.add(n8);
    n1.add(n9);

    n2.add(n1);
    n2.add(n5);

    n3.add(n1);
    n3.add(n5);
    n3.add(n6);

    n4.add(n1);
    n4.add(n6);
    n5.add(n2);
    n5.add(n3);
    n6.add(n3);
    n6.add(n4);
    n7.add(n1);
    n8.add(n1);
    n9.add(n1);

    dfs(n9);


  }

  static void dfs(GraphNode_AL node) {
    System.out.println(node.val);
    node.checked = true;
    for (int i = 0; i < node.size(); i++) {
      GraphNode_AL graphNode = node.getNeighbor(i);
      if (graphNode.checked == false)
        dfs(graphNode);
    }
  }
}

边集的表示

package org.lanqiao.algo.elementary._12_graph;

/**
 * 边 的封装
 * 边集可以用来表示图
 */
public class Edge<T> implements Comparable<Edge> {
  private T start;
  private T end;
  private int distance;

  public Edge(T start, T end, int distance) {
    this.start = start;
    this.end = end;
    this.distance = distance;
  }

  public T getStart() {
    return start;
  }

  public void setStart(T start) {
    this.start = start;
  }

  public T getEnd() {
    return end;
  }

  public void setEnd(T end) {
    this.end = end;
  }

  public int getDistance() {
    return distance;
  }

  public void setDistance(int distance) {
    this.distance = distance;
  }

  @Override
  public String toString() {
    return start + "->" + end + ":" + distance;
  }

  @Override
  public int compareTo(Edge obj) {
    int targetDis = obj.getDistance();
    return distance > targetDis ? 1 : (distance == targetDis ? 0 : -1);
  }
}
package org.lanqiao.algo.elementary._12_graph;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MapBuilder {
  public List<Edge> build() {
    List<Edge> edges = new ArrayList<>();
    edges.add(new Edge("1", "2", 4));
    edges.add(new Edge("1", "3", -1));
    edges.add(new Edge("2", "3", 3));
    edges.add(new Edge("2", "4", 5));
    edges.add(new Edge("4", "5", 10));
    // Collections.sort(edges);
    return edges;
  }

  public int getPointNum() {
    return 5;
  }

  public List<Edge> build1() {
    List<Edge> edges = new ArrayList<>();
    edges.add(new Edge("1", "2", 4));
    edges.add(new Edge("1", "3", -1));
    edges.add(new Edge("2", "3", 3));
    edges.add(new Edge("2", "4", 5));
    edges.add(new Edge("4", "5", 10));
    edges.add(new Edge("0", "2", 10));
    edges.add(new Edge("0", "3", 10));
    edges.add(new Edge("0", "4", 1));
    edges.add(new Edge("0", "5", 1));

    Collections.sort(edges);
    return edges;
  }

  public int getPointNum1() {
    return 6;
  }
}

2、图的dfs

/*给定一个方阵,定义连通:上下左右相邻,并且值相同。
可以想象成一张地图,不同的区域被涂以不同颜色。
输入:
整数N, (N<50)表示矩阵的行列数
接下来N行,每行N个字符,代表方阵中的元素
接下来一个整数M,(M<1000)表示询问数
接下来M行,每行代表一个询问,
格式为4个整数,y1,x1,y2,x2,
表示询问(第y1行,第x1列) 与 (第y2行,第x2列) 是否连通。
连通输出true,否则false

例如:
10
0010000000
0011100000
0000111110
0001100010
1111010010
0000010010
0000010011
0111111000
0000010000
0000000000
3
0 0 9 9
0 2 6 8
4 4 4 6

程序应该输出:
false
true
true*/

import java.util.Scanner;

public class Main{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        char[][] graph =    new char[N][N];
        for(int i=0;i<N;i++){
            graph[i] = sc.nextLine().toCharArray();
        }
        int M =sc.nextInt();
        int[][] query = new int[M][4];
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < 4; j++) {
                query[i][j] = sc.nextInt();
            }
        }
        // M个起点和终点
        for (int i = 0; i < M; i++) {
            //对每个起点和终点,检查是否连通
            boolean ok_fal = check(graph, new int[N][N], query[i]);//一个例子一个例子查
            System.out.println(ok_fal);
        }

    }
    static boolean check(char[][] graph, int[][] label, int[] points){
        int x1 = points[0];
        int y1 = points[1];
        int x2 = points[2];
        int y2 = points[3];

        if(x1 ==x2 && y1 ==y2){
            return true;
        }

        int value = graph[x1][y1];
        boolean f1 = false;
        boolean f2 = false;
        boolean f3 = false;
        boolean f4 = false;
        if(x1-1>=0 && label[x1-1][y1]==0 &&graph[x1 - 1][y1] == value){
            label[x1 - 1][y1] = 1; // 坐标的位置标记为已访问
            points[0] = x1 - 1; // 把左边的点作为新起点,递归
            f1 = check(graph, label, points);
            //回溯
            label[x1 - 1][y1] = 0;
            points[0] = x1;
        }
        //往右走
        if (x1 + 1 < graph.length && label[x1 + 1][y1] == 0 && graph[x1 + 1][y1] == value) {
            label[x1 + 1][y1] = 1;
            points[0] = x1 + 1;
            f2 = check(graph, label, points);
            label[x1 + 1][y1] = 0;
            points[0] = x1;
        }
        //往上走
        if (y1 - 1 >= 0 && label[x1][y1 - 1] == 0 && graph[x1][y1 - 1] == value) {
            label[x1][y1 - 1] = 1;
            points[1] = y1 - 1;
            f3 = check(graph, label, points);
            label[x1][y1 - 1] = 0;
            points[1] = y1;
        }
        //往下走
        if (y1 + 1 < graph.length && label[x1][y1 + 1] == 0 && graph[x1][y1 + 1] == value) {
            label[x1][y1 + 1] = 1;
            points[1] = y1 + 1;
            f4 = check(graph, label, points);
            label[x1][y1 + 1] = 0;
            points[1] = y1;
        }
        return f1|| f2 || f3 || f4;
        //若有一个可以,那就可行

    }

}

2、/**
 * 输入一个m行n列的字符矩阵,统计字符“@”组成多少个八连块。
 *  如果两个字符“@”所在的格子相邻(横、竖或者对角线方向),就说它们属于同一个八连块。
 *
 *  分析:图的dfs
 */

public class Main{
    static char[][] data = {
            "*@@*@".toCharArray(),
            "**@*@".toCharArray(),
            "****@".toCharArray(),
            "@@@*@".toCharArray(),
            "@@**@".toCharArray(),
    };
    static int cnt;
    static void dfs(int r,int c){
        if(r<0 || r>=data.length || c<0 ||c>=data.length) return; //超出范围
        if( data[r][c] == '*'){
            return;
        }
        data[r][c] ='*';
        dfs(r+1,c);
        dfs(r-1,c);
        dfs(r, c + 1);
        dfs(r, c - 1);
        dfs(r + 1, c + 1);
        dfs(r + 1, c - 1);
        dfs(r - 1, c - 1);
        dfs(r - 1, c + 1);
    }

    public static void main(String[] args) {
        for(int i=0;i<data.length;i++){
            for(int j=0;j<data[0].length;j++){
                if(data[i][j] =='@'){
                    dfs(i,j);
                    ++cnt;  //这里不知道怎么个加法的话可以极端想想,比如全是×@×@×@,,这样的话就知道为什么是完成后+1了
                }
            }
        }
        System.out.println(cnt);
    }

}

3、拓扑排序 外加 判断有无环的功能 (这个我还不是很懂)

tip:邻接表表示是否有环 ,只有出度才是他的邻居,,,拓扑排序只能用于无环图,所以代码的时候要先判断是无环的才能往下进行。

class Main{
    static final int n = 4;//定点数
    static String[] v = {"a", "b", "c", "d"};//顶点内容
    static int[][] graph = {
            {0, 1, 0, 0},
            {0, 0, 0, 0},
            {0, 1, 0, 0},
            {0, 0, 1, 0},
    };
    //标记顶点访问状态,1:已经访问并返回,0:从未被方位,-1:正在递归访问还未退出
    static int[] vis = new int[n];
    //拓扑排序结果
    static int[] topo = new int[n];
    //标记topo数组的哪一位被改写
    static int t = n;
    public static void main(String[] args) {
        for(int i=0;i<n;i++){
            if(vis[i] == 1) continue;
            boolean bool = dfs(i);
            if(!bool){
                System.out.println(false);
                return;
            }
        }
        for(int i=0;i<n;i++){
            System.out.println(v[topo[i]]);
        }

    }

    static boolean dfs(int i){
        vis[i] = -1;
        for(int j=0;j<n;j++){
            if(graph[i][j]>0){//当前关注顶点i到顶点j有出度
                //此处,关于j顶点的递归还没有退出,前驱的状态是-1,后继的状态也是-1,说明在此次递归的链路上早就路过了j,现在是第二次路过j,
                // 一次递归链路两次经过j,只有一种情况,形成了环
                if (vis[j] < 0) return false;
          
                //如不考虑有环的情况,这里就照常继续就好了
                //j没被访问过,执行递归
                if (vis[j] == 0 && dfs(j) == false) return false;

            }
        }
        //整个递归是按照出度方向来的,所以上面的循环+递归走到尽头时,i没有出度,没有出度的点可以认为是最大的点(之一)
        topo[--t] = i;
        vis[i] = 1;
        return true;
    }
}

4、七桥问题(欧拉问题)

tip: 没有办法的叫欧拉回路(不能通过一笔画搞定的)能够通过一笔画搞定的就叫欧拉道路

欧拉道路:要求是一个无向联通图,而且最多只有两个奇点,(奇点的意思是度数为奇数),这种情况则一定是欧拉道路‘

如果有两个奇点,他们必须是起点和终点

如果奇点不存在,可以从任意点出发,最终一定会回到该店,称为欧拉回路

对于有向图:欧拉道路存在的充要条件是:最多只能有两个点的入度不等于初度,而且必须是其中一个点的出度恰好比入读大1,另一个的入度比出度大1.

import java.util.Stack;

class test6{
    //图的邻接矩阵
    private static int[][] graph = {
            {0, 1, 2, 1},
            {1, 0, 0, 0},
            {2, 0, 0, 1},
            {1, 0, 1, 0}
    };
    private static final int n = 4;
    //标记边的访问情况,因为通路是双向的,所以用二维数组
    private static int[][] vis = new int[n][n];
    static Stack<String> path = new Stack<>();
    //
    public static void main(String[] args) {
        euler(2);//从哪个定点开始???
        while (!path.isEmpty()){
            System.out.println(path.pop());
        }
    }

    static void euler(int u){
        //其他顶点
        for(int v =0;v<n;v++){
            //有边,且访问次数少于连接数
            if(graph[u][v]>0 && vis[u][v]<graph[u][v]){
                //路是双向的
                vis[u][v]++;
                vis[v][u]++;
                //v作为新的起点,递归
                euler(v);
                //已走u->v,将这一步走法加入栈中
                path.push((char)('A'+u)+"->"+(char)('A'+v));
            }
        }
    }

}

5、二分图:图的着色问题

n个定点的图,给每个顶点上色,是相邻的定点颜色不同。,问是否可以最多只用两种颜色上色?

对图染色所需的最少颜色数称为最小着色图,最小着色图为2的图称为二分图

import java.util.ArrayList;
import java.util.List;

public class test6{
    public static void main(String[] args) {
        MyNode n1 = new MyNode(1);
        MyNode n2 = new MyNode(2);
        MyNode n3 = new MyNode(3);
        MyNode n4 = new MyNode(4);
        n1.add(n2);
        n1.add(n4);

        n2.add(n1);
        n2.add(n3);

        n3.add(n2);
        n3.add(n4);

        n4.add(n1);
        n4.add(n3);

        //任意顶点都可以
        System.out.println(dfs(n1,1));
    }

    static boolean dfs(MyNode node ,int c){
        node.color= c;////同时标记已访问和具体颜色
        for(int i=0;i<node.size();i++){//遍历所有neighbor
            MyNode neighbor = (MyNode) node.getNeighbor(i);//具体的neighbor
            //如果相邻接点颜色一样,就返回false
            if(neighbor.color == c) return false;
            //如果没有被染色,,就然不同的染色然后进行递归
            if(neighbor.color ==0){
                boolean res = dfs(neighbor,-c);
                if(!res){
                    return false;
                }
            }
        }
        return  true;
    }
    static private class MyNode extends GraphNode{
        int color;
        public MyNode(int val){
            super(val);
        }
        public MyNode(int val, int color){
            super(val);
            this.color = color;
        }

    }
    static private class GraphNode{
        public int val;
        private List<GraphNode> neighbors;

        public boolean check = false;
        public GraphNode(int val){
            this.val = val;
        }
        public GraphNode getNeighbor(int i){
            return neighbors.get(i);
        }
        public void add(GraphNode node){
            if(this.neighbors ==null){
                this.neighbors = new ArrayList<>(); this.neighbors = new ArrayList<>();

                this.neighbors.add(node);
            }
        }
        public int size(){
            return this.neighbors.size();
        }
    }
}import java.util.ArrayList;
import java.util.List;

public class test6{
    public static void main(String[] args) {
        MyNode n1 = new MyNode(1);
        MyNode n2 = new MyNode(2);
        MyNode n3 = new MyNode(3);
        MyNode n4 = new MyNode(4);
        n1.add(n2);
        n1.add(n4);

        n2.add(n1);
        n2.add(n3);

        n3.add(n2);
        n3.add(n4);

        n4.add(n1);
        n4.add(n3);

        //任意顶点都可以
        System.out.println(dfs(n1,1));
    }

    static boolean dfs(MyNode node ,int c){
        node.color= c;////同时标记已访问和具体颜色
        for(int i=0;i<node.size();i++){//遍历所有neighbor
            MyNode neighbor = (MyNode) node.getNeighbor(i);//具体的neighbor
            //如果相邻接点颜色一样,就返回false
            if(neighbor.color == c) return false;
            //如果没有被染色,,就然不同的染色然后进行递归
            if(neighbor.color ==0){
                boolean res = dfs(neighbor,-c);
                if(!res){
                    return false;
                }
            }
        }
        return  true;
    }
    static private class MyNode extends GraphNode{
        int color;
        public MyNode(int val){
            super(val);
        }
        public MyNode(int val, int color){
            super(val);
            this.color = color;
        }

    }
    static private class GraphNode{
        public int val;
        private List<GraphNode> neighbors;

        public boolean check = false;
        public GraphNode(int val){
            this.val = val;
        }
        public GraphNode getNeighbor(int i){
            return neighbors.get(i);
        }
        public void add(GraphNode node){
            if(this.neighbors ==null){
                this.neighbors = new ArrayList<>(); this.neighbors = new ArrayList<>();

                this.neighbors.add(node);
            }
        }
        public int size(){
            return this.neighbors.size();
        }
    }
}

6、最小生成树问题

tip:图去掉一条边就是树

所有生成树中边的权重和最小的,称之为最小生成树

常用于网络构建等建设性问题的优化

有两个算法:prime算法 和 kruskal算法

kruskal算法:  这是一种贪心算法 :把所有的边的权重排个序,,从小往大的拍,从小的开始一个一个的放入一个新的tree的边集(加的过程唯一要注意的就是不能形成环),,tree里定点数 = 边的数+1 就不能再放了,如果再加的话就会形成回路,没有办法再是一棵树了 

package org.lanqiao.algo.elementary._12_graph.dfs;

import org.lanqiao.algo.elementary._11_tree.UnionFind;
import org.lanqiao.algo.elementary._12_graph.Edge;

import java.util.*;

public class Kruskal {
  private final List<Edge> edgeList;
  private final int n;//总顶点数

  private Set<Edge> T = new HashSet<>();//生成树的边集
  private Map pntAndNode = new HashMap();

  public Set<Edge> getT() {
    buildMST();
    return T;
  }

  public Kruskal(List<Edge> edgeList, int n) {
    this.edgeList = edgeList;
    //为每个顶点建立一个并查集的点
    for (Edge edge : edgeList) {
      pntAndNode.put(edge.getStart(), new UnionFind.UFNode());
      pntAndNode.put(edge.getEnd(), new UnionFind.UFNode());
    }
    this.n = n;
  }

  public static void main(String[] args) {
    List<Edge> edgeList = build();
    Kruskal obj = new Kruskal(edgeList, 5);
    // obj.buildMST();
    for (Edge e : obj.getT()) {
      System.out.println(e);
    }
  }

  private static List<Edge> build() {
    List<Edge> l = new ArrayList<>();
    l.add(new Edge("C", "D", 1));
    l.add(new Edge("C", "A", 1));
    l.add(new Edge("C", "E", 8));
    l.add(new Edge("A", "B", 3));
    l.add(new Edge("D", "E", 3));
    l.add(new Edge("B", "C", 5));
    l.add(new Edge("B", "E", 6));
    l.add(new Edge("B", "D", 7));
    l.add(new Edge("A", "D", 2));
    l.add(new Edge("A", "E", 9));

    return l;
  }

  /*构建MST的核心方法*/
  private void buildMST() {
    Collections.sort(edgeList);//排序
    //迭代
    for (Edge e : edgeList) {
      if (!ok(e))
        continue;
      //确认过了,就把边都加入
      T.add(e);

      if (T.size() == n - 1)
        return;//生成树的边数==总顶点数-1 =》 所有点都已经连接
    }
  }

  //并查集中查询e的起点和终点是否在一个集中
  private boolean ok(Edge e) {
    UnionFind.UFNode x = (UnionFind.UFNode) pntAndNode.get(e.getStart());
    UnionFind.UFNode y = (UnionFind.UFNode) pntAndNode.get(e.getEnd());
    if (UnionFind.find(x) != UnionFind.find(y)) {//在不同的集中
      UnionFind.union(x, y);//合并并返回true
      return true;
    }
    return false;
  }

}

图的bfs

7、最短路问题

给定两个顶点,在以这两个点为起始点和终点的路径中,边的权值和最小的路径,考虑权值为点之间的距离

单源最短路径:有三种算法:(单源就是一个起点,一个终点,,多源的话就比如多个起点和终点,这是就需要网络流算法了)

(1)bellman-ford: 每一趟扫描边集,以i和j作为起点和终点,如果s->i存在且s->i->j小于s->j则更新,在这一趟中没有更新的话就退出循环

(2)dijkstra

(3)floyed

先说第一个:bellman-ford算法

* bellman-ford算法
* 遍历所有的边,边有起点i和终点j,如果源点到起点的最短距离d[i]已经算出来,就比较d[j]和d[i]+cost,如果前者比后者大,就可以更新d[j]
* 如此往复,直到没有数据可更新,这样源点到所有顶点的最短距离就算出来了
*
* 对于上一个代码,可以先把边集提取出来,这样不用每次都扫描二维数组
* */

import java.util.ArrayList;
import java.util.List;

class Test48{
    static int[][] graph = {
            {0, 2, 5, 0, 0, 0, 0},
            {2, 0, 4, 6, 10, 0, 0},
            {5, 4, 0, 2, 0, 0, 0},
            {0, 6, 2, 0, 0, 1, 0},
            {0, 10, 0, 0, 0, 3, 5},
            {0, 0, 0, 1, 3, 0, 9},
            {0, 0, 0, 0, 5, 9, 0}
    };
    public static void main(String[] args) {
        edges =  buildEdges(graph); //把所有的边提出来,提出来的形式都是i到j 权为多少
        int [] shortestPath = shortpath(0);
        for(int e:shortestPath){
            System.out.println(e);
        }
    }

    static int[] shortpath(int s){
        int n = graph.length;
        int[]d = new int[n];
        for(int i=0;i<n;i++){
            d[i] = Integer.MAX_VALUE;
        }
        d[s] = 0;//到自己的距离为0 //我其实不知道这句话是用来干嘛的
        while(true){
            boolean update = false;

            for(Edge<Integer> e:edges){
                if(d[e.getStart()]!=Integer.MAX_VALUE && d[e.getEnd()]>d[e.getStart()]+e.getDistance()){
                    update = true;
                    d[e.getEnd()] = d[e.getStart()]+e.getDistance();
                }
            }
            if(!update){
                break;
            }
        }
        return d;
    }

    static List<Edge<Integer>> edges;

    static List<Edge<Integer>> buildEdges(int[][] garph){
        int n =graph.length;
        List<Edge<Integer>> edges = new ArrayList<>();
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                int cost = graph[i][j];
                if(cost>0){
                    edges.add(new Edge<>(i,j,cost));
                }
            }
        }
        return edges;
    }
}

然后是dijkstra算法

思路:找到最短距离已经确定的点,从他出发更新相邻顶点的最短距离,伺候不再关心图中最短路径已确定的点

 Dijkstra算法采用的是一种贪心的策略,声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,
* 初始时,原点 s 的路径权重被赋为 0 (dis[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),
* 同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s。
* 然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,OK,此时完成一个顶点,
* 然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,
* 如果是,那么就替换这些顶点在dis中的值。
* 然后,又从dis中找出最小值,重复上述动作,直到T中包含了图的所有顶点。

猜你喜欢

转载自blog.csdn.net/h_666666/article/details/87014706