数据结构与算法-图(Graph)-JS

本文介绍了图数据结构及其在现实世界中的应用,如家谱和组织架构。图由顶点和边组成,分为无向图和有向图,以及有权图和无权图。文中详细阐述了图的两种表示方法:邻接矩阵和邻接表,并讨论了它们各自的优缺点。此外,文章重点讲解了图的遍历算法,包括广度优先搜索(BFS)和深度优先搜索(DFS),并提供了JS实现。最后,给出了一个图结构的封装示例,以及BFS和DFS的测试代码。
摘要由CSDN通过智能技术生成

数据结构与算法-图(Graph)-JS

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

图可以模拟很多现实的数据结构,比如家谱或公司组织架构等

图是使用几叉树都不能模拟的。图的特点:一组顶点(通常用V Vertex表示顶点的集合);一组边(通常用E Edge表示边的集合),边可以是有向的,也可以是无向的

欧拉七桥问题开辟了图论与集合拓扑,欧拉最终解释这个问题无解,并给出了一笔画的充要条件是:奇点(连到一点的边的数目是奇数条)的数目不是0个就是2个。

图的术语:

顶点:图中的一个节点;边:顶点和顶点之间的连线;相邻顶点:有一条边连接在一起的顶点;度:一个顶点的度是相邻顶点的数量;路径:顶点与另一个顶点的一个连续序列;简单路径:路径不包含重复的顶点;回路:第一个顶点和最后一个顶点相同的路径称为回路;

无向图:所有的边都没有方向;有向图:图中的边是有方向的;无权图:边没有携带权重;有权图:边有一定的权重;

程序中如何表示图,需要体现顶点和边,顶点可以使用一个数组来存储起来或者使用链表,边的表示方法有些复杂

图的一种表示方式:邻接矩阵。使用二维数组来表示顶点直接的连接,可以表示方向,也可以用数字表示权重。邻接矩阵的问题:如果图是一个稀疏图,矩阵中存在大量的0,这就意味着浪费了计算机存储空间来表示根本不存在的边。

图的另一种表示方式:邻接表。可以通过数组、链表、字典、哈希表实现。邻接表的问题:计算“出度”(自己指向别人的数量)是比较简单的,计算“入度”就很麻烦了,需要遍历,可以构造一个“逆邻接表”,才能有效地计算“入度”,但是在开发中,“入度”相对用的比较少。

图的遍历思想和树的遍历思想是一样的,图的遍历意味着需要将图中的每个顶点访问一遍,并且不能有重复的访问

有两种算法可以对图进行遍历,广度优先搜索(Breath-First Search,BFS)深度优先搜索(Depth-First-Search,DFS),这两种遍历算法都需要明确指定第一个被访问的顶点。

两种算法的思想:BFS:基于队列,入队列的顶点先被探索;DFS:基于栈或使用递归(递归本质上就是函数栈的调用),通过将顶点存入栈中,顶点是沿着路径被探索的,存在新的相邻顶点就去访问。

为了记录顶点是否被访问过,使用三种颜色来反应它们的状态:白色:表示该顶点还没有被访问(没有入队列);灰色:表示该顶点被访问过(已经入队列了),但并未被探索过(还没有获取该顶点的相邻顶点);黑色:表示该顶点被访问过且别完全探索过(获取了该顶点的相邻顶点)

广度优先搜索:

从指定的第一个顶点开始遍历图,新访问其所有的相邻点,当依次访问相邻点时,继续访问相邻点的相邻点。

BFS实现步骤:创建一个队列Q;将白色v入队列Q(访问v),v变为灰色;如果Q非空,执行下面步骤,将灰色v从Q中取出队列,再将v所有的未被访问过的邻接点(白色)加入到队列中(被探索),此时v变为黑色,v可以进行一些其他操作,比如之后的呈现操作handler。

深度优先搜索:

从第一个指定的顶点开始遍历图,沿着路径知道这条路径最后的顶点被访问了,接着原路回退变探索下一条路径。(广度优先搜索相当于从开始顶点逐渐扩散,深度优先搜索相当于先一条路走到黑,然后原路返回找其他岔路)

//先进行一个字典的封装
function Dictionary() {
    //字典属性
    this.items = {};
    //字典操作方法
    //在字典中添加键值对
    Dictionary.prototype.set = function (key, value) {
        this.items[key] = value;
    }
    //判断字典中是否有某个key
    Dictionary.prototype.has = function (key) {
        return this.items.hasOwnProperty(key);
    }
    //从字典中移除元素
    Dictionary.prototype.remove = function(key) {
        //判断字典中是否有这个key
        if (!this.has(key)) return false;
        //从字典中删除key
        delete this.items[key];
        return true;
    }
    //根据key去获取value
    Dictionary.prototype.get = function (key) {
        return this.has(key) ? this.items[key] : undefined;
    }
    //获取所有的keys
    Dictionary.prototype.keys = function () {
        return Object.keys(this.items);
    }
    //获取所有的values
    Dictionary.prototype.values = function () {
        return Object.values(this.items);
    }
    //size方法
    Dictionary.prototype.size = function () {
        return this.keys().length;
    }
    //clear方法
    Dictionary.prototype.clear = function () {
        this.items = [];
    }
}
//封装队列类
function Queue() {
    //属性
    this.items = [];
    //方法
    //1.将元素加入到队列中
    Queue.prototype.enqueue = function (element) {
        this.items.push(element);
    }
    //2.从队列中删除前端元素
    Queue.prototype.dequeue = function () {
        return this.items.shift();
    }
    //3.查看前端的元素
    Queue.prototype.front = function () {
        return this.items[0];
    }
    //查看队列是否为空
    Queue.prototype.isempty = function () {
        return this.items.length == 0;
    }
    //查看队列中的元素个数
    Queue.prototype.size = function () {
        return this.items.length;
    }
    //6.toString方法
    Queue.prototype.toString = function () {
        var resultStr = '';
        for (var i = 0; i < this.items.length; i++) {
            resultStr += this.items[i];
        }
        return resultStr;
    }
}
//图结构的封装
function Graph() {
    //属性:顶点(数组)/边(字典)
    this.vertexes = []; //顶点
    this.edges = new Dictionary() //边
    //方法
    //添加顶点的方法
    Graph.prototype.addVertex = function (v) {
        this.vertexes.push(v);
        this.edges.set(v, []);
    }
    //添加边的方法
    Graph.prototype.addEdge = function (v1, v2) {
        //下面两句表示的是无向图,如果是有向图,只需要写其中的句就行
        this.edges.get(v1).push(v2);
        this.edges.get(v2).push(v1);
    }
    //实现toString方法
    Graph.prototype.toString = function () {
        //定义字符串,保存最终结果
        var resultString = "";
        //遍历所有的顶点,以及顶点对应的边,达到这样效果A->B C D
        for (var i = 0; i < this.vertexes.length; i++) {
            resultString += this.vertexes[i] + '->';
            var vEdges = this.edges.get(this.vertexes[i]);
            for (var j = 0; j < vEdges.length; j++) {
                resultString += vEdges[j] + ' ';
            }
            resultString += '\n';
        }
        return resultString;
    }
    //初始化状态颜色
    Graph.prototype.initializeColor = function () {
        var colors = [];
        for (var i = 0; i < this.vertexes.length; i++) {
            colors[this.vertexes[i]] = 'white';
        }
        //返回的数组有很多属性,这些属性的名称为遍历的顶点名,这些属性的值都是white
        return colors;
    }
    //实现广度优先搜索(BFS)
    //handler为呈现出来的函数
    Graph.prototype.bfs = function (initV, handler) {
        //初始化颜色,把图的所有顶点都变为白色的,colors是个数组,这个数组的属性值都是white
        var colors = this.initializeColor();
        //创建队列
        var queue = new Queue();
        //将顶点加入到队列中
        queue.enqueue(initV);
        //循环从队列中取出元素
        while (!queue.isempty()) {
            //将v的颜色设置为灰色
            colors[v] = 'grey';
            //从队列中取出一个顶点
            var v = queue.dequeue();
            //获取和顶点相连的另外顶点
            var vList = this.edges.get(v);
            //遍历所有的顶点并且加入到队列中
            for (var i = 0; i < vList.length; i++) {
                var e = vList[i];
                if (colors[e] == 'white') {
                    colors[e] = 'grey';
                    queue.enqueue(e);
                }
            }
            //将顶点设置为黑色
            colors[v] = 'black';
            //处理顶点
            handler(v);
        }
    }
    //深度优先探索(DFS)
    Graph.prototype.dfs = function (initV, handler) {
        //初始化颜色,白色
        var colors = this.initializeColor();
        //从某个顶点开始一次递归访问
        this.dfsVisit(initV, colors, handler);
    }
    //
    Graph.prototype.dfsVisit = function (v, colors, handler) {
        //将颜色变为灰色
        colors[v] = 'grey';
        //处理v顶点
        handler(v);
        //访问v相连的顶点
        var vList = this.edges.get(v);
        for (var i = 0; i < vList.length; i++) {
            var e = vList[i];
            if (colors[e] == 'white') {
                this.dfsVisit(e, colors, handler);
            }
        }
        //将v设置为黑色
        colors[v] = 'black';
    }
}
//测试代码
//创建图结构
var graph = new Graph();
//添加顶点
var myVertexes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
//不能用下面的代码直接添加顶点,理论上只是添加顶点没问题,但是少了addVertex方法为每个顶点加的边
// graph.vertexes = myVertexes;
for (var i = 0; i < myVertexes.length; i++) {
    graph.addVertex(myVertexes[i]);
}
//添加边
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');
//测试结果
alert(graph);
//测试bfs
var result = '';
graph.bfs(graph.vertexes[0], function (v) {
    result += v + ' ';
})
alert(result); //A B C D E F G H I 
//测试dfs
result = '';
graph.dfs(graph.vertexes[0], function (v) {
    result += v + ' ';
})
alert(result); //A B E I F C D G H 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值