zoukankan      html  css  js  c++  java
  • 图简介:图是一种与树有些相像的数据结构,实际上,从数学意义上讲,树是图的一种。之前的数据结构都有一个框架,这个框架是由相应的算法规定的。例如,二叉树是那样一个形状,就是因为那样的形状使它容易搜索数据和插入新数据。树的边表示了从一个节点到另一个节点的快捷方式。另一方面,图通常有一个固定的形状,这是由物理或抽象的问题所决定的。

    邻接:如果两个顶点被同一条边连接,就称这两个顶点是邻接的。

    路径:路径是边的序列。例如从顶点B到顶点J,经过顶点A和顶点E,路径就为BAEJ。有可能路径不止一条。

    连通图:如果至少有一条路径可以连接起所有的顶点,那么这个图被称作连通的。

    有权图:在某些图中,边被赋予一个权值,权值是一个数字,它能代表两个顶点间的物理距离,或者从一个顶点到另一个顶点的时间,或者是两点间的花费,这样的图叫做带权图。

    顶点:在大多数情况下,顶点表示某个真实世界的对象,这个对象必须用数据项来描述。示例:

    public class Vertax {
        private char label;
        private boolean wasVisited;
        Vertax(char label){
            this.label = label;
            this.wasVisited = false;
        }
        public char getLabel() {
            return label;
        }
        public void setLabel(char label) {
            this.label = label;
        }
        public boolean isWasVisited() {
            return wasVisited;
        }
        public void setWasVisited(boolean wasVisited) {
            this.wasVisited = wasVisited;
        }
    }

    边:一般用两个方法表示图:邻接矩阵和邻接表。(如果一条边连接两个顶点,这两个顶点就是邻接的)。

    邻接矩阵:是一个二维数组,数据项表示两点间是否存在边。如果图有N个顶点,邻接矩阵就是N*N的数组。

    需要注意的是,矩阵的上三角是下三角的镜像:两个三角包含了相同的信息。这个冗余看似低效,但在大多数计算机语言中,创造一个三角形数组比较困难,所以只好求其次接受这个冗余。这也要求当增加一条边时,必须更新邻接矩阵的两部分,而不是一部分。

    邻接表:是一种链表数组(或者是链表的链表)。

      下面介绍图的遍历。有两种方式,深度优先遍历和广度优先遍历。深度优先搜索通过栈来实现,而广度优先搜索通过队列实现。

    深度优先搜索:先找一个起始点,首先访问该顶点,然后把该点放入栈中,以便记住它,最后标记该点。

    规则1:如果可能,访问一个邻接的未访问顶点,标记它,并把它放入栈中。

    规则2:当不能执行规则1时,如果栈不空,就从栈中弹出一个顶点。

    规则3:如果不能执行规则1和规则2,就完成了整个搜索过程。

    深度优先搜索算法要得到距离起始点最远的顶点,然后在不能继续前进的时候返回。“深度”这个术语表示与起始点的距离。

    模拟问题:深度优先搜索通常用在游戏仿真中(还有走迷宫问题)。在一般的游戏中,可以在几个可能的动作中选择一个,每个选择导致更进一步的选择,这些选择又产生了更多的选择,这样就形成了一个代表可能性的不断伸展的树形图。一个选择点代表一个顶点,采取的特定选择代表边,由它可以到达下一个选择顶点。

    广度优先搜索:跟深度优先搜索相反,算法好像要尽可能地靠近起始点,它首先访问起始顶点的所有邻接点,然后再访问较远的区域。这种搜索不能用栈,而要用队列来实现。

    规则1:访问下一个未来访问的邻接点(如果存在),这个顶点必须是当前顶点的邻接点,标记它,并把它插入到队列中。

    规则2:如果因为已经没有未访问顶点而不能执行规则1,那么从队列头取一个顶点(如果存在),并使其成为当前节点。

    规则2:如果因为队列为空而不能执行规则2,则搜索结束。

    广度优先搜索有一个有趣的属性:它首先找到与起始点相距一条边的所有顶点,然后是与起始点相距两条边的顶点,依此类推。如果要寻找起始顶点到指定顶点的最短距离,那么这个属性非常有用。首先执行BFS,当找到指定顶点时,就可以说这条路径是到这个顶点的最短路径。

    package graph;
    import stack.Queue;
    import stack.Stack;
    
    //
    public class Graph {
        private final static int MAX_LENGTH = 20;//顶点最大数,容量
        private Vertax[] vertaxList;//顶点数组
        private int[][] adjacentMatrix;//邻接矩阵
        private int currentLength;//当前顶点数
        
        Graph(){
            vertaxList = new Vertax[MAX_LENGTH];
            currentLength = 0;
            adjacentMatrix = new int[MAX_LENGTH][MAX_LENGTH];
            for(int i = 0;i < MAX_LENGTH;i++)
                for(int j = 0;j < MAX_LENGTH;j++)
                    adjacentMatrix[i][j] = 0;
        }
        //添加顶点
        public void addVertax(char ch){
            vertaxList[currentLength++] = new Vertax(ch);
        }
        //添加节点后修改邻接矩阵
        public void addEdge(int start,int end){
            adjacentMatrix[start][end] = 1;
            adjacentMatrix[end][start] = 1;
        }
        //展示顶点
        public void displayVertax(int index){
            System.out.println(vertaxList[index].getLabel());
        }
        //深度优先搜索
        public void depthFirstSearch(){
            vertaxList[0].setWasVisited(true);//标记成已访问状态
            displayVertax(0);
            Stack stack = new Stack();
            stack.push(0);
            int currentIndex;
            while(!stack.isEmpty()){
                currentIndex = (Integer)stack.peek();
                currentIndex = getAdjacentVertax(currentIndex);
                if(currentIndex != -1){
                    vertaxList[currentIndex].setWasVisited(true);
                    displayVertax(currentIndex);
                    stack.push(currentIndex);
                }else{
                    stack.pop();
                }
            }
            //重置成未访问状态
            for(int i = 0;i < currentLength;i++){
                vertaxList[i].setWasVisited(false);
            }
        }
        //广度优先搜索
        public void breadthFirstSearch(){
            Queue queue = new Queue();
            vertaxList[0].setWasVisited(true);//标记成已访问状态
            displayVertax(0);
            queue.insert(0);
            int currentIndex;
            while(!queue.isEmpty()){
                currentIndex = (Integer)queue.peek();
                currentIndex = getAdjacentVertax(currentIndex);
                if(currentIndex == -1){
                    queue.remove();
                }else{
                    vertaxList[currentIndex].setWasVisited(true);//标记成已访问状态
                    displayVertax(currentIndex);
                    queue.insert(currentIndex);
                }
            }
            //重置成未访问状态
            for(int i = 0;i < currentLength;i++){
                vertaxList[i].setWasVisited(false);
            }
        }
        //返回未访问过的邻接节点
        public int getAdjacentVertax(int i){
            for(int j = 0;j < currentLength;j++){
                if(adjacentMatrix[i][j] == 1 && vertaxList[j].isWasVisited() == false){
                    return j;
                }
            }
            return -1;
        }
    }

    最小生成树:对于非带权图,最少的边连接所有的顶点,保证彼此连通,这就组成了最小生成树(MST)。注意,最小生成树边E的数量总比顶点V的数量小1:E = V - 1。记住,不必关心边的长度,并不需要找到一条最短路径,而是要找最少数量的边。(在带权图里面,这点会改变)。

    创建最小生成树的算法与搜索的算法非常类似,在执行深度优先搜搜过程中,记录走过的边,就可以创建一棵最小生成树。这是因为DFS访问所有顶点,但只访问一次。

    有向图:图有时需要一种前面没有涉及过的特性:边有方向。这时,图叫做有向图,在有向图中,只能沿着边指定的方向移动。在程序中,有向图与无向图的区别是有向图的边在邻接矩阵中只有一项。它的上下三角不是对称的。

    拓扑排序:对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

    规则1:找到一个没有后继节点的顶点。

    规则2:从图中删除这个顶点,在列表的前面插入顶点的标记。

    身体是革命的本钱,爱跑步,爱生活!
  • 相关阅读:
    【矩阵】
    关于集合的思路
    TSQL生成Combguid
    ServiceLocator是反模式
    使用表达式树创建对象
    TeamCity配置笔记
    AutoMapper映射ExpressionTree
    Automapper扩展方法
    Cache&Session Viewer
    Orchard源码:Logging
  • 原文地址:https://www.cnblogs.com/caozx/p/8639461.html
Copyright © 2011-2022 走看看