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

八数码难题

题目描述

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'); //在当前状态的字符串中找到字符0int 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 bfs(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 << bfs(start);return 0;
}

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

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

相关文章

文心一言 VS 讯飞星火 VS chatgpt (210)-- 算法导论16.1 1题

一、根据递归式(16.2)为活动选择问题设计一个动态规划算法。算法应该按前文定义计算最大兼容活动集的大小 c[i,j]并生成最大集本身。假定输入的活动已按公式(16.1)排好序。比较你的算法和GREEDY-ACTIVITY-SELECTOR的运行时间。如何要写代码&#xff0c;请用go语言。 文心一言&…

excel统计分析——裂区设计

参考资料&#xff1a;生物统计学 裂区设计&#xff08;split-plot design&#xff09;是安排多因素试验的一种方法&#xff0c;裂区设计对因素的安排有主次之分&#xff0c;适用于安排对不同因素试验精度要求不一的试验。 裂区设计时&#xff0c;先按第一因素的处理数划分主区&…

独立站营销新纪元:AI与大数据塑造个性化体验的未来

随着全球互联网的深入发展和数字化转型的不断推进&#xff0c;作为品牌建设和市场营销的重要载体&#xff0c;独立站将迎来新的发展机遇。新技术的涌现&#xff0c;特别是人工智能和大数据等技术的广泛应用&#xff0c;为独立站带来了前所未有的机遇与挑战。本文Nox聚星将和大家…

ios xcode 15 PrivacyInfo.xcprivacy 隐私清单

1.需要升级mac os系统到13 兼容 xcode 15.1 2.升级mac os系统到14 兼容 xcode 15.3 3.选择 New File 4.直接搜索 privacy 能看到有个App Privacy 5.右击Add Row 7.直接选 Label Types 8.选中继续添加就能添加你的隐私清单了 苹果官网文档Describing data use in privacy man…

Windows安装SSH教程:进阶配置选项详解

当我们谈论在Windows上安装SSH时,我们通常会关注基本的安装步骤和客户端/服务器的设置。然而,SSH的配置远不止于此。在本文中,我们将深入探讨一些高级的SSH配置选项,这些选项可以帮助您更好地定制SSH体验,提高安全性和性能。 一、SSH配置文件的详解 在Windows上,SSH的配…

Java二阶知识点总结(四)常见算法的基本结构

一、二叉树的DFS&#xff08;深度优先算法&#xff09; 1、递归 基本递归&#xff08;以中序遍历为例&#xff09; public List<Integer> inorderTraversal(TreeNode root) {List<Integer> resultnew ArrayList<>();order(root,result);return result;}pub…

从零开始学HCIA之IPv6基础05

1、IPv6地址支持无状态自动配置方式&#xff0c;主机通过某种机制获取网络前缀信息&#xff0c;然后主机自己生成地址的接口标识部分。 2、路由器发现功能是IPv6地址自动配置功能的基础&#xff0c;主要通过两种消息实现。 &#xff08;1&#xff09; 路由器通告&#xff08;…

springboot247人事管理系统

人事管理系统的设计与实现 摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;问卷信息因为其管理内容繁杂&#xff0c;管理数量繁多导…

webpack5基础--09_处理其他资源

处理其他资源 开发中可能还存在一些其他资源&#xff0c;如音视频等&#xff0c;我们也一起处理了 1. 配置 const path require("path");module.exports {entry: "./src/main.js",output: {path: path.resolve(__dirname, "dist"),filename…

Redis基本使用和基础知识整理

Redis是做什么的&#xff1f; Redis是一个开源&#xff0c;内存存储的数据结构服务器&#xff0c;可用作数据库&#xff0c;高速缓存和消息队列。Redis将数据储存在内存当中 内存的特点 易失性&#xff08;在断电之后数据就没有了&#xff09;进行读取数据等IO操作的速度要比…

C语言进阶—表达式求值

隐式类型转换&#xff1a; C 的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。 为了获得这个精度&#xff0c;表达式中的字符和短整型操作数在使用之前被转换为普通整型&#xff0c;这种转换称为整型提升。 #include <stdio.h>int main() {char c 1;printf(…

Springboot 的几种配置文件形式

方式一&#xff1a;多个yml文件 步骤1&#xff1a;创建多个配置文件 application.yml #主配置文件 application-dev.yml #开发环境的配置 application-prod.yml #生产环境的配置 application-test.yml #测试环境的配置步骤2&#xff1a;applicaiton.yml中指定配置 在a…

代码随想录day13(1)栈与队列:用栈实现队列(leetcode232)

题目要求&#xff1a;使用栈实现push、pop、empty、peek&#xff08;返回队列首部元素&#xff09;。 思路&#xff1a;本题思路比较容易&#xff0c;即用两个栈模拟即可&#xff0c;pop时只需要先判断stackout栈是否为空&#xff0c;如果不空直接弹出&#xff0c;如果空就将s…

JVM双亲委派模型

Java虚拟机的双亲委派模型是一种类加载机制&#xff0c;用于确保Java类在被加载时的安全性和一致性。该模型通过将类加载请求委派给父类加载器来保证类加载的顺序和一致性。这个模型主要由以下几个关键点组成&#xff1a; 双亲委派&#xff1a; 当Java虚拟机收到加载类的请求时…

restTemplate请求接口转发(包含普通get,post请求和下载)

此代码包含普通get&#xff0c;post请求和下载&#xff0c;代码如下&#xff1a; Configuration public class RestTemplateConfig {Beanpublic RestTemplate restTemplate() {return new RestTemplate();}}Data public class CustomButtonRequestDto {ApiModelProperty(value…

PTA天梯 L2-001 紧急救援

作为一个城市的应急救援队伍的负责人&#xff0c;你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候&#xff0c;你的任务是带领你的…

组合逻辑电路(一)(加法器)

目录 组合逻辑电路的特点及功能描述 采用SSI的组合逻辑电路的分析与设计 采用SSI的组合逻辑电路的分析 采用SII的组合逻辑电路的设计 例 例 例 常用的MSI组合逻辑电路 半加器 全加器 加法器 串行进位加法器 例 例 超前进位加法器 组合逻辑电路的特点及功能描述 小规模集成…

原生微信小程序电影购票+后台管理系统[含文档]

简介 正文获取联系方式&#xff01;❤❤❤关于JAVA/小程序/安卓/PHP/Python/C#/ASP.NET/大数据/爬虫/数据可视化都可推荐选题和提供专业的指导和支持。 【毕设源码推荐项目】基于原生微信小程序电影购票系统 适用于计算机类毕业设计&#xff0c;本系统功能完整&#xff0c;适合…

TypeScript新手指南:何时使用.ts,何时转向.tsx?

在TypeScript中&#xff0c;.ts和.tsx文件扩展名代表两种不同的文件类型&#xff1a; .ts&#xff1a;这是一个标准的TypeScript文件&#xff0c;它可以包含TypeScript代码&#xff0c;该代码最终会被编译成JavaScript。.ts文件通常不包含JSX代码&#xff0c;JSX是一种JavaScri…

基础小白快速入门并行计算------>我们为什么要学习并行计算

什么是并行计算&#xff1f; 随着计算机的不断发展&#xff0c;我们处理的数据不断变多&#xff0c;需要更大得到处理能力、我们希望计算机有着更大更强壮的计算能力&#xff0c;由于cpu的运行能力有限&#xff0c;我们便想到了将多个cpu进行串联计算问题&#xff0c;这也就是我…