js实现八码数问题

js实现八码数问题

问题描述

​ 八数码问题,在3×3的方格棋盘上,摆放着1到8这八个数码,有1个方格是空的,用数字0代表。要求对空格执行空格左移、空格右移、空格上移和空格下移这四个操作使得棋盘从初始状态到目标状态。例如:

2 8 3

1 0 4

7 6 5

​ 初试状态

1 2 3

8 0 4

7 6 5

​ 目标状态

​ 请任选一种盲目搜索算法(深度优先搜索或宽度优先搜索)或任选一种启发式搜索方法(A 算法或A* 算法)编程求解八数码问题(初始状态任选),并对实验结果进行分析,得出合理的结论。

实验原理

实验环境:JavaScript编程语言

​ 算法实现原理:八码数问题(也称为滑块谜题)是一个经典的人工智能问题,目标是从一个初始状态通过有限步数达到目标状态。广度优先搜索(BFS)是一种逐层扩展搜索的算法,适用于求解最短路径或最少步数问题。在八码数问题中,BFS会探索所有可能的下一步状态,直到找到目标状态。

程序框图

在这里插入图片描述

实验步骤

  1. 定义数据结构和辅助函数
  • 定义状态表示方法(如数组)。

  • 实现检查两个状态是否相等的函数。

  • 实现生成下一个状态的函数。

  1. 实现BFS算法
  • 初始化队列和访问集合。
  • 将初始状态入队并标记为已访问。
  • 开始BFS循环,直到队列为空或找到目标状态。
  • 在每次循环中,出队当前状态,生成所有可能的下一个状态,并检查每个新状态。
  1. 测试和验证
  • 使用不同的初始状态和目标状态进行测试。
  • 验证算法的正确性和效率。

基本数据结构分析和实现

  • 状态表示:使用数组表示八码数状态,其中0表示空格。

  • 队列:用于存储待处理的状态。

  • 集合:用于存储已访问的状态,以避免重复处理。

  • 代码如下:

class Node {
    constructor(state, parent, action, depth) {
        this.state = state;
        this.parent = parent;
        this.action = action;
        this.depth = depth;
    }
}
 
function isGoal(state, goalState) {
    return state.join('') === goalState.join('');
}
 
function getStateIndex(state) {
    return state.join(',');
}
 
function getNeighbors(state) {
    const neighbors = [];
    const zeroIndex = state.indexOf(0);
    const x = Math.floor(zeroIndex / 3);
    const y = zeroIndex % 3;
    const directions = [
        { dx: -1, dy: 0 }, { dx: 1, dy: 0 },
        { dx: 0, dy: -1 }, { dx: 0, dy: 1 }
    ];
 
    for (const { dx, dy } of directions) {
        const newX = x + dx;
        const newY = y + dy;
        if (newX >= 0 && newX < 3 && newY >= 0 && newY < 3) {
            const newIndex = newX * 3 + newY;
            const newState = [...state];
            [newState[zeroIndex], newState[newIndex]] = [newState[newIndex], newState[zeroIndex]];
            neighbors.push(newState);
        }
    }
    return neighbors;
}

编写程序的各个子模块

模块1:初始化

  • 建立时间:实验开始时。
  • 功能:初始化队列、访问集合和初始状态。
  • 输入参数:初始状态和目标状态。
  • 输出参数:无。
  • 与其它模块联系:调用生成节点和BFS算法模块。
function initialize(initialState, goalState) {
    const startNode = new Node(initialState, null, null, 0);
    const queue = [startNode];
    const visited = new Set([getStateIndex(initialState)]);
    return { queue, visited, goalState };
}

模块2:BFS算法

  • 建立时间:实验进行时。
  • 功能:执行广度优先搜索。
  • 输入参数:初始化模块的输出。
  • 输出参数:解路径或失败信息。
  • 与其它模块联系:调用检查目标和生成邻居模块。
function bfs(initialState, goalState) {
    const { queue, visited, goalState } = initialize(initialState, goalState);
 
    while (queue.length > 0) {
        const currentNode = queue.shift();
 
        if (isGoal(currentNode.state, goalState)) {
            return constructPath(currentNode);
        }
 
        const neighbors = getNeighbors(currentNode.state);
        for (const neighbor of neighbors) {
            const neighborIndex = getStateIndex(neighbor);
            if (!visited.has(neighborIndex)) {
                const newNode = new Node(neighbor, currentNode, null, currentNode.depth + 1);
                queue.push(newNode);
                visited.add(neighborIndex);
            }
        }
    }
    return null; // 无解
}

模块3:构造路径

  • 建立时间:找到目标状态时。
  • 功能:从目标节点回溯构造解路径。
  • 输入参数:目标节点。
  • 输出参数:解路径。
  • 与其它模块联系:被BFS算法模块调用。
function constructPath(node) {
    const path = [];
    while (node !== null) {
        path.unshift(node.state);
        node = node.parent;
    }
    return path;
}

完整源码

 class Node {
        constructor(state, parent, action, depth) {
          //设置节点的状态,state 是一个数组,表示当前的状态
          this.state = state;
          //设置节点的父节点,parent 是另一个 Node 实例,表示当前节点是从哪个节点扩展而来的
          this.parent = parent;
          //设置导致当前状态的动作或移动,action 可以是任何描述动作的对象或值
          this.action = action;
          //设置节点的深度,depth 是一个整数,表示从初始节点到当前节点的步数
          this.depth = depth;
        }
      }

      //判断是否是目标状态
      function isGoal(state, goalState) {
        // 将 state 和 goalState 数组转换为字符串,并比较它们是否相等。如果相等,说明当前状态是目标状态。
        return state.join("") === goalState.join("");
      }

      // 将 state 数组的元素用逗号连接成一个字符串,作为该状态的唯一索引或标识。
      function getStateIndex(state) {
        return state.join(",");
      }
      //获取当前节点的邻居节点
      function getNeighbors(state) {
        // 初始化一个空数组,用于存储当前状态的所有邻居状态(即可以通过一步移动到达的状态)
        const neighbors = [];
        // 找到状态数组中值为 0 的元素的索引
        const zeroIndex = state.indexOf(0);
        // 计算 0 所在位置的行、列坐标
        const x = Math.floor(zeroIndex / 3);
        const y = zeroIndex % 3;
        // 定义一个包含四个方向的数组,每个方向都是一个对象,表示在网格中可以移动的方向
        const directions = [
          //上
          { dx: -1, dy: 0 },
          //下
          { dx: 1, dy: 0 },
          //左
          { dx: 0, dy: -1 },
          //右
          { dx: 0, dy: 1 }
        ];
        // 遍历每个方向
        for (const { dx, dy } of directions) {
          // 计算新位置的行、列坐标
          const newX = x + dx;
          const newY = y + dy;
          // 检查新位置是否在网格内
          if (newX >= 0 && newX < 3 && newY >= 0 && newY < 3) {
            const newIndex = newX * 3 + newY;
            const newState = [...state];
            // 交换 0 和新位置上的元素,生成新的状态
            [newState[zeroIndex], newState[newIndex]] = [newState[newIndex], newState[zeroIndex]];
            // 将新的状态添加到邻居数组中
            neighbors.push(newState);
          }
        }
        // 返回包含所有邻居状态的数组
        return neighbors;
      }
      //初始化
      function init(initialState, goalState) {
        const startNode = new Node(initialState, null, null, 0);
        const queue = [startNode];
         创建一个Set集合,用于存储已经访问过的状态
        const visited = new Set([getStateIndex(initialState)]);
        // 返回一个对象,该对象包含队列、已访问集合和目标状态
        return { queue, visited, goalState };
      }
      //算法:广度优先搜索
      function bfs(start, goal) {
         调用init函数来初始化搜索所需的数据结构,包括队列、已访问集合和目标状态
        const { queue, visited, goalState } = init(start, goal);
        // 当队列不为空时,继续搜索
        while (queue.length > 0) {
          // 从队列头部取出一个节点作为当前节点
          const currentNode = queue.shift();
          // 检查当前节点的状态是否为目标状态
          if (isGoal(currentNode.state, goal)) {
            // 如果是目标状态,构造该路径,并返回该路径
            return constructPath(currentNode);
          }
          // 获取当前节点状态的所有邻居状态
          const neighbors = getNeighbors(currentNode.state);
          //遍历每一个邻居状态
          for (const neighbor of neighbors) {
            const neighborIndex = getStateIndex(neighbor);
            // 检查邻居状态是否已经被访问过
            if (!visited.has(neighborIndex)) {
              // 如果邻居状态未被访问过,则创建一个新的节点来表示该邻居状态
              // 新节点的状态为邻居状态,父节点为当前节点,动作为null(因为这里没有具体动作信息),深度为当前节点深度加1
              const newNode = new Node(neighbor, currentNode, null, currentNode.depth + 1);
              // 将新节点加入队列,以便后续访问
              queue.push(newNode);
              //标记为已访问
              visited.add(neighborIndex);
            }
          }
        }
        return null; // 无解
      }
      //构造路径
      function constructPath(node) {
        // 初始化一个空数组path,用于存储从初始状态到目标状态的路径
        const path = [];
        // 当节点不为null时,继续循环
        while (node !== null) {
          // 将当前节点的状态添加到path数组的开头 从目标状态开始,回溯到初始状态,所以路径应该是反向的
          path.unshift(node.state);
          // 将当前节点设置为它的父节点   这样我们就可以继续回溯到上一个状态
          node = node.parent;
        }
        return path;
      }

      //转换为3*3输出
      function changeFn(step) {
        for (let i = 0; i < 3; i++) {
          let row = "";
          for (let j = 0; j < 3; j++) {
            const index = i * 3 + j;
            row += "  " + step[index] + "  ";
          }
          console.log(row);
        }
      }
      //输出
      function outPutStep(start, goal) {
        const result = bfs(start, goal);
        if (result) {
          console.log("一共走了", result.length - 1, "步");
          result.forEach((step, index) => {
            if (index == 0) {
              console.log("初始状态为:");
              changeFn(step);
            } else {
              console.log("====================第" + index + "步");
              changeFn(step);
            }
          });
        }
      }
      // 案例
      const start = [2, 8, 3, 1, 0, 4, 7, 6, 5];
      const goal = [1, 2, 3, 8, 0, 4, 7, 6, 5];
      outPutStep(start, goal);

运行结果

初始状态

[1, 2, 3,
 4, 0, 5,
 6, 7, 8]

目标状态

[1, 2, 3,
 4, 5, 6,
 7, 8, 0]

运行结果

在这里插入图片描述

实验分析与讨论

  • 算法效率:BFS算法在解决八码数问题时,由于需要探索所有可能的下一步状态,因此空间和时间复杂度都较高。对于较复杂的初始状态,可能需要较长的运行时间和较大的存储空间。
  • 解的存在性:BFS算法能够保证找到最短路径,但如果初始状态与目标状态之间不存在解路径,算法将遍历所有可能的状态,这在实际应用中可能不可行。
  • 优化方向:可以考虑使用启发式搜索算法(如A*算法)来优化搜索过程,通过引入评估函数来指导搜索方向,减少不必要的探索。

结论

​ 本实验使用JavaScript实现了广度优先搜索(BFS)算法来解决八码数问题。通过定义适当的数据结构和辅助函数,以及实现BFS算法和路径构造函数,成功找 到了从初始状态到目标状态的解路径。实验结果表明,BFS算法能够正确解决八码数问题,但存在效率较低的问题。未来可以考虑引入启发式搜索算法来优化搜索过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值