图的深度优先遍历及广度优先遍历

图的引出:前面我们学了线性表和树。线性表局限于一个直接前驱和一个直接后继的关系;树也只能有一个直接前驱也就是父结点。但我们需要表示多对多的关系时,我们就需要用到图这种数据结构。

图的表示方式有两种:二维数组表示(邻接矩阵)链表表示(邻接表)

邻接矩阵中,如果两个顶点相连通,则为1,如果不连通,则为零。

图的遍历

有两种策略。1、深度优先遍历(Depth First Search)。2、广度优先遍历(Broad First Search)

深度优先遍历思想:深度优先遍历是一种纵向切入的思想。思想是先访问当前顶点,然后再以这个顶点作为初始顶点,访问该顶点的第一个邻接顶点。可以这样理解:每次访问完当前顶点后首先访问当前顶点的第一个邻接顶点。这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接结点进行横向访问。由此可以看出,需要通过递归来实现深度优先遍历

广度优先遍历思想:广度优先遍历类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的节点的顺序,以便按这个顺序来访问这些结点的邻接结点。

接下来我举一个例子,讲述深度优先遍历的思想

接下来还是以同样一个例子讲述广度优先的遍历思想:

 两者的区别:深度优先就是一直到底遍历;而广度优先就好似一张网,一步一步迈进遍历。

这是两者最主要的区别

深度优先遍历的步骤

  1. 访问初始结点v,并标记结点v为已访问。
  2. 查找结点v的第一个邻接结点w。
  3. 若w存在,则继续执行4,如果w不存在,则回到第1步,将从v的下一个结点继续。
  4. 若w未被访问,对w进行深度优先遍历递归(即把w当作另一个v,然后进行步骤123).
  5. 查找结点v的w邻接结点的下一个邻接结点,转到步骤3.  

广度优先遍历的步骤

  1. 访问初始结点v并标记结点v为已访问。
  2. 结点v入队列
  3. 当队列非空时,继续执行,否则算法结束。
  4. 出队列,取得队列头结点u。
  5. 查找结点u的第一个邻接结点w。
  6. 若结点u的邻接结点w不存在,则转到步骤3;否则循环执行以下三个步骤:

        6.1若结点w尚未被访问,则访问结点w并标记为已访问。

        6.2结点w入队列。

        6.3查找结点u的继w邻接结点后的下一个邻接结点w,转到步骤6。

具体实现代码附上,大家可以通过代码仔细去感悟。

package com.liu.chart;

import java.util.ArrayList;
import java.util.LinkedList;


/**
 * @author liuweixin
 * @create 2021-09-20 8:50
 */
//图
public class VertexChart {
    ArrayList<String> VertexList;
    int[][] edges;//用来储存顶点之间的关系
    int numOfEdges;//表示边的个数

    public static void main(String[] args) {
//        String[] data = new String[]{"A", "B", "C", "D", "E"};
        String[] data = new String[]{"1", "2", "3", "4", "5", "6", "7", "8"};
        VertexChart vertexChart = new VertexChart(data.length);
        for (String value : data) {
            vertexChart.addVertex(value);
        }
//        vertexChart.addEdge(0,1,1);
//        vertexChart.addEdge(0,2,1);
//        vertexChart.addEdge(1,2,1);
//        vertexChart.addEdge(1,3,1);
//        vertexChart.addEdge(1,4,1);
        vertexChart.addEdge(0, 1, 1);
        vertexChart.addEdge(0, 2, 1);
        vertexChart.addEdge(1, 3, 1);
        vertexChart.addEdge(1, 4, 1);
        vertexChart.addEdge(2, 5, 1);
        vertexChart.addEdge(2, 6, 1);
        vertexChart.addEdge(3, 7, 1);
        vertexChart.addEdge(4, 7, 1);
        vertexChart.addEdge(5, 6, 1);
        vertexChart.show();
        System.out.println();
        vertexChart.dfs();//深度优先遍历:1-2-4-8-5-3-6-7
        System.out.println();
        vertexChart.bfs();//广度优先遍历:1-2-3-4-5-6-7-8
    }

    public VertexChart(int VertexCount) {
        edges = new int[VertexCount][VertexCount];
        VertexList = new ArrayList<String>(VertexCount);
        numOfEdges = 0;
    }

    /**
     * 对广度优先方法的封装
     */
    public void bfs(){
        boolean[] isVisited = new boolean[VertexList.size()];
        //添加该循环的原因在于,有可能数据是分布在两张图甚至多张图上的,所以我们需要对所有的数据实现广度优先遍历
        //如果数据都分布在单一的一张图上,则不需要加此循环
        for (int i = 0; i < VertexList.size(); i++) {//对所有数据进行广度优先遍历
            if(!isVisited[i]){//判断是否访问过
                bfs(isVisited,i);//进行广度优先遍历
            }
        }
    }
    /**
     *广度优先
     * @param isVisited 是否被访问
     * @param i  初始结点
     */
    public void bfs(boolean[] isVisited, int i){
        int u;//表示队列的头结点对应的下标
        int w;//邻接结点w
        //队列,记录结点访问的顺序
        LinkedList queue = new LinkedList();
        //访问结点,输出结点信息
        System.out.print(VertexList.get(i)+ " ");
        //标记为已访问
        isVisited[i]=true;
        //将结点加入队列
        queue.addLast(i);
        while (!queue.isEmpty()){
            //取出队列的头结点下标
            u=(Integer)queue.removeFirst();
            //得到第一个邻接结点的下标w
            w=getFirstNeighbor(u);
            while (w!=-1){//找到
                //是否访问过
                if(!isVisited[w]){
                    System.out.print(VertexList.get(w)+" ");
                    //标记已经访问
                    isVisited[w]=true;
                    //入队
                    queue.addLast(w);
                }
                //以u为前驱点,找w后面的下一个邻接点
                w=getNextNeighbor(u,w);//体现出广度优先.
            }
        }
    }

    //对深度优先遍历方法进行封装
    public void dfs() {
        boolean[] isVisited = new boolean[VertexList.size()];
        //添加该循环的原因在于,有可能数据是分布在两张图甚至多张图上的,所以我们需要对所有的数据实现深度优先遍历
        //如果数据都分布在单一的一张图上,则不需要加此循环
        for (int i = 0; i < VertexList.size(); i++) {//对所有数据进行深度优先遍历
            if(!isVisited[i]){//判断是否访问过
                dfs(isVisited, 0);//进行深度优先
            }
        }

    }

    /**
     * 深度优先遍历
     *
     * @param isVisited 是否被访问
     * @param i         初始结点
     */
    public void dfs(boolean[] isVisited, int i) {
        System.out.print(VertexList.get(i) + " ");
        isVisited[i] = true;//设置该点已被访问过
        //获取该结点的第一个邻接结点的下标
        int index = getFirstNeighbor(i);
        while (index != -1) {//如果循环能进来,此时表明有下一个邻接结点
            if (!isVisited[index]) {//如果该结点未被访问过
                //进行递归遍历+回溯
                dfs(isVisited, index);
            }
            //如果该结点已被访问过
            index = getNextNeighbor(i, index);
        }
    }

    /**
     * 根据前一个结点的邻接结点的下标获取下一个邻接结点
     *
     * @param v1
     * @param v2
     * @return
     */
    public int getNextNeighbor(int v1, int v2) {
        for (int i = v2 + 1; i < VertexList.size(); i++) {
            if (edges[v1][i] > 0) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 得到该下标的第一个邻接结点
     *
     * @param index
     * @return 如果找到,则返回该下标值,如果找不到,返回-1
     */
    public int getFirstNeighbor(int index) {
        for (int i = 0; i < VertexList.size(); i++) {
            if (edges[index][i] == 1) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 显示图的邻接矩阵
     */
    public void show() {
        for (int[] arr : edges) {
            for (int data : arr) {
                System.out.print(data + " ");
            }
            System.out.println();
        }
    }

    /**
     * 获取顶点的个数
     *
     * @return 返回顶点的个数
     */
    public int getVexCount() {
        return VertexList.size();
    }

    /**
     * 获取边的个数
     *
     * @return 返回边的个数
     */
    public int getNumOfEdges() {
        return numOfEdges;
    }

    /**
     * 返回对应的两个顶点之间的关系
     *
     * @param v1 第一个顶点的下标
     * @param v2 第二个顶点的下标
     * @return 如果返回1,则表示其可以连通;如果返回0,则表示其不连通
     */
    public int getWeight(int v1, int v2) {
        return edges[v1][v2];
    }

    /**
     * 获取对应下标的顶点值
     *
     * @param index 对应下标
     * @return 返回该顶点值
     */
    public String getVertex(int index) {
        return VertexList.get(index);
    }

    /**
     * 把顶点的数据存储到List中
     *
     * @param data
     */
    public void addVertex(String data) {
        VertexList.add(data);
    }

    /**
     * 将顶点之间的关系进行表示
     *
     * @param v1     表示点的下标,即第几个顶点
     * @param v2     第二个顶点对应的下边
     * @param weight 两者的关系。如果为1,则表示两者连通;如果为0,则表示两者不连通。
     */
    public void addEdge(int v1, int v2, int weight) {
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numOfEdges++;
    }
}

  • 2
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一个较为复杂的算法问题,需要一定的编程基础和论知识。以下是一份C++代码,实现了深度优先遍历广度优先遍历,并将其改为输出深度优先生成树和广度优先生成树。 ```c++ #include<iostream> #include<cstdio> #include<cstring> #include<queue> #define MAXN 100 using namespace std; int n,m;//n个点,m条边 int head[MAXN],nex[MAXN],ver[MAXN]; int dfn[MAXN],low[MAXN],vis[MAXN],fa[MAXN]; int dcnt,cnt,tot; queue<int>q; void add(int x,int y){ ver[++cnt]=y; nex[cnt]=head[x]; head[x]=cnt; } void tarjan(int x){//tarjan算法求边双 dfn[x]=low[x]=++dcnt; vis[x]=1; for(int i=head[x];i;i=nex[i]){ int y=ver[i]; if(!dfn[y]){ tarjan(y); low[x]=min(low[x],low[y]); if(low[y]>=dfn[x]){ tot++; int z; do{ z=q.front(); q.pop(); printf("%d ",z); fa[z]=tot; }while(z!=y); printf("%d\n",x); fa[x]=tot; } } else if(vis[y]){ low[x]=min(low[x],dfn[y]); } } } void bfs(){//广搜生成树 memset(vis,0,sizeof(vis)); q.push(1); vis[1]=1; while(!q.empty()){ int x=q.front(); q.pop(); for(int i=head[x];i;i=nex[i]){ int y=ver[i]; if(!vis[y]){ vis[y]=1; q.push(y); printf("%d->%d\n",x,y); fa[y]=x; } } } } void dfs(int x){//深搜生成树 vis[x]=1; for(int i=head[x];i;i=nex[i]){ int y=ver[i]; if(!vis[y]){ printf("%d->%d\n",x,y); fa[y]=x; dfs(y); } } } int main(){ memset(head,0,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(vis,0,sizeof(vis)); cnt=0; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b); add(b,a); } printf("Tarjan:\n"); for(int i=1;i<=n;i++){ if(!dfn[i]){ tarjan(i); } } printf("DFS:\n"); memset(vis,0,sizeof(vis)); dfs(1); printf("BFS:\n"); memset(vis,0,sizeof(vis)); bfs(); return 0; } ``` 输入格式: 第一行输入两个整数n和m,表示有n个点和m条边。 接下来m行,每行两个整数a和b,表示a和b之间有一条边。 输出格式: 首先输出Tarjan算法求得的边双联通分量。 接下来输出深度优先生成树。 最后输出广度优先生成树。 示例输入: ``` 10 12 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 2 6 3 7 5 9 ``` 示例输出: ``` Tarjan: 1 2 3 4 5 6 7 8 9 10 DFS: 1->2 2->3 3->4 4->5 5->6 6->7 7->8 8->9 9->10 BFS: 1->2 1->3 2->6 3->4 4->5 6->7 7->8 8->9 9->10 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值