资讯专栏INFORMATION COLUMN

从零到一:用深度优先算法检测有向图的环路(应用场景:性格测试)

nevermind / 575人阅读

摘要:小结使用深度优先算法,我们能够检测性格测试游戏的逻辑正确性,相比以往课堂上的理论,在这里算是一个具体的应用场景吧。其实深度优先算法的应用面也很广,迟早还会再碰面的。

写在前面

在开始前想先说一下关于这个课题的感想——能学以致用是一件很快乐的事情。

深度优先算法(简称DFS),在大学的数据结构课本中有这一个章节,依稀记得另外一个叫广度优先算法(简称BFS),在当时的我看来,它们都还只是理论。万万没想到的是,在毕业后的两年,我会接触到它们,并写下关于这个算法的应用文章,而契机是一个跟性格测试有关的游戏。

这个系列文章的重点,是如何利用DFS算法来检测有向图的回路,而具体的应用场景,就是性格测试。相比于纯讲理论,我更喜欢从实际应用出发,如果你对此感兴趣,就请继续看下去吧。

性格测试游戏

想必你肯定玩过问答类的性格测试游戏,游戏规则非常简单,按照心中所想回答问题即可。回答完一个问题后会跳转到另外一个问题,不同的回答可能进入不同的分支。回答完所有问题后会给出一个关于你性格的解答,如下图。

问题就来了,这种性格测试游戏的模型其实是一张有向图。一般而言,题目及答案都是作者设定好的,因此不会出现死循环,也就是环路。例如 1->2->4->1,就是一个死循环,玩家可能一直在第1、2、4这三道题一直循环,游戏不能结束。

如果游戏很复杂,有很多道题目,有可能会设计出死循环。那么像这种环路,我们能用程序检测出来吗?答案是肯定的。

下面先来POST一些概念。

什么是图?

摘自:百度百科 - 图

在数学中,一个图(Graph)是表示物件与物件之间的关系的数学对象,是图论的基本研究。

什么是有向图?

摘自:百度百科 - 图

如果给图的每条边规定一个方向,那么得到的图称为有向图。

什么是深度优先算法?

摘自:百度百科 - 深度优先搜索

深度优先搜索算法(Depth-First-Search),是搜索算法的一种。是沿着树或图的深度遍历节点,尽可能深的搜索分支。当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。

如上图,按DFS的方式以A为起点去遍历的话,遍历顺序为:

A-B-D-E-C-F-G

如果还有不明白的可以自行Google一下。

实践出真知
示例代码
/**
 * 测试数据,1代表第一题,2代表第二题,-1代表结果A,-2代表结果B,以此类推
 * @type {Array}
 */
var testData = [
    [2, 3],
    [4, -3],
    [-1, -2],
    [1, -2]
];

/**
 * 递归测试,使用深度优先算法
 * @param  {Array}  data   测试数据
 * @param  {Number} qIndex 问题下标
 * @param  {Number} aIndex 答案下标
 * @param  {Array}  path   当前回答路径,例如[1,2,4]代表1->2->4的回答顺序
 */
function recurseTest(data, qIndex, aIndex, path) {
    var question = data[qIndex]; // 当前问题
    var answer = question[aIndex]; // 要遍历的答案
    // 1.判断是否跳转到结果
    if (answer > 0) { // 跳转到其他问题
        if (path.indexOf(answer) > -1) { // 逻辑错误,当前回答路径已存在,死循环
            var result = path.concat([answer, "wrong"]).join(", ");
            showResult(result);
        } else { // 逻辑正确,继续沿着这个答案遍历下去
            path.push(answer);
            recurseTest(data, answer - 1, 0, path);
        }
    } else { // 跳转到结果
        path.push(answer);
    }
    // 2.判断是否最后一个答案
    if (aIndex === question.length - 1) { // 已经是当前这道题的最后一个答案,返回上层
        var result = path.concat(["true"]).join(", ");
        showResult(result);
        path.pop();
    } else if (aIndex < question.length - 1) { // 还有其他答案,使用下一个答案遍历下去
        recurseTest(data, qIndex, aIndex + 1, path);
    }
}

/**
 * 显示回答结果
 * @param  {String} content 内容
 */
function showResult(content) {
    console.log(content);
    if (typeof document !== "undefined") {
        var div = document.createElement("div");
        div.innerText = content;
        document.body.appendChild(div);
    }
}

// 测试一下
showResult("测试结果:");
recurseTest(testData, 0, 0, [1]);
测试结果

在线示例

https://jsfiddle.net/Vincent_...

要点解读
1.栈的使用

上述代码中的数组path,应该理解成一个栈,它记录的是当前递归的回答顺序,比如[1, 2, 4],代表着,先回答第一题,再回答第二题,再回答第四题。

2.环路的判断

假如下一个要移动到的问题的序号,存在于栈中,就代表出现了环路,例如[1, 2, 4, 1],此时代表出现了死循环。

3.返回上层,遍历下一条分支

这个时候就体现出栈的作用了,比如我们跑完了1->2->?的分支后,需要跑1->3->?的分支,即返回上层,则使2出栈,3入栈。

时间复杂度的延伸

DFS算法的时间复杂度是:O(b^m) (b-分支系数,m-图的最大深度)

因此可以看出如果分支系数越大(也就是每一题的答案越多),图深度越大(题目的数量越多),时间复杂度就越高。

为此,我们可以来看看运行这个检测的方法,花了多少时间,递归了多少次:

上面我们只有几个节点,每个节点只有2个出度,因此运算起来很快。如果增加到12个节点呢,每个节点4个出度呢?

没错,是两千多万次递归,时间也来到了接近300ms,越多的顶点和边将带来更多的检测时间,因此检测过多的顶点和边将带来性能问题,这是使用深度优先算法来检测的时候需要注意的。(之前就是因为一个游戏配了20道题,运行一下这个检测方法,直接跑到崩溃。。。)

小结

使用深度优先算法,我们能够检测性格测试游戏的逻辑正确性,相比以往课堂上的理论,在这里算是一个具体的应用场景吧。其实深度优先算法的应用面也很广,迟早还会再碰面的。

另一方面,我们讨论了DFS算法的时间复杂度,当图的顶点数增加到一定程度时,运算量暴涨,也因此抛出了一个性能的问题。在看似简单的实现中,我们其实要注意处理好细节,毕竟,放大到1亿次运算,都不是小事!

最后,希望大家会喜欢这样的文章吧。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84764.html

相关文章

  • 资源依赖问题在 bowl 中一种解决方式

    摘要:多个异步任务的顺序执行通过方法,取得了一个描述加载顺序的二维数组。同时,二维数组的长度也是不定的,更不能穷举。利用这个特性,只需要遍历原二维数组,将每个放在一个中的函数中执行并返回即可因为的返回值就是一个,有一种惰性执行的感觉。 问题 bowl 是一个利用 local storage 进行静态资源缓存和加载的工具库,在开发过程中遇到过一些问题,其中比较典型的是加载多个资源的时候资源之间...

    Ilikewhite 评论0 收藏0
  • 从零到一Phaser.js写意地开发小游戏(Chapter 5 - 游戏大功告成)

    摘要:回顾上一节我们完成了游戏核心场景的大部分工作,能操控主角,能随机掉落苹果了。于是我们修改之前的方法,也就是接到苹果后的方法。接到炸弹后结束和苹果掉地上的调用方式是一样的。 showImg(https://segmentfault.com/img/bVNawu?w=900&h=500); 回顾 上一节我们完成了游戏核心场景play的大部分工作,能操控主角,能随机掉落苹果了。那么这一节我们...

    Jeff 评论0 收藏0

发表评论

0条评论

nevermind

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<