每周一算法:A*(A Star)算法

news2024/9/28 15:33:30

八数码难题

题目描述

3 × 3 3\times 3 3×3 的棋盘上,摆有八个棋子,每个棋子上标有 1 1 1 8 8 8 的某一数字。棋盘中留有一个空格,空格用 0 0 0 来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为 123804765 123804765 123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入格式

输入初始状态,一行九个数字,空格用 0 0 0 表示。

输出格式

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数。保证测试数据中无特殊无法到达目标状态数据。

样例 #1

样例输入 #1

283104765

样例输出 #1

4

提示

样例解释

图中标有 0 0 0 的是空格。绿色格子是空格所在位置,橙色格子是下一步可以移动到空格的位置。如图所示,用四步可以达到目标状态。

并且可以证明,不存在更优的策略。

广度优先搜索

算法思想

根据题目描述,输入一个棋盘的初始状态,求从初始状态到目标状态需要的最少移动次数,可以用广度优先搜索求解,基本思想如下:

  • 将初始状态 start \text{start} start的移动步数设为 0 0 0,然后将其加入队列
  • 只要队列不为空
    • 从队首取出一个状态 state \text{state} state
    • 如果 state = end \text{state}=\text{end} state=end结束状态,则搜索结束,返回到达的 state \text{state} state移动步数
    • 否则,找到 state \text{state} state中字符 0 0 0的位置,向相邻方向进行扩展
      • 如果扩展到一个新的状态,则计算扩展到新状态的步数,并将新状态加入队列

代码实现

#include <bits/stdc++.h>
using namespace std;
queue<string> q;
unordered_map<string,int> dis;

int dx[]={-1, 1, 0, 0}, dy[]={0, 0, -1, 1};

int bfs(string start)
{
    string end = "123804765";
    dis[start] = 0;
    q.push(start);
    while(!q.empty())
    {
        string state = q.front(); q.pop();
        int step = dis[state];
        if(state == end) return step;
        int k = state.find('0'); //在当前状态的字符串中找到字符0
        int x = k / 3, y = k % 3; //将字符串中的位置转换为矩阵中的坐标
        for(int i = 0; i < 4; i ++)
        {
            int a = x + dx[i], b = y + dy[i]; 
            if(a < 0 || a >= 3 || b < 0 || b >= 3) continue; //越界
            swap(state[k], state[a * 3 + b]); //将数字字符与0进行交换,转移到新状态
            if(!dis.count(state)) //没有访问过
            { 
                dis[state] = step + 1; //转移到state状态的最小步数
                q.push(state); //入队
            }
            swap(state[k], state[a * 3 + b]); //恢复现场,交换回来,为下次转移做准备
        }
    }
    return -1;
}

int main()
{
    string start;
    char c;
    for(int i = 0; i < 9; i ++)
    {
        cin >> c;
        start += c;
    }
    cout << bfs(start) << endl;
    return 0;
}

A*算法

通过BFS可以发现,对每个状态都可以将 0 0 0向上右下左四个方向进行扩展,在最坏情况下要搜索的状态空间为 4 9 4^9 49,指数级别,搜索的效率比较低。在这种情况下,可以使用A*算法进行求解。

A*(A Star)算法是一种很常用的路径查找和图形遍历算法,它有较好的性能和准确度。

A*算法通过下面的函数来计算每个状态的优先级:

f ( n ) = g ( n ) + h ( n ) f(n)=g(n) + h(n) f(n)=g(n)+h(n)
其中:

  • f ( n ) f(n) f(n)是当前状态 n n n综合优先级。当选择下一个要扩展的状态时,我们总会选取综合优先级最高(值最小)的状态。
  • g ( n ) g(n) g(n)是状态距离起点(初始状态)的代价
  • h ( n ) h(n) h(n)是状态 n n n距离终点(目标状态)的预计代价,这也就是A*算法的启发函数

算法思想

A*算法与BFS类似,不同之处在于A*算法使用优先队列,选取 f ( n ) f(n) f(n)值最小(优先级最高)的状态作为下一个待扩展的状态。基本思想如下:

  • 将初始状态 start \text{start} start的移动步数设为 0 0 0,然后其综合优先级初始状态加入优先队列
  • 只要队列不为空
    • 取出优先队列中综合优先级最高(值最小)的状态 state \text{state} state
    • 如果 state = end \text{state}=\text{end} state=end结束状态,则搜索结束,返回到达的 state \text{state} state移动步数
    • 否则,找到 state \text{state} state中字符 0 0 0的位置,向相邻方向进行扩展
      • 如果扩展到一个新状态,或者到达该状态的步数减少,将状态的综合优先级和状态本身继续加入优先队列

启发函数

从算法的基本思想可以看出来,启发函数会影响A*算法的行为。

  • 在极端情况下,当启发函数 h ( n ) h(n) h(n)为0时,则将由 g ( n ) g(n) g(n)决定状态的优先级,此时算法就退化成了Dijkstra算法。
  • 如果 h ( n ) h(n) h(n)始终小于等于状态 n n n到终点的代价,则A*算法保证一定能够找到最短路径。但是当 h ( n ) h(n) h(n)的值越小,算法将遍历越多的状态,也就导致算法越慢。
  • 如果 h ( n ) h(n) h(n)完全等于状态 n n n到终点的代价,则A*算法将找到最佳路径,并且速度很快。可惜的是,并非所有场景下都能做到这一点。因为在没有达到终点之前,很难确切算出距离终点还有多远。
  • 如果 h ( n ) h(n) h(n)的值比状态 n n n到终点的代价要大,则A*算法不能保证找到最短路径,不过此时会很快。

通过调节启发函数我们可以控制算法的速度和精确度。因为在一些情况,可能未必需要最短路径,而是希望能够尽快找到一个路径即可,这也是A*算法比较灵活的地方。

对于网格形式的图,有以下这些启发函数可以使用:

  • 如果图形中只允许朝上下左右四个方向移动,则可以使用曼哈顿距离(Manhattan distance)。
  • 如果图形中允许朝八个方向移动,则可以使用对角距离。
  • 如果图形中允许朝任何方向移动,则可以使用欧几里得距离(Euclidean distance)。

代码实现

#include <iostream>
#include <algorithm>
#include <queue>
#include <unordered_map>
using namespace std;
typedef pair<int, string> PIS;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
//启发函数取当前状态中每个数字与其目标位置的曼哈顿距离之和
int h(string state)
{
    int res = 0;
    for(int i = 0; i < state.size(); i ++)
    {
        if(state[i] != '0')
        {
            int t = state[i] - '1';
            res += abs(i / 3 - t / 3) + abs(i % 3 - t % 3); //累加每个数字到其正确位置的曼哈顿距离
        }
    }
    return res;
}
int astar(string start)
{
    string end = "123804765";
    priority_queue<PIS, vector<PIS>, greater<PIS>> heap; //小顶堆
    unordered_map<string, int> dis; //记录到达每一种状态的步数
    //g(start)返回起点到终点的预估距离
    heap.push({0 + h(start), start});
    dis[start] = 0;
    while(heap.size())
    {
        PIS t = heap.top(); heap.pop();
        string state = t.second;
        if(state == end) break; //终点第一次出队,搜索结束
        int step = dis[state];
        int k = state.find('0'); //找到0所在位置
        int x = k / 3, y = k % 3; 
        for(int i = 0; i < 4; i ++)
        {
            int a = x + dx[i], b = y + dy[i];
            if(a < 0 || a >= 3 || b < 0 || b >= 3) continue;
            swap(state[x * 3 + y], state[a * 3 + b]); //将0和目标交换
            if(!dis.count(state) || dis[state] > step + 1) //如果扩展到一个新的状态,或者能够缩短到state的距离
            {
                dis[state] = step + 1;
                heap.push({dis[state] + h(state), state}); //将综合优先级和状态加入优先队列
            }
            swap(state[x * 3 + y], state[a * 3 + b]);//恢复现场
        }
    }
    return dis[end];
}
int main()
{
    char c;
    string start;
    for(int i = 0; i < 9; i ++)
    {
        cin >> c;
        start += c;
    }
    cout << astar(start);
    return 0;
}

相关练习

洛谷P2324:骑士精神

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

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

相关文章

微信小程序购物/超市/餐饮/酒店商城开发搭建过程和需求

1. 商城开发的基本框架 a. 用户界面&#xff08;Frontend&#xff09; 页面设计&#xff1a;包括首页、商品列表、商品详情、购物车、下单界面、用户中心等。交云设计&#xff1a;如何让用户操作更加流畅&#xff0c;包括搜索、筛选、排序等功能的实现。响应式设计&#xff1…

【JAVA重要知识 | 第六篇】Java集合类使用总结(List、Set、Map接口及常见实现类)以及常见面试题

文章目录 6.Java集合类使用总结6.1概览6.1.1集合接口类特性6.1.2List接口和Set接口的区别6.1.3简要介绍&#xff08;1&#xff09;List接口&#xff08;2&#xff09;Set接口&#xff08;3&#xff09;Map接口 6.2Collection接口6.3List接口6.3.1ArrayList6.3.2LinkedList—不常…

网络模块使用Hilt注入

retrofit的异步回调方法已经做了线程切换&#xff0c;切换到了主线程 <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android"><uses-permission android:name"andr…

Solidity 智能合约开发 - 基础:基础语法 基础数据类型、以及用法和示例

苏泽 大家好 这里是苏泽 一个钟爱区块链技术的后端开发者 本篇专栏 ←持续记录本人自学两年走过无数弯路的智能合约学习笔记和经验总结 如果喜欢拜托三连支持~ 本篇主要是做一个知识的整理和规划 作为一个类似文档的作用 更为简要和明了 具体的实现案例和用法 后续会陆续给出…

Milvus向量数据库检索

官方文档&#xff1a;https://milvus.io/docs/search.md   本节介绍如何使用 Milvus 搜索实体。   Milvus 中的向量相似度搜索会计算查询向量与具有指定相似度度量的集合中的向量之间的距离&#xff0c;并返回最相似的结果。您可以通过指定过滤标量字段或主键字段的布尔表达…

Redis及其数据类型和常用命令(一)

Redis 非关系型数据库&#xff0c;不需要使用sql语句对数据库进行操作&#xff0c;而是使用命令进行操作&#xff0c;在数据库存储时使用键值对进行存储&#xff0c;应用场景广泛&#xff0c;一般存储访问频率较高的数据。 一般关系型数据库&#xff08;使用sql语句进行操作的…

市场复盘总结 20240314

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 25% 最常用的…

二.递归及实例(汉诺塔问题)

目录 5.递归 6-递归实例:汉诺塔问题 思路: 详细过程: 代码: 5.递归 调用自身 结束条件 6-递归实例:汉诺塔问题 思路: 结果: 详细过程: 代码: #n为盘子的个数 a,b,c分别为3个地方. def hannuta(n,a,b,c): ​if n>0:hannuta(n-1,a,c,b) #将n-1个从a经过c移到到b(a…

【C语言_C语言语句_复习篇】

目录 一、C语言的语句有哪些 1.1 空语句 1.2 表达式语句 1.3 函数调用语句 1.4 复合语句 1.5 控制语句 二、分支语句&#xff08;两种&#xff09; 1.1 if语句 1.1.1 普通分支语句(if、if_else) 1.1.2 嵌套if语句 1.1.3 else嵌套if两种写法的比较 1.1.4 else悬空问题 1.1.…

代码随想录算法训练营第四十七天|动态规划|198.打家劫舍、213.打家劫舍II、337.打家劫舍III

198.打家劫舍 文章 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定一个代…

Docker容器化技术(使用Docker搭建论坛)

第一步&#xff1a;删除容器镜像文件 [rootlocalhost ~]# docker rm -f docker ps -aq b09ee6438986 e0fe8ebf3ba1第二步&#xff1a;使用docker拉取数据库 [rootlocalhost ~]# docker run -d --name db mysql:5.7 02a4e5bfffdc81cb6403985fe4cd6acb0c5fab0b19edf9f5b8274783…

NLP:HanLP的下载与使用

昨天说到要做一个自定义的训练模型&#xff0c;但是很快这个想法就被扑灭了&#xff0c;因为这个手工标记的成本太大&#xff0c;而且我的上级并不是想要我做这个场景&#xff0c;而是希望我通过这个场景展示出可以接下最终需求的能力。换句话来说&#xff1a;可以&#xff0c;…

leetcode代码记录(找到小镇的法官

目录 1. 题目&#xff1a;2. 我的代码&#xff1a;小结&#xff1a; 1. 题目&#xff1a; 小镇里有 n 个人&#xff0c;按从 1 到 n 的顺序编号。传言称&#xff0c;这些人中有一个暗地里是小镇法官。 如果小镇法官真的存在&#xff0c;那么&#xff1a; 小镇法官不会信任任何…

【数据结构】树与堆 (向上/下调整算法和复杂度的分析、堆排序以及topk问题)

文章目录 1.树的概念1.1树的相关概念1.2树的表示 2.二叉树2.1概念2.2特殊二叉树2.3二叉树的存储 3.堆3.1堆的插入&#xff08;向上调整&#xff09;3.2堆的删除&#xff08;向下调整&#xff09;3.3堆的创建3.3.1使用向上调整3.3.2使用向下调整3.3.3两种建堆方式的比较 3.4堆排…

新 树莓派4B 温湿度监测 基于debian12的树莓派OS

前言 本文旨在完成通过外接温湿度传感器至树莓派使得树莓派不断记录并存储温湿度数据 这个领域有很多文章&#xff0c;但是部分文章已经缺乏了时效性&#xff0c;在最新系统不适用&#xff0c;本文目前适用 硬件 硬件连接 温湿度传感器常选用DHT11和DHT22&#xff0c;淘宝…

MTK的flash_tool.exe中,“Format-Download”、“Firmware-Upgrade”和“Download”是三种不同的刷机模式

在MTK的flash_tool.exe中&#xff0c;“Format-Download”、“Firmware-Upgrade”和“Download”是三种不同的刷机模式。具体分析如下&#xff1a; Format-Download&#xff1a;这种模式会执行全擦除&#xff0c;即清除存储器中的所有数据&#xff0c;然后下载新的固件。这种方…

图片编辑器tui-image-editor

提示&#xff1a;图片编辑器tui-image-editor 文章目录 前言一、安装tui-image-editor二、新建components/ImageEditor.vue三、修改App.vue四、效果五、遇到问题 this.getResolve is not a function总结 前言 需求&#xff1a;图片编辑器tui-image-editor 一、安装tui-image-ed…

DB算法原理与构建

参考&#xff1a; https://aistudio.baidu.com/projectdetail/4483048 Real-Time Scene Text Detection with Differentiable Binarization 如何读论文-by 李沐 DB (Real-Time Scene Text Detection with Differentiable Binarization) 原理 DB是一个基于分割的文本检测算…

IDEA自定义Maven仓库

Maven 是一款广泛应用于 Java 开发的工具&#xff0c;其作用类似于一个全自动的 JAR 包管理器&#xff0c;能够方便地导入开发所需的相关 JAR 包。在使用 Maven 进行 Java 程序开发时&#xff0c;开发者能够极大地提高开发效率。以下是关于如何安装 Maven 以及在 IDEA 中配置自…

电脑远程桌面选项变成灰色没办法勾选怎么办?

有些人在使用Windows系统自带的远程桌面工具时&#xff0c;会发现系统属性远程桌面选项卡中勾选启用“允许远程连接到此计算机”。 导致此问题出现的原因主要是由于组策略或者注册表设置错误造成的。 修复远程桌面选项变灰的两种方法&#xff01; 方法一&#xff1a;设置本地组…