使用 javascript 在 n*m 网格中演示 BFS 广度优先搜索算法求最短路径

news2024/11/26 17:35:27

完整代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        #box1 table{
          border: 0px;
          border-collapse: collapse;
          cursor: pointer;
          background-color: gray;
      }

      #box1 th {
          border: 0px;
          border-collapse: collapse;
          cursor: pointer;
      }

      #box1 td{
          border: 1px solid #000;
          border-collapse: collapse;
          cursor: pointer;
          text-align: center;
          font-size: 4;
      }

      #box1{
          border: 0px;
      }

      .wall{
          background-color: black;
      }

      .startPoint{
          background-color: blue;
      }

      .targetPoint{
          background-color: blue;
      }

      .passed{
          background-color: yellow;
      }
    </style>
</head>
<body>
    单击灰色格子将其设置为障碍( 黑色 ),单击障碍清空障碍
    <div id="box1"></div>
    <button onclick="startSearch()">开始寻路</button>
    <button onclick="initRingWalls()">生成环状障碍</button>
    <button onclick="initPointWalls()">生成点状障碍</button>
</body>
<script type="text/javascript">

    var map_code_td = new Map();
    var row_count = 40;
    var col_count = 40;
    var rowNum_start = 1;
    var colNum_start = 1;
    var rowNum_target = 20;
    var colNum_target = 20;
    var map_code_wallYn = new Map();
    var code_start = rowNum_start + "_" + colNum_start;
    var code_target = rowNum_target + "_" + colNum_target;

    // 辅助帮助显示横竖纹的底色棋盘,没有任何事件和业务逻辑
    function initBox1( ) {
        var table = document.createElement("table");
        table.rules  = 'all' ;
        for (var rowNum = 1; rowNum <= row_count; rowNum++) {
            var tr = document.createElement("tr");
            for (var colNum = 1; colNum <= col_count; colNum++) {
                var td = document.createElement("td");
                td.width = 10;
                td.height = 10;
                td.className ="road";
                tr.appendChild(td);
                var code = rowNum + "_" + colNum;
                map_code_td.set(code, td);
            }
            table.appendChild(tr);
        }
        document.getElementById( "box1" ).appendChild(table);
        table.addEventListener( "click", setOrClearWall );

        var code_start = rowNum_start + "_" + colNum_start;
        var code_target = rowNum_target + "_" + colNum_target;

        map_code_td.get( code_start ).className = "startPoint";
        map_code_td.get( code_target ).className = "targetPoint";
    }

    function initPointWalls(){
         // 清空障碍
        clearAllWalls();

        for (var rowNum = 1; rowNum <= row_count; rowNum++) {
            for (var colNum = 1; colNum <= col_count; colNum++) {
                var code = rowNum + "_" + colNum;
                if( code == code_start || code == code_target ){
                    continue;
                }
                // 右 1/3 的几率成为障碍
                var randomNum = randomBetween(1,10000);
                if( randomNum < 3333  ){
                    var td = map_code_td.get( code );
                    td.className = "wall";
                    map_code_wallYn.set( code, 'y' );
                }
            }
        }
    }

    // 鼠标的点击事件监听方法,用来将点击的格子设置为墙
    function setOrClearWall(){
        var td = event.srcElement;
        var rowNum = td.parentElement.rowIndex + 1;
        var colNum = td.cellIndex + 1;
        var code = rowNum + "_" + colNum;
        if( td.className == "wall" ){
            td.className = "road";
            map_code_wallYn.set( code, "n" );
        }else if( td.className =='road' ){
            td.className = "wall";
            map_code_wallYn.set( code, "y" );
        }
    }

    function randomBetween(minNum,maxNum){
        return Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum;
    }

    function clearAllWalls(){
        for (var rowNum = 1; rowNum <= row_count; rowNum++) {
            for (var colNum = 1; colNum <= col_count; colNum++) {
                var code = rowNum + "_" + colNum;
                if( code == code_start  || code == code_target){
                    continue;
                }
                var wallYn = map_code_wallYn.get( code );
                if( wallYn == 'y' ){
                    map_code_td.get( code ).className = "";
                    map_code_wallYn.set( code, "n" );
                }else{
                    var td = map_code_td.get( code );
                    if( td.className == 'passed' ){
                        td.className = "";
                    }
                }
            }
        }
    }

    function initRingWalls(){
        // 清空障碍
        clearAllWalls();

        // 将第三行设置为障碍
        var circles = [2,4,6,8,10,12,14,16,18];
        for( var i in circles ){
            var circle = circles[i];
            var randomNum = randomBetween(1,4);
            var randomNum_1 = randomBetween(circle,col_count - circle);
            var randomNum_2 = randomBetween(circle,col_count - circle);
            var randomNum_3 = randomBetween(circle,row_count - circle);
            var randomNum_4 = randomBetween(circle,row_count - circle);
            // 顶行
            for (var colNum = circle; colNum <= ( col_count - circle );colNum++){
                if( randomNum == 1 ){
                    if( colNum == randomNum_1 ){
                        continue;
                    }
                }
                var code = circle + "_" + colNum;
                map_code_td.get( code ).className = "wall";
                map_code_wallYn.set( code, "y" );
            }

            // 底行
            for (var colNum = circle; colNum <= ( col_count - circle );colNum++){
                if( randomNum == 2 ){
                    if( colNum == randomNum_2 ){
                        continue;
                    }
                }
                var code = ( row_count - circle ) + "_" +  colNum;
                map_code_td.get( code ).className = "wall";
                map_code_wallYn.set( code, "y" );
            }

            // 左列
            for (var rowNum = circle; rowNum <= ( row_count - circle );rowNum++){
                if( randomNum == 3 ){
                    if( rowNum == randomNum_3 ){
                        continue;
                    }
                }
                var code = rowNum + "_" + circle;
                map_code_td.get( code ).className = "wall";
                map_code_wallYn.set( code, "y" );
            }

            // 右列
            for (var rowNum = circle; rowNum <= ( row_count - circle );rowNum++){
                if( randomNum == 4 ){
                    if( rowNum == randomNum_4 ){
                        continue;
                    }
                }
                var code = rowNum + "_" + ( col_count - circle );
                map_code_td.get( code ).className = "wall";
                map_code_wallYn.set( code, "y" );
            }
        }
    }

    // 检测 code 表示的格子是否可以通行
    function canPass( code ){
        var arr = code.split("_");
        var rowNum = parseInt( arr[0])
        var colNum = parseInt( arr[1] );
        if( rowNum > row_count || rowNum < 1 || colNum > col_count || colNum < 1 ){
            // 该位置已经超过边界了,不能走
            return false;
        }
        var wallYn = map_code_wallYn.get( code );
        if( wallYn == 'y' ){
            // 该位置是墙,不能走
            return false;
        }
        return true;
    }

    class Node {
        constructor(code,parent) {
            this.code = code;
            this.children = [];
            this.parent = parent;
        }

        addChild(node) {
            this.children.push(node);
        }
    }

    initBox1(  );

    function startSearch(  ){
        // todo
        var node_start = new Node( code_start,null )
        console.log( node_start)
        console.log( node_start.code)
        var nodes_currLevel = [node_start];
        var nodes_nextLevel = [];
        var codes_passed = [];
        var node_target = null;
        while( true ){
            // todo 如果找到了 目标节点或者 nodes_currLevel 为空了,需要退出循环
            // todo 遍历 nodes_currLevel 中的 每个 node,检测其是否是目标节点,如果是目标节点,则找到了最短路径,
            if( nodes_currLevel.length == 0 ){
                break;
            }
            for( var i in nodes_currLevel ){
                var node = nodes_currLevel[i];
                console.log( "node = ",node)
                var code = node.code;
                console.log( "code = " + code );
                if( codes_passed.indexOf( code ) > -1 ){
                    // 该节点已经遍历过了,不需要重复遍历
                    continue;
                }
                if( code == code_target ){
                    // 找到目标节点了
                    node_target = node;
                    break
                }
                // todo 找到该节点的所有可达子节点,保存到下一级的集合中,并将当前节点置为已遍历状态
                // todo 找到该节点的上、右、下、左节点( 需要排除超过边界的和是障碍物的 )
                var arr = code.split("_");
                var rowNum = parseInt( arr[0] );
                var colNum = parseInt( arr[1] );

                var code_up = ( rowNum - 1 ) + "_" + colNum;
                var code_right = rowNum + "_" + ( colNum + 1 );
                var code_down = ( rowNum + 1 ) + "_" + colNum;
                var code_left = rowNum + "_" + ( colNum - 1 );
                if( canPass( code_up ) ){
                    // 上子节点可通过
                    // 更新树结构
                    var node_up = new Node( code_up,node );
                    node.addChild( node_up );
                    nodes_nextLevel.push( node_up );
                }
                if( canPass( code_right ) ){
                    // 右子节点可通过
                    // 更新树结构
                    var node_right = new Node( code_right,node );
                    node.addChild( node_right );
                    nodes_nextLevel.push( node_right );
                }
                if( canPass( code_down ) ){
                    // 下子节点可通过
                    // 更新树结构
                    var node_down = new Node( code_down,node );
                    node.addChild( node_down );
                    nodes_nextLevel.push( node_down );
                }
                if( canPass( code_left ) ){
                    // 左子节点可通过
                    // 更新树结构
                    var node_left = new Node( code_left,node );
                    node.addChild( node_left );
                    nodes_nextLevel.push( node_left );
                }
                codes_passed.push( code );
            }
            // 该层遍历完了,更新 nodes_currLevel 和重置 nodes_nextLevel
            nodes_currLevel = nodes_nextLevel;
            nodes_nextLevel = [];
            if( node_target != null ){
                break;
            }
        }
        if( node_target != null ){
            console.log("找到最短路径了,node_target = ",node_target);
            // todo 画出最短路径
            printPath( node_target );
        }else{
            alert( "未找到最短路径" );
        }
    }

    function printPath( node_target ){
        if( node_target == null ){
            return;
        }
        var node_prev = node_target.parent;
        console.log("node_prev = ",node_prev);
        while( true ){
            if( node_prev == null ){
                break;
            }
            var code = node_prev.code;
            if( code == code_start ){
                break
            }
            var td = map_code_td.get( code );
            td.className = "passed";
            node_prev = node_prev.parent;
        }
    }
</script>
</html>

广度优先搜索算法求解最短路径的过程就是在构筑一棵多叉树,当找到表示目标节点的叶子节点时,显而易见该叶子节点到根节点( 开始节点 )的高度即是路径的长度,所以哪个叶子节点最先被发现是目标节点,显然该节点距离根节点的高度肯定是最短的,因为最先结束嘛,但是这也是广度优先搜索算法的局限性,就是要求任意两个连通节点之间的距离相等,如果这里A到B,A到C,B到D,C到F等的距离不是相等的,显然算法执行过程中最先搜到目标节点距离根节点的距离不一定是最短的,所以需要改进一下该算法,怎么改进呢?其实大名鼎鼎的 Dijkstra  迪杰斯特拉算法就是改进的广度优先搜索算法,不过迪杰斯特拉算法不是那么显而易见,即不是显而易见的傻瓜式的就能想到的,需要思考一下逻辑。其实我们可以换一种思维,在n*m网格中做文章,比如网格中的一个 code = "3_4"的格子( 即第3行第4列的格子 ),其距离为5 ,我们可以把它拆分成5个虚拟格子,code 分别为 "3_4_1"、 "3_4_2"、 "3_4_3"、 "3_4_4"、 "3_4_5",正常的一个格子,不考虑障碍物、边界、已经走完的格子,是有4个可达格子的,分别是其上、右、下、左边的格子,对于虚拟出的格子,它的可达格子只能是下一个虚拟格子( 当然对于最尾部的虚拟格子,它的可达格子和正常的格子是一样的,都是上右下左 ),如图:

a为起点,k为终点,f格子的距离为5,其余格子的距离都为1,带了虚拟格子的搜索树长如下这样:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1162341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

protected by SourceGuardian and requires a SourceGuardian loader ‘ixed.8解决方案

php相关问题 安装程序提示以下内容 遇到某些php程序的安装提示&#xff1a; PHP script ‘/www/wwwroot/zhengban.youyacao.com/install/index.php’ is protected by SourceGuardian and requires a SourceGuardian loader ‘ixed.8.1.lin’ to be installed. 1) Click her…

面试测试岗脑子里实在没货,简历(软件测试)咋写?

简历咋写&#xff0c;这是很多没有【软件测试实际工作经验】的同学们非常头疼的事情。 简历咋写&#xff1f;首先你要知道简历的作用。 简历的作用是啥呢&#xff1f; 一句话就是&#xff1a;让HR小姐姐约你。 如何让HR看你一眼&#xff0c;便相中你的简历&#xff0c;实现在众…

panabit日志审计singleuser_action.php任意用户添加漏洞复现

文章目录 panabit日志审计singleuser_action.php任意用户添加漏洞复现0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 panabit日志审计singleuser_action.php任意用户添加漏洞复现 0x01 前言 免责声明&#xff1a;请勿利用文章…

PADS Router的操作页面及鼠标指令介绍

PADS Router的用户界面由菜单栏&#xff0c;工作界面&#xff0c;一般工具栏&#xff0c;状态栏&#xff0c;项目浏览器组&#xff0c;输出窗口&#xff0c;电子表格组成&#xff08;图1&#xff09;&#xff1a; 图1 注意&#xff1a;如果你的界面没有显示项目浏览器&#xff…

亚马逊车充UL2089认证费用是多少,测试流程

熟悉亚马逊的都知道&#xff0c;电子电器产品在亚马逊上是一个比较受欢迎的品类&#xff0c;卖家也是比较多&#xff0c;随着电子电器产品的多样化&#xff0c;亚马逊对于产品的安全以及合规审核也越来越严格&#xff0c;近期很多车充品类的卖家遇到很多的问题就是亚马逊要求商…

el-input输入校验插件(正则表达式)

使用方法&#xff1a;在main.js文件中注册插件然后直接在<el-input>加入‘v-插件名’ (1)在main.js文件&#xff1a; // 只能输入数字指令 import onlyNumber from /directive/only-number; Vue.use(onlyNumber); &#xff08;2&#xff09;在src/directive文件夹中 &a…

window10 mysql8.0 修改端口port不生效

mysql的默认端口是3306&#xff0c;我想修改成3307。 查了一下资料&#xff0c;基本上都是说先进入C:\Program Files\MySQL\MySQL Server 8.0这个目录。 看看有没有my.ini&#xff0c;没有就新建。 我这里没有&#xff0c;就新建一个&#xff0c;然后修改port&#xff1a; […

中文编程从入门到精通,中文编程语言开发工具分享

中文编程从入门到精通&#xff0c;中文编程语言开发工具分享 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&#xff0c;向如图这个实例就…

商标服务展示预约小程序的效果如何

想要打造自己的品牌&#xff0c;商标是必要的一步&#xff0c;除了可以自己申请外&#xff0c;部分商家会选择通过第三方代理平台操作&#xff0c;在商城注册场景包括查询、资料提交、驳回复审等。 市场生意并不缺&#xff0c;对商标注册代理机构来说&#xff0c;需要不断拓客…

线程池--简单版本和复杂版本

目录 一、引言 二、线程池头文件介绍 三、简单版本线程池 1.创建线程池 2.添加任务到线程池 3.子线程执行回调函数 4.摧毁线程池 5.简单版线程池流程分析 四、复杂版本线程池 1.结构体介绍 2.主线程 3.子线程 4.管理线程 一、引言 多线程版服务器一个客户端就需要…

【算能】模型转换过程报错

报错信息&#xff1a; RuntimeError: [!Error]: tpuc-opt otv_batch1.mlir --chip-assign"chipbm1684" --chip-top-optimize --convert-top-to-tpu"modeF16 asymmetricTrue" --canonicalize -o otv_bm1684_f16_tpu.mlir报错问题&#xff1a; 1684的模型…

Java面向对象(进阶)-- 四种权限测试与方法的重写(override_overwrite)

文章目录 一、四种权限修饰二、测试四种权限修饰&#xff08;1&#xff09;准备&#xff08;2&#xff09;同一个类&#xff08;3&#xff09;同一个包&#xff08;4&#xff09;同一个包子类&#xff08;5&#xff09;不同包子类&#xff08;6&#xff09;跨包不是子类&#x…

shell基础篇:Bash特性和shell变量

shell基础篇 一、Bash特性bash基础特性关于历史记录的简单用法bash特性汇总 二、shell变量变量含义shell变量名规则定义shell变量变量替换/引⽤变量的作⽤域 一、Bash特性 bash基础特性 ● bash是一 个命令处理器&#xff0c;运行在文本窗口中&#xff0c;并能执行用户直接输…

PCB设计入门基础

PCB设计入门基础 PCB基本结构 copper foil 铜箔laminate 层压(或粘合)材料inner layer core 内层堆芯 PCB基本结构是一个三明治的结构&#xff0c;它的上层是一个铜层&#xff0c;底层也是一个铜层&#xff0c;中间层脚FR-4&#xff0c;FR-4是一层不导电的物质&#xff0c;叫做…

轻量封装WebGPU渲染系统示例<11>- WebGPU简单PBR效果(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/main/src/voxgpu/sample/SimplePBRTest.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 细节请见&#xff1a;引擎系统设计思路 - 用户态与系统态隔离-CSDN博客 2. 高频调用与低频调用隔离。 …

爆肝整理,Fiddler+Charles+Chrome开发者工具弱网测试总结,一篇概全...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Fiddler模拟弱…

​国网、首钢集团、施耐德、吉利等出席2023中国企业绿电国际峰会​

2023中国企业绿电国际峰会将通过邀请国家发展和改革委员会能源研究所、国网能源研究院、落基山研究所、首钢集团技术研究院、远景集团、施耐德电气、有色金属技术经济研究院有限责任公司、上海电气储能、博世、巴斯夫、晶科能源、吉利汽车等领域的企业高管&#xff0c;从绿色电…

跨境电商五大运营模式都有哪些?有何特点?

在跨境电商高速发展之下&#xff0c;跨境电商平台数量不断增加&#xff0c;各种跨境电商模式也不断逐渐暴露在人们的视野&#xff0c;下面小编就来为大家分析分析这些跨境电商都有哪些&#xff0c;它们的特点又是哪些&#xff0c;快来一起了解了解吧! 一、跨境电商五大运营模式…

第二证券:43家上市券商三季报拆解

受生意、投行、自营三大业务“滑坡”影响&#xff0c;本年三季度&#xff0c;上市券商的运营成果略有“退让”。 根据Wind计算数据&#xff0c;本年第三季度&#xff0c;43家上市券商估计完结运营收入1127.37亿元&#xff0c;同比下降9.7%&#xff1b;归母净流润280.51亿元&am…

办公必备神器:如何用AI快速生成年终总结PPT?

2023年已经步入尾声&#xff0c;今年的销售业绩如何&#xff1f;用户同比增长率是否达到预期&#xff1f;部门年度API完成情况&#xff1f;新开发的项目进展如何&#xff1f;品牌全球计划在各区域市场的部署进展&#xff1f;…… 每年年底&#xff0c;不论是纵横全球的大企业&…