JS数据结构与算法—图

233 阅读5分钟

图结构

什么是图

  • 图结构是一种与树结构有些相似的树结构
  • 图论是数学的一个分支,并且,在数学的概念上,树是图的一种
  • 它以图为研究对象,研究顶点和边组成的图形的数学理论和方法
  • 主要研究的目的是事物之间的关系,顶点代表事物,边代表两个事物间的关系

图的特点

  • 一组顶点:通常用E(Vertex)表示顶点的集合
  • 一组边:通常用E(Edge)表示边的集合
  • 边可以是无向的,也可以是有向的
  • 比如A — B,通常表示无向,A –> B,通常表示有向。

一笔图的充要条件

  • 奇点的数目不是0个就是2个
  • 连到一点的边的数目如是奇数条,就称为奇点
  • 如果是偶数条就成为偶点
  • 要想一笔画成,必须中间点均是偶点
  • 也就是有来路必有另一条去路,奇点只能在两端,因此任何图能一笔画成,奇点要么没有要么在两端。

图的术语

  • 顶点:表示图中的一个结点。

  • 边:表示顶点和顶点之间的连线.

    • 注意: 这里的边不要叫做路径, 路径有其他的概念。
  • 相邻顶点:由一条边连接在一起的顶点称为相邻顶点.

  • 度:一个顶点的度是相邻顶点的数量.

  • 路径:路径是顶点到另一个顶点

    • 简单路径:简单路径要求不包含重复的顶点,比如 0 1 5 9 是一条简单路径
    • 回路:第一个顶点和最后一个顶点相同的路径称为回路
  • 无向图,有向图

    • 无向图:任何边都没有方向
    • 有向图:边有方向
  • 无权图和带权图

    • 无权图:边是没有任何意义的。
    • 带权图:表示边有一定的权重,可以是任意你希望表示的数据。

图的表示

邻接矩阵

  • 邻接矩阵让每个节点和一个整数向关联, 该整数作为数组的下标值。
  • 通常用一个二维数组来表示顶点之间的连接.
  • 缺点:
    • 如果是一个无向图, 邻接矩阵展示出来的二维数组, 其实是一个对称图,这种情况下会造成空间的浪费。
    • 如果图是一个稀疏图,那么矩阵中将存在大量的0, 这意味着我们浪费了计算机存储空间来表示根本不存在的边.而且即使只有一个边, 我们也必须遍历一行来找出这个边, 也浪费很多时间.

image.png 图片来源(zhyjc6.github.io/posts/2020/…)

邻接表

  • 邻接表由图中每个顶点以及和顶点相邻的顶点列表组成.
  • 这个列表有很多中方式来存储: 数组/链表/字典(哈希表)都可以.
  • 缺点
    • 邻接表计算"出度"是比较简单的(出度: 指向别人的数量, 入度: 指向自己的数量)
    • 邻接表如果需要计算有向图的"入度", 那么是一件非常麻烦的事情.

image.png 图片来源(zhyjc6.github.io/posts/2020/…)

图的遍历

遍历方式

  • 图的遍历思想和树的遍历思想是一样的
  • 图的遍历意味着需要将图中每个顶点访问一边,并且不能有重复
  • 遍历的两种算法
    • 广度优先搜索(Breadth-First Search,简称BFS)
    • 深度优先搜索(Dep-First Search,简称DFS)
  • 两种遍历算法,都需要明确指定第一个被访问的顶点
  • BFS:基于队列,入队列的顶点先被探索,从指定的第一个顶点开始遍历图, 先访问其所有的相邻点, 就像一次访问图的一层.换句话说, 就是先宽后深的访问顶点

image.png 图片来源博客:www.cnblogs.com/skywang1234…

  • DFS:基于栈或者使用递归,通过将顶点存入栈中,顶点是沿着路径被探索的,存在新的相邻顶点就去访问,接着原路回退并探索下一条路径

image.png 图片来源博客:www.cnblogs.com/skywang1234… 为了记录顶点是否被访问过,我们使用三种颜色来反应它们的状态

  • 白色:该顶点未被访问
  • 灰色:表示该顶点被访问过,但未被探索
  • 黑色:表示该顶点被访问且被完全探索过

图的封装

包括加入顶点,加入边,toString,广度优先搜索和深度优先搜索,使用字典和队列封装

    class Dictionary {
                constructor() {
                    this.items = {};
                }
                //字典添加键值对
                set(key, value) {
                    this.items[key] = value;
                }
                //判断字典中是否有某个key
                has(key) {
                    return this.items.hasOwnProperty(key);
                }

                //从字典中移除元素
                remove(key) {
                    if (!this.has(key)) return false;
                    delete this.items[key];
                    return true;
                }
                //根据key获取value
                get(key) {
                    return this.has(key) ? this.items[key] : undefined;
                }
                //获取所有的keys
                keys() {
                    return Object.keys(this.items);
                }
                //
            }
            class Queue {
                constructor() {
                    this.item = [];
                }
                //1.向队尾添加一个或多个项
                enqueue(element) {
                    this.item.push(element);
                }
                //2.从队列中删除前端的元素
                dequeue() {
                    return this.item.shift();
                }
                //3.查看前端的元素
                front() {
                    return this.item[0];
                }
                //4.查看队列是否为空
                isEmpty() {
                    return this.item.length == 0;
                }
                //5.查看队列中的个数u
                size() {
                    return this.item.length;
                }
                //6.toString方法
                toString() {
                    let resultString = '';
                    for (let i = 0; i < this.item.length; i++) {
                        resultString += this.item[i] + ' ';
                    }
                    return resultString;
                }
            }
            class Graph {
                constructor() {
                    this.vertexes = [];
                    this.edges = new Dictionary();
                }
                //添加顶点
                addVertex(v) {
                    this.vertexes.push(v);
                    this.edges.set(v, []);
                }
                //添加边
                addEdge(v1, v2) {
                    this.edges.get(v1).push(v2);
                    this.edges.get(v2).push(v1);
                }
                toString() {
                    let resultString = '';
                    for (let i of this.vertexes) {
                        resultString += i + '->';
                        let vEdges = this.edges.get(i);
                        for (let j of vEdges) {
                            resultString += j + ' ';
                        }
                        resultString += '\n';
                    }
                    return resultString;
                }
                initializeColor() {
                    let colors = [];
                    for (let i = 0; i < this.vertexes.length; i++) {
                        colors[this.vertexes[i]] = 'white';
                    }
                    return colors;
                }
                //广度优先搜索
                bfs(vInit, handler) {
                    //初始化图中各顶点的颜色
                    let colors = this.initializeColor(this.vertexes);
                    //创建队列
                    let queue = new Queue();
                    //将初始值加入到队列中
                    queue.enqueue(vInit);
                    //遍历初始节点指向的顶点
                    while (!queue.isEmpty()) {
                        //删除表中第一个元素
                        let v = queue.dequeue();
                        //将删除元素v的颜色改为灰色
                        colors[v] = 'gray';
                        //声明v顶点指向的顶点
                        let vList = this.edges.get(v);
                        //循环遍历vList
                        for (let i of vList) {
                            //判断被指顶点的颜色
                            if (colors[i] === 'white') {
                                colors[i] = 'gray';
                                queue.enqueue(i);
                            }
                        }
                        //对v顶点进行处理
                        handler(v);
                        //将v顶点的颜色改为黑色
                        colors[v] = 'black';
                    }
                }
                dfs(vInit, handler) {
                    //初始化颜色
                    let colors = this.initializeColor(this.vertexes);
                    //递归搜索
                    this.dfsVisit(vInit, colors, handler);
                }
                dfsVisit(v, colors, handler) {
                    //改变顶点的颜色
                    colors[v] = 'gray';
                    //处理顶点
                    handler(v);
                    //获取顶点指向的订点
                    let vList = this.edges.get(v);
                    //遍历
                    for (let i of vList) {
                        if (colors[i] === 'white') {
                            this.dfsVisit(i, colors, handler);
                        }
                    }

                    //将顶点的颜色变为黑色
                    colors[v] = 'black';
                }
            }
            let graph = new Graph();
            let myVertexes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
            myVertexes.forEach(item => {
                graph.addVertex(item);
            });
            graph.addEdge('A', 'B');
            graph.addEdge('A', 'C');
            graph.addEdge('A', 'D');
            graph.addEdge('C', 'D');
            graph.addEdge('C', 'G');
            graph.addEdge('D', 'G');
            graph.addEdge('D', 'H');
            graph.addEdge('B', 'E');
            graph.addEdge('B', 'F');
            graph.addEdge('E', 'I');
            // console.log(graph.toString());
            let resultBfs = '';
            graph.bfs(graph.vertexes[0], function (v) {
                resultBfs += v + '';
            });
            console.log(resultBfs);
            let resultDfs = '';
            graph.dfs(graph.vertexes[0], function (v) {
                resultDfs += v + '';
            });
            console.log(resultDfs);
                               根据coderwhy老师的课程总结记录