图论Graph theory 基础

研究由点和边组成的数学模型
graph theory

应用:
交通运输
社交网络
互联网
工作安排
脑区活动
程序状态执行

图的分类

default

2

图的连通性

default

简单图(Simple Graph)

simple graph

图的表示

邻接表适合表示稀疏图(Sparse Graph)
邻接矩阵适合表示稠密图(Dense Graph)

邻接矩阵

表示无向图
default
表示有向图
default

邻接表

表示无向图
default

表示有向图
default

图的实现

邻接矩阵

public class DenseGraph {
    private int n;// 节点数
    private int m;// 边数
    private boolean directed;//是否为有向图
    private boolean[][] g;// 图的具体数据
    
    public DenseGraph(int n, boolean directed) {
        this.n = n;
        this.m = 0;
        this.directed = directed;
        g = new boolean[n][n];
    }
    
    public int getNodesCount() {
        return n;
    }
    
    public int getEdgesCount(){
        return m;
    }
    
    public void addEdge(int v, int w) {
        if (v < 0 || v >= n || w < 0 || w >= n) {
            return;
        } 
        
        if (hasEdge(v, w)) {
            return;
        }
        
        g[v][w] = true;
        
        if (!directed) {
            g[w][v] = true;
        }
        m++;
    }
    
    public boolean hasEdge(int v, int w) {
        if (v < 0 || v >= n || w < 0 || w >= n) {
            return false;
        } 
        
        return g[v][w];
    }
}

邻接表

public class SparseGraph {
    private int n, m;
    private boolean directed;
    private List<List<Integer>> g;
    
    public SparseGraph(int n, boolean) {
        if (n < 0) {
            return;
        }
        this.n = n;
        this.m = 0;
        this.directed = directed;
        g = (ArrayList<Integer>[])new ArrayList[n];
    }
    
    public int getNodesCount() {
        return n;
    }
    
    public int getEdgesCount() {
        return m;
    }
    
    public boolean hasEdge(int v, int w) {
        if (v < 0 || v >= n || w < 0 || w >= n) {
            return false;
        }
        int n = g[v].size();
        
        for (int i = 0; i < n; i++) {
            if(g[v].get(i) == w) {
                return true;
            }
        } 
        return false;
    } 
    
    public void addEdge(int v, int w) {
        if (v < 0 || v >= n || w < 0 || w >= n) {
            return;
        }
        //如果不允许平行边,需要加这样的判断
        if (hasEdge(v, w)) {
            return;
        }
        //如果允许平行边,注释掉上述判断
        g[v].add(w);
        if (v != w && !directed) {
            g[w].add(v);
        }
        m++;
    }
}

遍历边

返回一个节点的所有邻边

对于稀疏图来说

返回当前行即可

    public Iterable<Integer> adj(int v) {
        if (v < 0 || v >= n) {
            return new ArrayList<Integer>();
        }
        return g[v];
    }

对于稠密图来说

需要遍历一下,代码如下:

    public Iterable<Integer> adj(int v) {
        List<Integer> res = new ArrayList<>();
        if (v < 0 || v >= n) {
            return res;
        }
        
        for (int i = 0; i < n; i++) {
            if (g[v][i]) {
                res.add(i);
            }
        }
        return res;
    }

边的遍历对图的遍历至关重要!

阶段重构

抽象一个图的接口

public interface Graph {
    public int getNodesCount();
    public int getEdgesCount();
    public void addEdge(int v, int w);
    public boolean hasEdge(int v, int w);
    public Iterable<Integer> adj(int v);
}

将DenseGraph和SparseGraph修改为实现Graph接口

图的遍历

深度优先遍历

从一个节点开始,不断向下,直到无法进行为止,由于图可能有环,因此需要记录遍历过程中的点。

求一个图的联通分量

以求一个图的联通分量来展示图的深度优先遍历

    //求无权图的联通分量
    public class Component {
        private Graph g;
        private boolean[] visited;// 记录dfs的过程中节点是否被访问
        private int ccount;// 记录联通分量个数
        private int[] id;// 每个节点所对应的联通分量标记
        
        public Component(Graph g) {
            this.g = g;
            int n = g.getNodesCount();
            visited = new boolean[n];
            id = new int[n];
            ccount = 0;
            
            //初始化
            for (int i = 0; i < n; i++) {
                visited[i] = false;
                id[i] = -1;
            }
            
            //完成联通分量求解
            for (int i = 0; i < n; i++) {
                if (!visited[i]) {
                    dfs(i);
                    ccount++;
                }
            }
        }
        
        private void dfs(int i) {
            visited[i] = true;
            id[i] = ccount;
            
            for (int j : g.adj(v)) {
                if (!visited[j]) {
                    dfs(j);
                }
            }
        }
        
        //返回联通分量
        public int count() {
            return ccount;
        }
        
        //判断两点是否联通
        public boolean isConnected(int v, int w) {
            if (g == null || g.getNodesCount() == 0) {
                return false;
            }
            
            int n = g.getNodesCount();
            
            if (v < 0 || v >= n || w < 0 || w >= n) {
                return false;
            }
            return id[v] == id[w];
        }
    }

求两点的路径

public class Path {
    private Graph g;
    private boolean[] visited;
    private int s;
    private int[] from;
    
    public Path(Graph g, int s) {
        this.g = g;
        this.s = s;
        int n = g.getNodesCount();
        visited = new boolean[n];
        from = new int[n];
        
        for (int i = 0; i < n; i++) {
            visited[i] = false;
            from[i] = -1;
        }
        
        dfs(s);
    }
    
    private void dfs(int i) {
        visited[i] = true;
        for (int j : g.adj()) {
            if (!visited[i]) {
                from[j] = i;
                dfs(j);
            }
        }
    }
    
    public boolean hasPath(int w) {
        if (w < 0 || w >= g.getNodesCount()) {
            return false;
        }
        return visited[w];
    }
    
    public List<Integer> path(int w) {
        List<Integer> res = new ArrayList<>();
        if (w < 0 || w >= g.getNodesCount()) {
            return res;
        }
        
        Stack<Integer> s = new Stack<>();
        int p = w;
        
        while (p != -1) {
            s.push(p);
            p = from[p];
        }
        
        while (!s.isEmpty()) {
            res.add(s.pop());
        }
        
        return res;
    }
}

深度遍历复杂度
稀疏图O(V+E)
稠密图O(V^2)
深度优先遍历对有向图依旧有效

广度优先遍历

又称为层序遍历,以两点间的最短路径为例

public class ShortestPath {
    private Graph g;
    private int s;
    private boolean[] visited;
    private int[] from;
    private int[] order;
    
    public ShortestPath(Graph g, int s) {
        this.g = g;
        this.s = s;
        int n = g.getNodesCount();
        from = new int[n];
        visited = new boolean[n];
        order = new int[n];
        
        for (int i = 0; i < n; i++) {
            from[i] = -1;
            visited[i] = false;
            order[i] = -1;
        }
        
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(s);
        visited[s] = true;
        order[s] = 0;
        
        while (!queue.isEmpty()) {
            int v = queue.poll();
            for (int i : g.adj()) {
                if (!visited[i]) {
                    queue.offer(i);
                    visited[i] = true;
                    from[i] = v;
                    order[i] = order[v] + 1;
                }
            }
        }
    }
    
    public boolean hasPath(int w) {
        if (w < 0 || w >= g.getNodesCount()) {
            return false;
        }
        return visited[w];
    }
    
    public List<Integer> path(int w) {
        List<Integer> res = new ArrayList<>();
        if (w < 0 || w >= g.getNodesCount()) {
            return res;
        }
        
        Stack<Integer> s = new Stack<>();
        
        int p = w;
        while (p != -1) {
            s.push(p);
            p = from[p];
        }
        
        while (!s.isEmpty()) {
            res.add(s.pop());
        }
        
        return res;
    }
    
    public int length(int w) {
        if (w < 0 || w >= g.getNodesCount()) {
            return -1;
        }
        return order[w];
    }
}

菟潞寺沙弥
303 声望55 粉丝

引用和评论

0 条评论