数据结构与算法15 - 图

1. 图概念 - 多对多关系

  • 提出背景
    1. 线性表只能局限于一个直接前驱、直接后继的关系 (一对一关系)
    2. 树只能局限于一个父节点的关系 (一对多关系)


专有名词
1. 边:两个结点之间的连接
2. 结点(顶点):可以具有零个或多个相邻元素
3. 无(有)向图:顶点之间的连接没有(有)方向 - 即边没有方向
4. 带权图(网):边带数值的图
5. 图的表示
①:数组形式 - 邻接矩阵
②:数组+链表形式 - 邻链表


无向图 - 数据结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-etWOj5RL-1587795740717)(en-resource://database/32760:1)]


有向图 - 数据结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-masTmSRk-1587795740721)(en-resource://database/32758:1)]


带权图(也称网) - 数据结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IeaO7fG9-1587795740725)(en-resource://database/32762:1)]

1.1 图的表式

1.1.1 矩阵 - 数组

缺点:浪费太多的空间 - 标记某个结点与图中所有结点的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8JqgGN16-1587795740728)(en-resource://database/32766:3)]

1.1.2 邻接表 - 数组+链表

优势:只记录某个结点与该结点有联系的结点信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQLgwBLy-1587795740733)(en-resource://database/32768:1)]

2. 代码

2.1 矩阵

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OYpXDFre-1587795740735)(en-resource://database/32766:3)]


2.1.1 基本类创建
/**
 * @Classname UndirectedWeightGraph
 * @Description
 * @Date 2020/4/16 13:32
 * @Created by lrc
 */

//无向带权图
public class UndirectedWeightGraph {

    //存储结点
    ArrayList<String> vertexs;

    // 存储结点之间的权值 - 结点之间的关系 -- 矩阵
    int[][] edges;

    // 存储图中边的个数
    int numOfEdges;
    
    
    //存储是否遍历过的节点信息 false未遍历  true已经遍历过
    boolean[] isVisable;
    
    
    public static void main(String[] args) {

        //1, 结点信息
        String[] vertexs = {"v1", "v2", "v3", "v4"};
        Integer n = vertexs.length;

        // 2. 创建带5个结点的无序带权图
        UndirectedWeightGraph graph = new UndirectedWeightGraph(n);

        // 3. 添加结点
        for(String vertex : vertexs) {
            graph.insertVertex(vertex);
        }

        // 4. 添加结点之间的权值 - 这里的权值1仅表示两结点之间有连接关系
        graph.insertWeight(0,1, 1);
        graph.insertWeight(0,2, 1);
        graph.insertWeight(1,2, 1);
        graph.insertWeight(1,3, 1);


        // 5. 显示图的矩阵
        //System.out.println(graph.getGraph());
        graph.showGrpah();

    }

    //初始化 - 结点个数
    UndirectedWeightGraph(int vertexsNum) {

        this.vertexs = new ArrayList<>(vertexsNum);
        this.edges = new int[vertexsNum][vertexsNum];
        this.numOfEdges = 0;

    }

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

    //获取结点个数
    public int getVertexsNum() {
        return this.vertexs.size();
    }


    //添加新结点
    public void insertVertex(String vertex) {
        this.vertexs.add(vertex);
    }

    //获取两结点之间的边的权值 "A结点表示0" "B节点表示1"
    public int getWeight(int firstVertex, int secondVertext) {
        return edges[firstVertex][secondVertext];
    }


    //获取矩阵 - 即把数组Edges权值图打印出来
    public String getGraph() {
        return Arrays.deepToString(edges);
    }

    //显示矩阵
    public void showGrpah() {
        for(int[] edgeWeight : edges) {
            System.out.println(Arrays.toString(edgeWeight));
        }
    }



    //添加结点之间的关系 - 即权值 - 因为无向图的关系必须赋值数组中两个元素相同的权值
    public void insertWeight(int firstVertex, int secondVertext, int weight) {
        edges[firstVertex][secondVertext] = weight;
        edges[secondVertext][firstVertex] = weight;
        numOfEdges++;
    }


}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C2CUzsK2-1587795740739)(en-resource://database/32770:1)]


2.1.2 遍历图的结点
遍历
深度优先遍历(Depth First Search)
广度优先遍历(Board First Search)
2.1.2.1 深度优先遍历(DFS)

DFS思想:访问到某个结点,就以某个结点访问该结点的第一个邻接结点 —纵向挖掘访问

找到W
未被访问
访问过
未找到W
1. 访问并输出初始结点V,并标记该V结点已被访问
2. 查找V结点某个邻接结点W
3. 是否找到W?
是否W已经被访问过?
对W进行深度遍历 - 递归进入该结点(即V为W,重复1、2、3步骤)
查找V的下一个邻接结点 - 从步骤3开始
未找到
返回到上一层递归


深度优先遍历代码
写法1

    //得到第一个邻接点的小标 - 找到返回W的小标,未找到返回-1 - 下一个小标的结点
    public int getFirstNeigbor(int index) {

        for(int j = 0; j <vertexs.size(); j++) {
            if(edges[index][j] > 0) {
                return j;
            }
        }
        return -1;
    }
    
    
    //根据前一个邻结点的小标来获取一个邻接点- 找到返回新的W的小标,未找到返回-1
    public int getNextNeigbor(int index, int w) {

        for(int j = w+1; j<vertexs.size(); j++ ) {
            if(edges[index][j] > 0) {
                return j;
            }
        }
        return -1;
    }
    

    //深度优先遍历
    public void dfs(boolean[] isVisited, int i) {

        System.out.println(vertexs.get(i));

        isVisited[i] = true;

        int w = getFirstNeigbor(i);

        while(w != -1) {
            if(!isVisited[w]) {
                dfs(isVisited, w);
            }

            w = getNextNeigbor(i, w);
        }
    }
    
    
    //重载深度优先算法
    public void dfs() {

        for(int i = 0; i<getVertexsNum(); i++) {

            //如果未访问过,则从该结点开始访问
            if(isVisable[i] == false) {
                dfs(i);
            }
        }
        
        //遍历完后修改回原来的状态
        for(int i = 0; i < isVisable.length; i++) {
            isVisable[i] = false;
        }        

    }


写法2

    //得到下一个邻接结点 - 寻找当前结点未被访问的邻接结点
    public int getNextNeigbor2(int index) {


        for(int j = 0; j<isVisable.length; j++) {

            if(edges[index][j] > 0 && isVisable[j] == false) {
                return j;
            }
        }
        return -1;

    }    
    
    
    //深度优先遍历2
    public void dfs2(int i) {

        System.out.println(vertexs.get(i));

        isVisable[i] = true;

        int w = getNextNeigbor2(i);

        while(w != -1) {
             dfs2(w);
            w = getNextNeigbor2(i);
        }
    }    
    
    //重载深度优先算法
    public void dfs2() {
           //图可能存在没有变得结点 - 故需要遍历所有结点
        for(int i = 0; i<getVertexsNum(); i++) {
            //如果未访问过,则从该结点开始访问
            if(isVisable[i] == false) {
                dfs2(i);
            }
        }
        
        //遍历完后修改回原来的状态
        for(int i = 0; i < isVisable.length; i++) {
            isVisable[i] = false;
        }

    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yXBq9WWz-1587795740743)(en-resource://database/32766:3)]

测试

public static void main(String[] args) {

        //1, 结点信息
        String[] vertexs = {"v1", "v2", "v3", "v4"};
        Integer n = vertexs.length;

        // 2. 创建带5个结点的无序带权图
        UndirectedWeightGraph graph = new UndirectedWeightGraph(n);

        // 3. 添加结点
        for (String vertex : vertexs) {
            graph.insertVertex(vertex);
        }

        // 4. 添加结点之间的权值 - 这里的权值1仅表示两结点之间有连接关系
        graph.insertWeight(0, 1, 1);
        graph.insertWeight(0, 2, 1);
        graph.insertWeight(1, 2, 1);
        graph.insertWeight(1, 3, 1);


        // 5. 显示图的矩阵
        //System.out.println(graph.getGraph());
        graph.showGrpah();

        graph.dfs2();

}


流程讲解

1. 首先dfs2( 0 )  打印V1

2. 寻找与V1相邻并且未访问过的邻结点   找到V2即 1

3. 进入递归     dfs(1)   打印V2

4. 寻找与V2相邻并且未访问过的邻结点   找到V3即 2

5. 进入递归     dfs(2)   打印V3

6. 寻找与V3相邻并且未访问过的邻结点   未找到  退出dfs(2)这个递归  返回到dfs(1)这个递归

7. 寻找与V2相邻并且未访问过的邻结点   找到V4即 3

8. 进入递归     dfs(3)   打印V4

9. 寻找与V4相邻并且未访问过的邻结点   未找到  退出dfs(3)这个递归  返回到dfs(1)这个递归

10. 寻找与V2相邻并且未访问过的邻结点  未找到  退出dfs(1)这个递归  返回到dfs(0)这个递归

11. 寻找与V1相邻并且未访问过的邻结点   未找到   退出dfs(0)    结束函数


2.1.2.2 广度优先遍历(BFS)- 分层搜索

BFS思想:从队列弹出的结点为基准,访问该结点的所有未被访问的邻结节点,并将其放入队列中。如果该弹出的结点所有邻接结点被访问完,则从队列弹出一个结点,重复前者动作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yu8AQUZA-1587795740748)(en-resource://database/32776:1)]

    
    //得到下一个邻接结点
    public int getNextNeigbor2(int index) {


        for (int j = 0; j < isVisable.length; j++) {

            if (edges[index][j] > 0 && isVisable[j] == false) {
                return j;
            }
        }
        return -1;

    }    
    
    public void bfs(int i) {

        // 创建队列
        LinkedList<Integer> queue = new LinkedList();

        //将i入队
        queue.addLast(i);

        //从队列中获取头结点
        int u = queue.removeFirst();
        // 打印并输出结点信息
        System.out.println(this.getVertexByIndex(i));
        isVisable[u] = true;


        //以弹出结点读出所有该结点的邻接结点
        while(true) {
            
            //获取u的未被访问的邻接结点
            int w = getNextNeigbor2(u);
            if(w != -1) {
                //打印邻接结点并将其放入队列中,并标记已经被访问
                System.out.println(getVertexByIndex(w));
                isVisable[w] = true;
                queue.addLast(w);
                //如果当前结点已经遍历过所有其邻接结点,则从队列中弹出一个结点,并访问该结点所有邻接结点
            }else {
                // 如果队列无元素可弹,结束循环
                if(queue.size() == 0) {
                    break;
                }
                u = queue.removeFirst();
            }
        }





    }

    //重载广度优先遍历
    public void bfs() {

        //所有结点必须遍历 - 因为存在没有边的结点
       for(int i = 0; i < getVertexsNum(); i++) {

            if( isVisable[i] == false) {
                bfs(i);
            }

        }

        //恢复遍历前的状态
        for(int i = 0; i < isVisable.length; i++) {
            isVisable[i] = false;
        }
    }


2.1.2.3 案例讲解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QuwLym09-1587795740753)(en-resource://database/32780:1)]

#深度优先遍历DFS
1 -> 2 -> 4 -> 8 -> 5 -> 3 -> 6 -> 7

#广度优先遍历BFS
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值