图的遍历算法

简介: 图的遍历算法

公众号merlinsea


  • 图的基本介绍
  • 图是由节点有穷集合V和边的集合E组成,一个图中至少有一个节点,但边可以有也可以没有。
  • 图可以分为有向图和无向图,有向图中的边都是有方向的,无向图中的边时没有方向的。
  • 在图中,边是用于表示两个节点之间的一种关系,通常会用两个定点表示边。比如在有向图中<A,D>边表示由节点A出发到达节点D的边,在无向图中(A,D)边表示链接A和D的边,这条边既可以由A到D,也可以由D到A。


640.png


  • 边的入度和出度
  • 节点的度是用于表示由多少条边与这个节点相关联,比如在有向图中节点A的度为3,表示有3条边与节点A相关联。
  • 节点的入度是用于表示有多少条边指向这个节点,比如在有向图中节点A的入度为0,节点C的入度为2。
  • 节点的出度是用于表示有多少条边从这点节点发出,比如在有向图中节点A的出度为3,节点C的出度为0。
  • 有向完全图和无向完全图
  • 有向完全图:若有向图中有n个节点,且图中任意两个节点都有两条反向的边链接,即一共有n*(n-1)条边,则称之为有向完全图。
  • 无向完全图:若无向图中有n个节点,且图中任意两节点之间都有一条边链接,即一共有n*(n-1)/2条边,则称之为无向完全图。

640.png


  • 路径
  • 路径是相邻顶点序偶所构成的序列称之为路径,比如上图的有向图中<A,D>、<D,E>、<E,C>就构成一条路径,表示从A可以到达D,再到达E,最后到达C。
  • 连通、连通图和连通分量
  • 节点A和节点B之间存在一条路径,则称节点A和节点B之间是连通的,也称之为节点A 可达 节点B
  • 连通图是指从图中的任意节点出发,都可以达到其余的所有节点,则将该图称之为连通图。
  • 连通分量是指从图中取部分节点和部分边,若所取的这些节点和边构成的图是连通的,则将这部分节点和边组成的集合称之为原图的一个连通分量。

640.png


  • 权和网
  • 如果图中每条边都带上一个相应的数字,这种与边相关的数称之为权,权可以用于表示一个节点到另一个节点到距离或者代价。
  • 边上带有权重的图也称之为带权图,或者称之为网。

  • 图的存储结构——邻接矩阵的介绍
  • 邻接矩阵是表示节点之间关系的矩阵,假设图G={V,E}是一个有n个节点的图,节点的编号是从0~n-1,则我们定义的图G的邻接矩阵A是有如下特性的方阵:
  • A(i,j) = 0 ,表示 节点i 不存在一条直接指向 节点j 的边

  • A(i,j) = 1 ,表示 节点i 存在一条直接指向 节点j 的边
  • 如果图中的边是带权重的,那么我们可以将图G定义为:
  • A(i,j) = w ,表示 节点i 存在一条直接指向 节点j 的边,且边的权重为w
  • A(i,j) = INF,INF为无穷大 ,表示 节点i 不存在一条直接指向 节点j 的边



  • 邻接矩阵代码实现
/**
 * 定义节点信息
 */
class Vertex{
    public int no; //节点编号
    public String info; //节点的其他信息
    public Vertex(int no,String info){
        this.no = no;
        this.info = info;
    }
}
/**
 * 定义邻接矩阵
 */
public class MatricGraph {
    public Vertex[] vertices;
    public int[][] matric;
    public MatricGraph(int n,int[][] edges){
        vertices = new Vertex[n];
        matric = new int[n][n];
        for(int i=0;i<n;i++){
            vertices[i] = new Vertex(i,String.valueOf(i));
        }
        for(int k=0;k<edges.length;k++){
            int i = edges[k][0];
            int j = edges[k][1];
            matric[i][j]=1;
        }
    }
    /**
     * 输出邻接矩阵
     */
    public void output(){
        for(int i=0;i<matric.length;i++){
            for(int j=0;j<matric[0].length;j++){
                System.out.printf("%d \t",matric[i][j]);
            }
            System.out.println();
        }
    }
}
//Main函数启动类
public class Main {
    public static void main(String[] args) {
        int[][] edges = new int[][]{{1,0},{1,2},{2,5},{2,3},{5,3},{3,4},{4,0},{0,5}};
        MatricGraph graph = new MatricGraph(6,edges);
        graph.output();
    }
}


  • 图的存储结构——邻接表的介绍
  • 邻接表是图的一种链式存储结构,邻接表的表头用于存放节点信息,每个节点后面用单链表连接着该节点的所有邻居节点。
  • 邻接表是由节点表和边表组成,针对无向图,边表就是与节点相连接的边;针对有向图,边表若是从该节点发出的边则称之为出边表,边表若是指向该节点的边则称之为入边表。
  • 如果图是一个带权图,可以在每个边节点上增加一个带权因子,用于表示权重。

640.png


  • 邻接表代码实现


/**
 * 边表的节点
 */
class EdgeNode{
    public int no; //节点编号
    public EdgeNode next; // 下一个边
    public EdgeNode(int no){
        this.no = no;
        next = null;
    }
    @Override
    public String toString() {
        return "EdgeNode{" +
                "no=" + no +
                '}';
    }
}
/**
 * 节点表的节点
 */
class VertexNode{
    public int no; //节点编号
    public String info; // 节点额外信息
    public EdgeNode neighbors; // 邻居节点
    public VertexNode(int no,String info){
        this.no = no;
        this.info = info;
        neighbors = null;
    }
    @Override
    public String toString() {
        return "VertexNode{" +
                "no=" + no +
                ", info='" + info + '\'' +
                '}';
    }
}
/**
 * 定义邻接表 [出边表]
 */
public class LinkGraph {
    public VertexNode[] graph;
    /**
     *传入节点个数n和边信息edges ,edges[k] = {i,j} 表示从i到j有一条边
     */
    public LinkGraph(int n,int[][] edges){
        graph = new VertexNode[n];
        for(int i=0;i<n;i++){
            graph[i] = new VertexNode(i,String.valueOf(i));
        }
        for(int k=0;k<edges.length;k++){
            int i = edges[k][0];
            int j = edges[k][1];
            EdgeNode edgeNode = new EdgeNode(j);
            //采用头插入法
            edgeNode.next = graph[i].neighbors;
            // 当前节点neighbors就是邻居节点
            graph[i].neighbors = edgeNode;
        }
    }
    public void output(){
        for(int i=0;i< graph.length;i++){
            System.out.println("节点 "+graph[i].toString()+" 的邻居:");
            EdgeNode edgeNode = graph[i].neighbors;
            while(edgeNode!=null){
                System.out.printf("%s \t" , edgeNode);
                edgeNode = edgeNode.next;
            }
            System.out.println();
        }
    }
}
public class Main {
    public static void main(String[] args) {
        int[][] edges = new int[][]{{1,0},{1,2},{2,5},{2,3},{5,3},{3,4},{4,0},{0,5}};
        LinkGraph graph = new LinkGraph(6,edges);
        graph.output();
    }
}


  • 图的遍历算法——深度优先遍历(DFS)介绍
  • 图的深度优先遍历思路:从访问出发点v开始,将v标记为已访问过,然后选取v的所有邻居节点,从这些没有被访问过的邻居节点出发,继续访问。

640.png



  • 基于邻接表的DFS代码实现
 /**
     * 图的深度优先遍历
     */
    public void dfs(int i,boolean[] isVisited){
        System.out.println(graph[i]);
        isVisited[i] = true;
        EdgeNode neighbor = graph[i].neighbors;
        while(neighbor!=null){
          //只要存在邻居且邻居没有访问过,就对这个进行进行dfs
            if(!isVisited[neighbor.no]){
                dfs(neighbor.no,isVisited);
            }
            neighbor = neighbor.next;
        }
    }
public class Main {
    public static void main(String[] args) {
        int[][] edges = new int[][]{{0,1},{1,2},{2,5},{2,3},{5,3},{3,4},{4,0},{0,5}};
        LinkGraph graph = new LinkGraph(6,edges);
        graph.output();
        boolean[] isVisited = new boolean[6];
        graph.dfs(0,isVisited);
    }
}


  • 算法性能分析
  • 深度优先遍历是针对每一个节点,访问了该节点的每一条边一次,因此我们可以假设一共有n个节点,有e条边,所以算法的时间复杂度的上限是O(n*e)


  • 图的遍历算法——广度优先遍历(BFS)介绍
  • 图的广度优先遍历的基本思路:首先访问起始节点v,然后依次访问节点v的邻居节点w1,w2,w3,... ,然后再继续访问w1的邻居节点,w2的邻居节点,.....,这样反复访问,直到访问结束。
  • 可以发现广度优先遍历是需要利用一个队列来实现的,下面我们给出广度优先遍历的流程图。


640.png


  • 基于邻接表的BFS代码实现


/**
     * 图的广度优先遍历
     */
    public void bfs(){
        Queue<Integer> queue = new LinkedList<>();
        boolean[] isVisited = new boolean[graph.length];
        //队列初始化
        queue.offer(0);
        //遍历
        while(!queue.isEmpty()) {
            int cur = queue.poll();
            if(!isVisited[cur]) {
                System.out.println(graph[cur]);
                isVisited[cur] = true;
                EdgeNode edgeNode = graph[cur].neighbors;
                while (edgeNode != null) {
                    if (!isVisited[edgeNode.no]) {
                        queue.offer(edgeNode.no);
                    }
                    edgeNode = edgeNode.next;
                }
            }
        }
    }
public class Main {
    public static void main(String[] args) {
        int[][] edges = new int[][]{{0,1},{1,2},{2,5},{2,3},{5,3},{3,4},{4,0},{0,5}};
        LinkGraph graph = new LinkGraph(6,edges);
        graph.output();
        System.out.println("图的广度优先遍历");
        graph.bfs();
    }
}
相关文章
|
2月前
|
算法
【算法与数据结构】二叉树(前中后)序遍历2
【算法与数据结构】二叉树(前中后)序遍历
|
13天前
|
存储 算法 Java
Java中,树与图的算法涉及二叉树的前序、中序、后序遍历以及DFS和BFS搜索。
【6月更文挑战第21天】Java中,树与图的算法涉及二叉树的前序、中序、后序遍历以及DFS和BFS搜索。二叉树遍历通过访问根、左、右子节点实现。DFS采用递归遍历图的节点,而BFS利用队列按层次访问。以下是简化的代码片段:[Java代码略]
19 4
|
1天前
|
存储 算法 搜索推荐
|
17天前
|
算法 Java 索引
【算法】重建二叉树并进行后序遍历的Java实现
【算法】重建二叉树并进行后序遍历的Java实现
18 5
|
17天前
|
存储 算法 Java
广度优先搜索(Breadth-First Search,BFS)是一种用于图的遍历或搜索的算法。
广度优先搜索(Breadth-First Search,BFS)是一种用于图的遍历或搜索的算法。
|
28天前
|
存储 算法
数据结构和算法学习记录——二叉树的存储结构&二叉树的递归遍历(顺序存储结构、链表存储结构、先序中序后序递归遍历)
数据结构和算法学习记录——二叉树的存储结构&二叉树的递归遍历(顺序存储结构、链表存储结构、先序中序后序递归遍历)
13 0
数据结构和算法学习记录——二叉树的存储结构&二叉树的递归遍历(顺序存储结构、链表存储结构、先序中序后序递归遍历)
|
8天前
|
算法 搜索推荐 C++
C++之STL常用算法(遍历、查找、排序、拷贝、替换、算数生成、集合)
C++之STL常用算法(遍历、查找、排序、拷贝、替换、算数生成、集合)
14 0
|
17天前
|
人工智能 算法 Java
深度优先搜索(Depth-First Search,DFS)是一种用于遍历或搜索树或图的算法。
深度优先搜索(Depth-First Search,DFS)是一种用于遍历或搜索树或图的算法。
|
17天前
|
存储 算法
【二叉树】数据结构——BST二叉树基本概念及算法设计(插入、删除、遍历操作)
【二叉树】数据结构——BST二叉树基本概念及算法设计(插入、删除、遍历操作)
|
2月前
|
机器学习/深度学习 算法 分布式数据库
数据结构与算法⑭(第四章_下)二叉树的模拟实现和遍历代码(下)
数据结构与算法⑭(第四章_下)二叉树的模拟实现和遍历代码
30 1