数据结构-图

图:
    表示“多对多”的关系 
    包含:
        一组顶点:通常用V (Vertex) 表示顶点集合 
        一组边:通常用E (Edge) 表示边的集合
            边是定点对(v,w)
            有向边<v,w> 表示从v指向w的边
            不考虑重边和自回路
            
抽象数据类型:
    类型名称:图(Graph)
    数据对象集:G(V,E)由一个非空的有限顶点集合V和一个有限边集合E组成。     
        Graph Create():建立并返回空图; 
        Graph InsertVertex(GraphG, Vertex v):将v插入G; 
        Graph InsertEdge(GraphG, Edge e):将e插入G; 
        void DFS(GraphG, Vertex v):从顶点v出发深度优先遍历图G; 
        void BFS(GraphG, Vertex v):从顶点v出发宽度优先遍历图G; 
        void ShortestPath(GraphG, Vertex v, intDist[]):计算图G中顶点v到任意其他顶点的最短距离; 
        void MST(GraphG):计算图G的最小生成树; 
        
怎么在程序中表示一个图:
    1.邻接矩阵:G[N][N]——N个顶点从0到N-1编号:
        说明:矩阵中每个点取值1或0:1表示有边,0表示没有边相连
        
        特点:
            [1]直观、简单、好理解 
            [2]方便检查任意一对顶点间是否存在边
            [3]方便找任一顶点的所有“邻接点”(有边直接相连的顶点) 
            [4]方便计算任一顶点的“度”(从该点发出的边数为“出 度”,指向该点的边数为“入度”)
                无向图:对应行(或列)非0元素的个数
                有向图:对应行非0元素的个数是“出度”;对应列非0元素的 个数是“入度”                
            [5]浪费空间——存稀疏图(点很多而边很少)有大量无效元素
                对稠密图(特别是完全图)还是很合算的
            [6]浪费时间——统计稀疏图中一共有多少条边

    2.邻接表:G[N]为指针数组,对应矩阵每行一个链表, 只存非0元素
        说明:一个数组,数组每个元素为一个链表。数组大小为途中点的个数,每个链表表示一个点与其他点的连接
        
        特点:    
            [1]方便找任一顶点的所有“邻接点”
            [2]节约稀疏图的空间
                需要N个头指针+ 2E个结点(每个结点至少2个域)
            [3]方便计算任一顶点的“度”?
                对无向图:是的
            [4]对有向图:只能计算“出度”;需要构造“逆邻接表”(存指向自己 的边)来方便计算“入度”
            [5]方便检查任意一对顶点间是否存在边?NO
    
图的遍历:
    1.深度优先搜索(DFS)    //类似于树的先序遍历
        void DFS (Vertex v){
            visit[v] = true;
            for (v的每个邻接点w){
                if (!visit[w])
                    DFS(W);
            }
        }
        
        若有N个顶点、E条边,时间复杂度是:
            用邻接表存储图,O(N+E)
            用邻接矩阵存储图,O(N^2)
        
    
    2.广度优先搜索(BFS) //类似于层序遍历
        void BFS ( Vertex V ) { 
            visited[V] = true; 
            Enqueue(V, Q);         //队列
            while(!IsEmpty(Q)){ 
                V = Dequeue(Q); 
                for( V 的每个邻接点W ) 
                    if( !visited[W] ) { 
                        visited[W] = true; 
                        Enqueue(W, Q); 
                    }
                } 
            }
        时间复杂度同1
        
图的联通性问题:
    1.联通:如果从V到W存在一条(无向)路径,则称 V和W是连通的
    2.路径::V到W的路径是一系列顶点{V, v1, v2, …, vn, W}的集合,其中任一对相邻的顶点间都有图 中的边。路径的长度是路径中的边数
    (如果带 权,则是所有边的权重和)。如果V到W之间的所 有顶点都不同,则称简单路径 
    3.回路:起点等于终点的路径 
    4.连通图:图中任意两顶点均连通
    
    对于不连通的图:
        1.联通分量:无向图的极大联通子图,性质:
            极大顶点数:再加1个顶点就不连通了 
            极大边数:包含子图中所有顶点相连的所有边

        2.强连通:有向图中顶点V和W之间存在双向路 径,则称V和W是强连通的 
        3.强连通图:有向图中任意两顶点均强连通
        4.强连通分量:有向图的极大强连通子图
    
    不连通图的遍历:
        void ListComponents( Graph G ) { 
            for( each V in G ) 
                if( !visited[V] ) { 
                    DFS( V ); /*or BFS( V )*/         //每调用一次DFS(V),就 把V所在的连通分量遍历 了一遍。BFS也是一样。
                } 
            }
    
最短路径问题:
    在网络中,求两个不同顶点之间的所有路径 中,边的权值之和最小的那一条路径 
    
单源最短路径问题:从某固定源点出发,求其 到所有其他顶点的最短路径,可分为有权图的情况和无权图情况
    1.无权图单源最短路径算法:
        实际上就是通过广度优先搜索(BFS)遍历实现
        
        dist[W] 保存每个点到源点s的最小距离
        path[w]保存w的上一个点,用于保存最短路径
        
        void Unweighted( Vertex S ) { 
            Enqueue(S, Q); 
            while(!IsEmpty(Q)){ 
                V = Dequeue(Q); 
                for( V 的每个邻接点W ) 
                    if( dist[W]==-1  ) { 
                        dist[W] = dist[V]+1; 
                        path[W] = V; 
                        Enqueue(W, Q); 
                    } 
            } 
        }
        
        简单代码实现:
            public static void main(String[] args) throws InterruptedException {
                System.out.println("begin");
                
                 Node nodea = new Node("a"); Node nodeb = new Node("b");
                 Node nodec = new Node("c"); Node noded = new Node("d");
                 Node nodee = new Node("e"); Node nodef = new Node("f");
                 Node nodeg = new Node("g");
                 
                Map<Node, List<Node>> map = new HashMap<>();
                List<Node> nodes1 = new ArrayList<>();
                nodes1.add(nodeb);
                nodes1.add(nodee);
                map.put(nodea, nodes1);

                List<Node> nodes2 = new ArrayList<>();
                nodes2.add(nodea);
                nodes2.add(nodef);
                nodes2.add(nodec);
                map.put(nodeb, nodes2);
                List<Node> nodes3 = new ArrayList<>();
                nodes3.add(nodeb);
                nodes3.add(nodef);
                nodes3.add(nodeg);
                nodes3.add(noded);
                map.put(nodec, nodes3);

                List<Node> nodes4 = new ArrayList<>();
                nodes4.add(nodec);
                nodes4.add(nodeg);
                map.put(noded, nodes4);
                List<Node> nodes5 = new ArrayList<>();
                nodes5.add(nodea);
                nodes5.add(nodef);
                nodes5.add(nodeg);
                map.put(nodee, nodes5);
                List<Node> nodes6 = new ArrayList<>();
                nodes6.add(nodeb);
                nodes6.add(nodec);
                nodes6.add(nodee);
                map.put(nodef, nodes6);
                List<Node> nodes7 = new ArrayList<>();
                nodes7.add(nodee);
                nodes7.add(nodec);
                nodes7.add(noded);
                map.put(nodeg, nodes7);
                
                Map<Node, Integer> dist = new HashMap<>();
                Map<Node, Node> path = new HashMap<>();
                
                dist.put(nodea, 0);
                dist.put(nodeb, -1);
                dist.put(nodec, -1);
                dist.put(noded, -1);
                dist.put(nodee, -1);
                dist.put(nodef, -1);
                dist.put(nodeg, -1);
                
                Queue<Node> queue = new LinkedList<>();
                
                queue.add(nodea);
                nodea.isVisit = true;
                while (!queue.isEmpty()) {
                    Node poll = queue.poll();
                    List<Node> list = map.get(poll);
                    if(list != null && list.size()>0){
                        for (Node node : list) {
                            if(!node.isVisit){
                                dist.put(node, dist.get(poll) +1);
                                path.put(node, poll);
                                node.isVisit = true;
                                queue.add(node);
                            }
                        }
                    }
                }
                System.out.println(dist);
                //显示a到d的最短路径
                Stack<Node> nodeSta= new Stack<>();
                nodeSta.add(noded);
                Node tempNode = noded;
                while(!tempNode.equals(nodea)){
                    Node node = path.get(tempNode);
                    nodeSta.add(node);
                    tempNode = node;
                }
                System.out.println("a到d最短路径:");
                while (!nodeSta.isEmpty()) {
                    System.out.println(nodeSta.pop());
                }
                
                System.out.println("end");
            }

            public static class Node {
                String name;
                Boolean isVisit = false;

                public Node(String name) {
                    super();
                    this.name = name;
                }

                @Override
                public int hashCode() {
                    final int prime = 31;
                    int result = 1;
                    result = prime * result + ((name == null) ? 0 : name.hashCode());
                    return result;
                }

                @Override
                public boolean equals(Object obj) {
                    if (this == obj)
                        return true;
                    if (obj == null)
                        return false;
                    if (getClass() != obj.getClass())
                        return false;
                    Node other = (Node) obj;
                    if (name == null) {
                        if (other.name != null)
                            return false;
                    } else if (!name.equals(other.name))
                        return false;
                    return true;
                }

                @Override
                public String toString() {
                    return "Node [name=" + name + "]";
                }
            }
        
    2.有权图单源最短路径算法:
        Dijkstra算法:
            a.令S={源点s + 已经确定了最短路径的顶点vi} 
            b.对任一未收录的顶点v,定义dist[v]为s到v的最短路径长度,但该路径仅经过S中的顶点。即路径 {s(viS)v}的最小长度
            c.若路径是按照递增(非递减)的顺序生成的,则
                真正的最短路必须只经过S中的顶点(为什么?) 
                每次从未收录的顶点中选一个dist最小的收录(贪心) 
                增加一个v进入S,可能影响另外一个w的dist值!
                    dist[w] = min{dist[w], dist[v] + <v,w>的权重}
                
        dist[W] 保存每个点到源点s的最小距离        //dist[W]的初始化:dist[s]设置为零,与s直接相连的节点设置为对应边的权值,其他初始化为无穷大
        path[w]保存w的上一个点,用于保存最短路径

        思路:每次从dist[W]中得到距离值最小的节点,将这个节点设置为已收录,遍历这个节点的邻接且未被收录节点,更新dist值
            ,循环这个过程,直到所有点被收录
        
        实现:
            void Dijkstra( Vertex s ) {
                while(1) { 
                    V = 未收录顶点中dist最小者; 
                    if( 这样的V不存在) 
                        break; 
                    collected[V] = true; 
                    for ( V 的每个邻接点W ) 
                        if( collected[W] == false ) 
                            if( dist[V]+E<V,W>< dist[W] ) { 
                                dist[W] = dist[V] + E<V,W>; 
                                path[W] = V; 
                            } 
                } 
            }/* 不能解决有负边的情况*/
            
        public static void main(String[] args) throws InterruptedException {
            System.out.println("begin");
            
             Node nodea = new Node("a"); Node nodeb = new Node("b");
             Node nodec = new Node("c"); Node noded = new Node("d");
             Node nodee = new Node("e"); Node nodef = new Node("f");
             Node nodeg = new Node("g");
             //类似邻接表,保存连接关系
            Map<Node, List<Node>> map = new HashMap<>();
            List<Node> nodes1 = new ArrayList<>();
            nodes1.add(nodeb);
            nodes1.add(nodee);
            map.put(nodea, nodes1);

            List<Node> nodes2 = new ArrayList<>();
            nodes2.add(nodea);
            nodes2.add(nodef);
            nodes2.add(nodec);
            map.put(nodeb, nodes2);
            List<Node> nodes3 = new ArrayList<>();
            nodes3.add(nodeb);
            nodes3.add(nodef);
            nodes3.add(nodeg);
            nodes3.add(noded);
            map.put(nodec, nodes3);

            List<Node> nodes4 = new ArrayList<>();
            nodes4.add(nodec);
            nodes4.add(nodeg);
            map.put(noded, nodes4);
            List<Node> nodes5 = new ArrayList<>();
            nodes5.add(nodea);
            nodes5.add(nodef);
            nodes5.add(nodeg);
            map.put(nodee, nodes5);
            List<Node> nodes6 = new ArrayList<>();
            nodes6.add(nodeb);
            nodes6.add(nodec);
            nodes6.add(nodee);
            map.put(nodef, nodes6);
            List<Node> nodes7 = new ArrayList<>();
            nodes7.add(nodee);
            nodes7.add(nodec);
            nodes7.add(noded);
            map.put(nodeg, nodes7);
            //记录权值,即每条边的长度
            Map<String, Integer> distMap = new HashMap<>();
            distMap.put("a-b", 3);
            distMap.put("a-e", 2);
            distMap.put("b-f", 2);
            distMap.put("b-c", 3);
            distMap.put("c-f", 4);
            distMap.put("c-d", 2);
            distMap.put("e-f", 1);
            distMap.put("e-g", 1);
            distMap.put("c-g", 2);
            distMap.put("d-g", 6);
            //记录最短路径
            
            Map<Node, Node> path = new HashMap<>();
            //保存收录的点
            Set<Node> s = new HashSet<>();
            
            //最小堆保存节点,//记录最短路径
            PriorityQueue<Node> queue = new PriorityQueue<>(1000, new Comparator<Node>() {
                public int compare(Node o1, Node o2) {
                    return o1.distance - o2.distance;
                };
            });
            Node atoa = new Node("a", 0);
            Node btoa = new Node("b", Integer.MAX_VALUE);
            Node ctoa = new Node("c", Integer.MAX_VALUE);
            Node dtoa = new Node("d", Integer.MAX_VALUE);
            Node etoa = new Node("e", Integer.MAX_VALUE);
            Node ftoa = new Node("f", Integer.MAX_VALUE);
            Node gtoa = new Node("g", Integer.MAX_VALUE);
            
            queue.add(atoa);
            queue.add(btoa);
            queue.add(ctoa);
            queue.add(dtoa);
            queue.add(etoa);
            queue.add(ftoa);
            queue.add(gtoa);
            
            //核心算法
            while(!queue.isEmpty()) {
                Node poll = queue.poll();    //弹出dis最小的点
                s.add(poll);    //收录这个点
                List<Node> list = map.get(poll);    //获取邻接点
                for (Node node : list) {
                    if(!s.contains(node)){        //如果临界点没有被收录,计算并更新新距离
                        Integer dis = distMap.get(poll.name + "-" + node.name) != null ? distMap.get(poll.name + "-" + node.name) :distMap.get(node.name + "-" + poll.name);
                        int tempDis = poll.distance + dis;
                        if(dis != null && tempDis < getDis(node, queue).distance) {
                            node.distance = tempDis;
                            queue.remove(node);    
                            queue.offer(node);        //这两个实现最小堆的节点重排保证dis最小的点在根节点
                            path.put(node, poll);    //path用于记录最小路径
                        }
                        
                    }
                }
            }
            
            System.out.println(s);
            //显示a到d的最短路径
            Stack<Node> nodeSta= new Stack<>();
            nodeSta.add(nodec);
            Node tempNode = nodec;
            while(!tempNode.equals(nodea)){
                Node node = path.get(tempNode);
                nodeSta.add(node);
                tempNode = node;
            }
            System.out.println("a到c最短路径:");
            while (!nodeSta.isEmpty()) {
                System.out.print(nodeSta.pop().name + "->");
                if(nodeSta.size() == 1){
                    System.out.println(nodeSta.pop().name);
                }
            }
            
            System.out.println("end");
        }


                
多源最短路径问题:求任意两顶点间的最短路径
    方法1:直接将单源最短路算法调用|V|遍     
        T= O( |V|^3 + |E||V|)
    方法2:    Floyd算法
        T= O( |V|^3 )
        
    Floyd算法
        使用邻接矩阵保存图,矩阵中D[i,j]保存的是i到j的距离,如果i,j间没有边则初始化为无穷大
        依次将每个点K取出,比较之前记录的其他任意两个点如A B间的距离AB如果这个距离大于AK + KB则更新AB的值为AK + KB
        void Floyd() {  
            for ( i = 0; i < N; i++ ) 
                for( j = 0; j < N; j++ ) { 
                    D[i][j] = G[i][j];     //初始化
                    path[i][j] = -1; 
                } 
            for( k = 0; k < N; k++ ) 
                for( i = 0; i < N; i++ ) 
                    for( j = 0; j < N; j++ ) 
                        if( D[i][k] + D[k][j] < D[i][j] ) { 
                            D[i][j] = D[i][k] + D[k][j];     //更新最短路径值
                            path[i][j] = k;     //用于后续找出任意两点的最短路径
                        } 
        }
        
最小生成树问题:    //有等价关系:最小生成树存在↔图连通
    是一颗树
        无回路
        |v|个顶点一定有|v|-1条边
        
    是生成树:
        包含全部顶点
        V-1条边全部都在图里
    边的权重和最小
    
    得到最小生成树的算法:
        贪心算法:
            什么是“贪”:每一步都要最好的 
            什么是“好”:权重最小的边
            需要约束:
                只能用图里有的边 
                只能正好用掉|V|-1条边
                不能有回路
        
        Prim算法:让一棵小树长大   //属于贪心算法
            void Prim() { 
                MST = {s}; 
                while(1) { 
                    V = 未收录顶点中dist最小者; 
                    if( 这样的V不存在) break; 
                    将V收录进MST: dist[V] = 0; 
                    for ( V 的每个邻接点W ) 
                        if(  W未被收录) 
                            if( E(V,W)< dist[W] ){ 
                                dist[W] = E(V,W); 
                                parent[W] = V; 
                            } 
                } 
                
                if( MST中收的顶点不到|V|个) 
                    Error ( “生成树不存在”); 
            }
        
        首先选择图中一个点为根节点,找到与根节点相连的最短的边,将这条边和这条边上另一个端点收录到树中,接着找到与树中顶点相连的所有边中
        最短的边及顶点,在不形成回路的情况下,收录为树的一部分。如此循环知道所有的点都被收录进树中。
        
        Kruskal算法—将森林合并成树
            void Kruskal( Graph G ) {  
                MST = { } ;
                while( MST 中不到|V|-1 条边&& E 中还有边) { 
                    从E 中取一条权重最小的边E(v,w) ; 
                    将E(v,w)从E 中删除; 
                    if( E(V,W)不在MST 中构成回路)     
                        将E(V,W)加入MST; else 彻底无视E(V,W);         
                } 
                if( MST 中不到|V|-1 条边) 
                    Error ( “生成树不存在”); 
                        
            }

        从图中找到权重最小的边,判断这条边加进mst中时是否构成回路,不构成回路则将其加入到mst中,否则不加入并从图中去掉这条边
        接着按同样方式在图中继续找下一条边,知道边数达到v-1,则找到了最小生成树。如果找不到v-1条边,则生成树不存在
        
            如何获取最小的边:使用最小堆
            如何判断加入边后是否构成回路:并查集
        
        
拓扑排序:
    AOV网络:是一个有向图,所有真实的活动表现为图中的顶点,顶点和顶点之间的有向边标识两个事情的先后顺序,这样的图称为AOV网络


    拓扑序:如果图中从V到W有一条有向路径, 则V一定排在W之前。满足此条件的顶点序列 称为一个拓扑序
        
    获得一个拓扑序的过程就是拓扑排序 

    AOV如果有合理的拓扑序,则必定是有向无环 图(Directed Acyclic Graph, DAG)
    
        
    获得拓扑序的算法:
        1.时间复杂度:O(|V|^2)
        void TopSort() {  
            for( cnt= 0; cnt< |V|; cnt++ ) { 
                V = 未输出的入度为0的顶点; 
                if( 这样的V不存在) { 
                    Error ( “图中有回路”); 
                    break; 
                } 
                输出V,或者记录V的输出序号; 
                for( V 的每个邻接点W) 
                    Indegree[W]––; 
            }
        }
        从图中每次获取入度为0的顶点,输出,遍历这个顶点的临界点,将邻接点的入度减一
        
        2.T= O( |V| + |E| )
        voidTopSort() { 
            for( 图中每个顶点V ) 
                if( Indegree[V]==0 ) 
                    Enqueue( V, Q ); 
                    
            while( !IsEmpty(Q) ) { 
                V = Dequeue( Q ); 
                输出V,或者记录V的输出序号; 
                cnt++; 
                for( V 的每个邻接点W ) 
                    if( ––Indegree[W]==0 ) 
                        Enqueue( W, Q );
            } 
            
            if( cnt!= |V| ) 
                Error( “图中有回路”); 
        }
        将度为0的顶点放在一个队列中,这样每次都能快速得到度为0的顶点
        
       

 

posted @ 2019-11-24 14:17  foreast  阅读(214)  评论(0编辑  收藏  举报