目录
一、图(Graph)
1.概念
2.有向图
3.出度、入度
4.无向图
5.简单图、多重图
6.无向完全图
7.有向完全图
8.有权图
9.连通图
10.连通分量(无向图)
11.强连通图(有向图)
12.强连通分量
13.邻接矩阵
1.基本概念
2.邻接矩阵--有权图
14.邻接表
1.基本概念
2.有权图
15.图的实现
图的基础接口
添加边addEdge
图的总边(edgesSize)
图的点的接口
图的边的接口
删除边removeEdge
完整代码
二、图的遍历
新的图接口
三、广度优先搜索(BFS)
1.图解
编辑 2.思路
3.代码实现
四、深度优先搜索(DFS)
1.前序遍历
2.图解
3.递归实现
4.通过栈-----非递归实现
1)步骤
2)图解
3)代码实现
五、AOV网(Activity On Vertex Network)
六、拓扑排序(Topological Sort)
1.简介
2.思路
3.代码实现
一、图(Graph)
1.概念

2.有向图

3.出度、入度

4.无向图
 
5.简单图、多重图

6.无向完全图

7.有向完全图

8.有权图
 
9.连通图

10.连通分量(无向图)

11.强连通图(有向图)

12.强连通分量

13.邻接矩阵
1.基本概念

2.邻接矩阵--有权图
 
14.邻接表
1.基本概念

2.有权图

15.图的实现
图的基础接口
package graph;
public interface Graph<V,E> {
//    边的数量
    int edgesSize();
//    顶点数量
    int verticesSize();
//    添加顶点
    void addVertex(V v);
//    添加边(无权值)
    void addEdge(V from,V to);
//    添加边(有权值)
    void addEdge(V from,V to,E weight);
//    删除顶点
    void removeVertex(V v);
//    删除边
    void removeEdge(V from,V to);
    interface vertexVisitor<V>{
        boolean visit(V v);
    }
    
    void print();
}
添加边addEdge
    /**
     * 添加无权值的边
     * @param from
     * @param to
     */
    @Override
    public void addEdge(V from, V to) {
        addEdge(from,to,null);
    }
    /**
     * 添加有权值的边
     * @param from
     * @param to
     * @param weight
     */
    @Override
    public void addEdge(V from, V to, E weight) {
        //根据传入的参数from找到出发点,如果不存在则创建
        Vertex<V,E> fromVertex=vertices.get(from);
        if (fromVertex==null){
            fromVertex=new Vertex<>(from);
            //将点和对应的点关系存入
            vertices.put(from,fromVertex);
        }
        //根据传入参数to找到终点,如果找不到则创建
        Vertex<V,E> toVertex=vertices.get(to);
        if (toVertex==null){
            toVertex=new Vertex<>(to);
            //将点和对应的点关系存入
            vertices.put(to,toVertex);
        }
        //根据出发点和终点,创建边
        Edge<V,E> edge=new Edge<>(fromVertex,toVertex);
        edge.weight=weight;//有权值加上,无权值则为null
        //不管原来是否存在,都先删除,在添加进去
        if (fromVertex.outEdges.contains(edge)){ //说明存在
            toVertex.inEdges.remove(edge);
            //在整个图中的边减少
            edges.remove(edge);
        }
        fromVertex.outEdges.add(edge);
        toVertex.inEdges.add(edge);
        //在整个图中的边增加
        edges.add(edge);
    }图的总边(edgesSize)
    public int edgesSize() {
        return edges.size();
    }图的点的接口
    /**
     * 顶点
     * @param <V>
     * @param <E>
     */
    private static class Vertex<V,E>{
        //顶点值
        V value;
        Set<Edge<V,E>> inEdges=new HashSet<>();
        Set<Edge<V,E>> outEdges=new HashSet<>();
        public Vertex(V value) {
            this.value = value;
        }
        /**
         * 比较两个顶点是否相等
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            return Objects.equals(value,((Vertex<V,E>)obj).value);
        }
        @Override
        public int hashCode() {
            return value==null ? 0: value.hashCode();
        }
    }图的边的接口
    /**
     * 边
     * @param <V>
     * @param <E>
     */
    private static class Edge<V,E> {
        Vertex<V,E> from;
        Vertex<V,E> to;
        E weight;
        public Edge(Vertex<V, E> from, Vertex<V, E> to) {
            this.from = from;
            this.to = to;
        }
        /**
         * 判断两条边是否相同
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            Edge<V,E> edge=(Edge<V, E>) obj;
            return Objects.equals(from,edge.from) && Objects.equals(to,edge.to);
        }
        /**
         * 如果equals的结果是true说明一样,则hashcode值也一样
         * @return
         */
        @Override
        public int hashCode() {
            return from.hashCode()+to.hashCode();
        }
    }删除边removeEdge
    /**
     * 删除边
     * @param from
     * @param to
     */
    @Override
    public void removeEdge(V from, V to) {
//        根据传入的from获得起点,不存在则不需要删除
        Vertex<V,E> fromVertex=vertices.get(from);
        if (fromVertex ==null){
            return;
        }
//        根据传入的to找到终点,不存在则不需要删除
        Vertex<V,E> toVertex=vertices.get(to);
        if (toVertex==null) return;
//        根据起点和终点获得边,然后删除
        Edge<V,E> edge=new Edge<>(fromVertex,toVertex);
        if (fromVertex.outEdges.remove(edge)){ //表示删除成功
            toVertex.inEdges.remove(edge);
            edges.remove(edge);
        }
    }完整代码
package graph;
import java.util.*;
public class ListGraph<V,E> implements Graph<V,E>{
    // 传入的V与顶点类Vertex的映射【要记录该点的出入是inEdges还是outEdges】
    private Map<V, Vertex<V, E>> vertices = new HashMap<>();
    // 边的Set集合
    private Set<Edge<V, E>> edges = new HashSet<>();
    public void print(){
        System.out.println("[顶点]-------------------");
        vertices.forEach((V v, Vertex<V, E> vertex) -> {
            System.out.println(v);
            System.out.println("out-----------");
            System.out.println(vertex.outEdges);
            System.out.println("int-----------");
            System.out.println(vertex.inEdges);
        });
        System.out.println("[边]-------------------");
        edges.forEach((Edge<V, E> edge) -> {
            System.out.println(edge);
        });
    }
    @Override
    public int edgesSize() {
        return edges.size();
    }
    @Override
    public int verticesSize() {
        return vertices.size();
    }
    @Override
    public void addVertex(V v) {
//        put(key,value)
        vertices.put(v,new Vertex<>(v));
    }
    /**
     * 添加无权值的边
     * @param from
     * @param to
     */
    @Override
    public void addEdge(V from, V to) {
        addEdge(from,to,null);
    }
    /**
     * 添加有权值的边
     * @param from
     * @param to
     * @param weight
     */
    @Override
    public void addEdge(V from, V to, E weight) {
        //根据传入的参数from找到出发点,如果不存在则创建
        Vertex<V,E> fromVertex=vertices.get(from);
        if (fromVertex==null){
            fromVertex=new Vertex<>(from);
            //将点和对应的点关系存入
            vertices.put(from,fromVertex);
        }
        //根据传入参数to找到终点,如果找不到则创建
        Vertex<V,E> toVertex=vertices.get(to);
        if (toVertex==null){
            toVertex=new Vertex<>(to);
            //将点和对应的点关系存入
            vertices.put(to,toVertex);
        }
        //根据出发点和终点,创建边
        Edge<V,E> edge=new Edge<>(fromVertex,toVertex);
        edge.weight=weight;//有权值加上,无权值则为null
        //不管原来是否存在,都先删除,在添加进去
        if (fromVertex.outEdges.contains(edge)){ //说明存在
            toVertex.inEdges.remove(edge);
            //在整个图中的边减少
            edges.remove(edge);
        }
        fromVertex.outEdges.add(edge);
        toVertex.inEdges.add(edge);
        //在整个图中的边增加
        edges.add(edge);
    }
    /**
     * 删除点
     * @param v
     */
    @Override
    public void removeVertex(V v) {
        //根据传入的值找到点并且删除,不存在则不做操作
        Vertex<V,E> vertex=vertices.remove(v);
        if (vertex==null) return;
//        迭代器遍历集合vertex.outEdges,删除所有从该点出去的边
//        iterator.hasNext():相当于i++
        for (Iterator<Edge<V,E>> iterator = vertex.inEdges.iterator();iterator.hasNext();){
            Edge<V,E> edge=iterator.next();//遍历从该点出去的边
            edge.to.inEdges.remove(edge);//获取终点进入的边,并从中删除遍历到的边
            //这个remove方法是将当前的这个边删除
            iterator.remove();//将当前遍历到当前遍历到的元素edge从集合vertex.outEdges中删除
            edges.remove(edge);
        }
        // 迭代器遍历集合vertex.inEdges, 删除所有进入该点的边
        for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {
            Edge<V, E> edge = iterator.next(); // 遍历到的进入该点的边
            edge.from.outEdges.remove(edge); // 获取起点出去的边,并从中删除遍历到的边
            iterator.remove(); // 将当前遍历到的元素edge从集合vertex.inEdges中删掉
            edges.remove(edge);
        }
    }
    /**
     * 删除边
     * @param from
     * @param to
     */
    @Override
    public void removeEdge(V from, V to) {
//        根据传入的from获得起点,不存在则不需要删除
        Vertex<V,E> fromVertex=vertices.get(from);
        if (fromVertex ==null){
            return;
        }
//        根据传入的to找到终点,不存在则不需要删除
        Vertex<V,E> toVertex=vertices.get(to);
        if (toVertex==null) return;
//        根据起点和终点获得边,然后删除
        Edge<V,E> edge=new Edge<>(fromVertex,toVertex);
        if (fromVertex.outEdges.remove(edge)){ //表示删除成功
            toVertex.inEdges.remove(edge);
            edges.remove(edge);
        }
    }
    /**
     * 顶点
     * @param <V>
     * @param <E>
     */
    private static class Vertex<V,E>{
        //顶点值
        V value;
        Set<Edge<V,E>> inEdges=new HashSet<>();
        Set<Edge<V,E>> outEdges=new HashSet<>();
        public Vertex(V value) {
            this.value = value;
        }
        /**
         * 比较两个顶点是否相等
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            return Objects.equals(value,((Vertex<V,E>)obj).value);
        }
        @Override
        public int hashCode() {
            return value==null ? 0: value.hashCode();
        }
    }
    /**
     * 边
     * @param <V>
     * @param <E>
     */
    private static class Edge<V,E> {
        Vertex<V,E> from;
        Vertex<V,E> to;
        E weight;
        public Edge(Vertex<V, E> from, Vertex<V, E> to) {
            this.from = from;
            this.to = to;
        }
        /**
         * 判断两条边是否相同
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            Edge<V,E> edge=(Edge<V, E>) obj;
            return Objects.equals(from,edge.from) && Objects.equals(to,edge.to);
        }
        /**
         * 如果equals的结果是true说明一样,则hashcode值也一样
         * @return
         */
        @Override
        public int hashCode() {
            return from.hashCode()+to.hashCode();
        }
    }
}
二、图的遍历
图的遍历
从图中某一顶点出发访问图中其余顶点,且每一个顶点仅被访问一次
 图有2种常见的遍历方式(有向图、无向图都适用)
- 广度优先搜索(Breadth First Search,BFS),又称为宽度优先搜索、横向优先搜索。
- 深度优先搜索(Depth First Search,DFS)
发明“深度优先搜索”算法的2位科学家在1986年共同获得计算机领域的最高奖:图灵奖。
新的图接口
package graph;
import java.util.List;
import java.util.Set;
public interface Graph<V,E> {
//    边的数量
    int edgesSize();
//    顶点数量
    int verticesSize();
//    添加顶点
    void addVertex(V v);
//    添加边(无权值)
    void addEdge(V from,V to);
//    添加边(有权值)
    void addEdge(V from,V to,E weight);
//    删除顶点
    void removeVertex(V v);
//    删除边
    void removeEdge(V from,V to);
    void print();
    void bfs(V begin, vertexVisitor<V> visitor); // 广度优先搜索
    /**
     * 非递归版的深度优先搜索
     * @param begin
     * @param visitor
     */
    void dfs(V begin, vertexVisitor<V> visitor);
    List<V> topologicalSort(); // 拓扑排序
    interface vertexVisitor<V>{
        boolean visit(V v);
    }
}
三、广度优先搜索(BFS)
1.图解
之前所学的二叉树层序遍历就是一种广度优先搜索。
注:BFS结果不唯一。
经历一层可以访问到的

 2.思路
 2.思路
 
将该点从队列取出来,然后再将其所子节点依次放入队列

从某个点开始,将它可以到达的点放入队列,如果已经访问过则跳过,然后从队列中取出点重复该过程。
第一层:假设从点A开始,它可以到达B、F,则将B、F入队。
 此时队列中元素 [B、F]
 第二层:队头B出队,B可以到达C、I、G,将C、I、G入队。
 此时队列中元素 [F、C、I、G]
 第三层:队头F出队,F可以到达G、E,但G已访问过,将E入队。
 此时队列中元素 [C、I、G、E]
 第四层:队头C出队,C可以到达I、D,但I已访问过,将D入队。
 此时队列中元素 [I、G、E、D]
 第五层:队头I出队,I可以到达D,但D已访问过,不执行操作。
 此时队列中元素 [G、E、D]
 第六层:队头G出队,G可以到达D、H,但D已访问过,将H入队。
 此时队列中元素 [E、D、H]
 第七层:队头E出队,E可以到达D、H、F,都访问过,不执行操作。
 此时队列中元素 [D、H]
 第八层:队头D出队,D可以到达C、H、E,都访问过,不执行操作。
 此时队列中元素 [H]
 第九层:队头H出队,H可以到达D、G、E,都访问过,不执行操作。
 此时队列中元素 []
 队列为空,广度优先搜索结束。
3.代码实现
    /**
     * 广度优先搜索:BFS
     * @param begin
     * @param visitor
     */
    @Override
    public void bfs(V begin, vertexVisitor<V> visitor) {
        if (visitor ==null) return;
        //根据传入的值begin找到顶点
        Vertex<V,E> beginVertex=vertices.get(begin);
        //该顶点不存在,不做操作
        if (beginVertex==null) return;
        //存放已经访问过的节点
        //因为如果不存放,则遍历到下一个顶点的时候,会将刚刚出队列的数值再一次放入
        Set<Vertex<V,E>> visitedVertices=new HashSet<>();
        //临时存放
        Queue<Vertex<V,E>> queue=new LinkedList<>();
        queue.offer(beginVertex); //元素入队
        //将已经加入过队列的元素标记一下
        visitedVertices.add(beginVertex);
        //思考参考二叉树层次遍历,队列存放每一层的顶点,用集合记录已经访问过的点
        while (!queue.isEmpty()){
            //从队列中取出第一个顶点
            Vertex<V,E> vertex=queue.poll();
            if (visitor.visit(vertex.value)) return;
            //遍历【从队列中取出的顶点】的出去的边,将【这些边的终点】入队,并且标记为已经访问过
            for (Edge<V,E> edge:vertex.outEdges){
                //如果集合中已经记录该顶点,说明已经访问过,跳过进行下一轮
                if (visitedVertices.contains(edge.to)) continue;
                queue.offer(edge.to);
                visitedVertices.add(edge.to);
            }
        }
    }四、深度优先搜索(DFS)
之前所学的二叉树前序遍历就是一种深度优先搜索。
注:DFS结果不唯一。
1.前序遍历

2.图解

 
 
3.递归实现
    /**
     * 递归实现--->深度优先遍历:DFS
     * @param begin
     * @param visitedVertices 已经访问过的节点
     */
    /**
     * 递归实现深度优先搜索DFS
     */
    public void dfs(V begin) {
        Vertex<V, E> beginVertex = vertices.get(begin); // 根据传入的值获取顶点
        if (beginVertex == null) return; // 顶点不存在则不执行操作
        dfs(beginVertex, new HashSet<>()); // 传入的集合,用来记录访问过的顶点
    }
    private void dfs(Vertex<V, E> vertex, Set<Vertex<V, E>> vistedVertices){
        System.out.println(vertex.value);
        vistedVertices.add(vertex);
        for(Edge<V, E> edge : vertex.outEdges){
            if(vistedVertices.contains(edge.to)) continue;
            //dfs会自己后退
            dfs(edge.to, vistedVertices);
        }
    }
4.通过栈-----非递归实现
1)步骤
1.从outEdge中选择一条边
2.将选择边的from和to边,按顺序入栈【先入from再入to】
3.打印选择边的to
4.将to边添加到已经访问的范围中
5.break
2)图解

3)代码实现
    /**
     * 非递归版(通过栈实现):深度优先搜索(DFS)
     * @param
     */
    @Override
    public void dfs(V begin, vertexVisitor<V> visitor) {
        if (visitor ==null) return;
        //查看该点是否存在
        Vertex<V,E> beginVertex=vertices.get(begin);
        if (begin==null) return;
        //创建一个set存放已经访问过的节点
        Set<Vertex<V,E>> visitedVertices=new HashSet<>();
        //创建一个栈临时存放
        Stack<Vertex<V,E>> stack=new Stack<>();
        //先访问起点
        stack.push(beginVertex);
        visitedVertices.add(beginVertex);
        //如果该节点已经访问过,则不再放入
        if (visitor.visit(begin)) return;
        while (!stack.isEmpty()){ //如果栈不为空
            Vertex<V,E> vertex=stack.pop();
            for (Edge<V,E> edge :vertex.outEdges){ //访问出发点的每一条边
                if (visitedVertices.contains(edge.to)) continue; //表示这条边已经访问过
                //找了一条适合且未遍历过的边
                stack.push(edge.from);//将出发点加入栈中
                stack.push(edge.to);//将出度点加入栈中
                //将加入栈中的节点加入已经访问过发范围中
                visitedVertices.add(edge.to);
                if (visitor.visit(edge.to.value)) return;
                break;
            }
        }
    }五、AOV网(Activity On Vertex Network)
一项大的工程常被分为多个小的子工
子工程之间可能存在一定的先后顺序,即某些子工程必须在其他的一些子工程完成后才能开始。
 在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,子工程被称为活动(Activity)
以顶点表示活动、有向边表示活动之间的先后关系,这样的图简称为 AOV 网。【有很强的依赖关系】
标准的AOV网必须是一个有向无环图(Directed Acyclic Graph,简称 DAG)

六、拓扑排序(Topological Sort)
1.简介
前驱活动:有向边起点的活动称为终点的前驱活动
- 只有当一个活动的前驱全部都完成后,这个活动才能进行
后继活动:有向边终点的活动称为起点的后继活动

2.思路
可以使用卡恩算法(Kahn于1962年提出)完成拓扑排序。
假设 L 是存放拓扑排序结果的列表:
- ① 把所有入度为 0 的顶点放入 queue 中,然后把这些顶点从图中去掉,放入list中
- ② 然后找到已经从queue中移除的入度为0的节点的下一个节点,将下一个节点的入度-1,如果-1后,入度为0,则直接加入queue
- ③ 重复操作 ①,直到找不到入度为 0 的顶点
- 如果此时 L 中的元素个数和顶点总数相同,说明拓扑排序完成
- 如果此时 L 中的元素个数少于顶点总数,说明原图中存在环,无法进行拓扑排序

3.代码实现
    /**
     * 拓扑排序
     * @return
     */
    @Override
    public List<V> topologicalSort() {
        List<V> list = new ArrayList<>();
        Queue<Vertex<V, E>> queue = new LinkedList<>();
        Map<Vertex<V, E>, Integer> ins = new HashMap<>();
        // 初始化(将度为0的节点放入队列)
        vertices.forEach((V v, Vertex<V, E> vertex) -> {
            int indegree = vertex.inEdges.size(); // 入度
            if(indegree == 0) { // 入度为0,放入队列
                queue.offer(vertex);
            } else { // 入度不为0,用map记录它的入度
                ins.put(vertex, indegree);
            }
        });
        while(!queue.isEmpty()){ // 从队列中取节点
            //取出队列中的第一个元素
            Vertex<V, E> vertex = queue.poll();
            list.add(vertex.value); // 放入返回结果中
            //遍历取出的节点的出度边
            for (Edge<V, E> edge : vertex.outEdges){
                // 队列中取出节点所通向节点的入度
                int toIndegree = ins.get(edge.to) - 1;
                if(toIndegree == 0) { // 入度为0,放入队列
                    queue.offer(edge.to);
                } else { // 入度不为0,用map记录它的入度
                    ins.put(edge.to, toIndegree);
                }
            }
        }
        return list;
    }


















