【人工智能实验】A*算法求解8数码问题 golang

news2025/1/22 9:17:41

人工智能经典问题八数码求解

实际上是将求解转为寻找最优节点的问题,算法流程如下:

  1. 求非0元素的逆序数的和,判断是否有解
  2. 将开始状态放到节点集,并设置访问标识位为true
  3. 从节点集中取出h(x)+g(x)最小的节点
  4. 判断取出的节点的状态是不是最终状态,如果是的话则回溯打印
  5. 找出取出的节点的状态中的0的位置
  6. 对取出的节点进行move操作,包含up down left right
  7. 如果move后的状态的访问标识位为false,则添加。否则什么都不做

需要注意:节点的数据结构如下

  • 状态:int数组
  • h(x):当前节点的状态到目标状态的距离
  • g(x):当前节点的状态到初始状态的距离
  • 动作:到当前节点所进行的move类型
  • 父节点:记录上一个状态,方便回溯打印

使用go语言实现如下

  • main.go

    package main
    
    import (
    	"container/heap"
    	"github.com/gookit/color"
    	"log"
    	"os"
    	"os/signal"
    	"syscall"
    )
    
    var (
    	start  = []int{2, 8, 3, 1, 6, 4, 7, 0, 5}
    	target = []int{1, 2, 3, 8, 0, 4, 7, 6, 5}
    )
    var (
    	movables    = []string{"up", "down", "left", "right"}
    	moveOffsets = map[string]int{"up": -3, "down": 3, "left": -1, "right": 1}
    )
    var (
    	visited = make(map[string]bool)
    )
    
    func main() {
    	color.BgCyan.Println("Y02114562")
    	printFun := func(list []int) {
    		for _, i := range list {
    			color.BgLightCyan.Print(i, ",")
    		}
    		color.BgLightCyan.Print("\n")
    	}
    	printFun(start)
    	printFun(target)
    	if reverseSum(start) != reverseSum(target) {
    		log.Fatal("不可解")
    	}
    	path, steps := solve(start)
    	if steps == -1 {
    		log.Fatal("No solution")
    	}
    	color.BgGreen.Println("只需:", steps, "步")
    	color.BgGreen.Println("操作:", path)
    
    	quit := make(chan os.Signal, 1)
    	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    	<-quit
    }
    
    // 启发函数:h(x) 从当前状态到目标的距离
    func manhattanDistance(state []int) int {
    	distance := 0
    
    	for i := 0; i < 9; i++ {
    		if state[i] != 0 {
    			row1, col1 := i/3, i%3
    			// 遍历所有不为0的点,计算他与他的目标位置的曼哈顿距离
    			for j := 0; j < 9; j++ {
    				if state[i] == target[j] {
    					row2, col2 := j/3, j%3
    					distance += abs(row1-row2) + abs(col1-col2)
    					break
    				}
    			}
    		}
    	}
    	return distance
    }
    
    // 启发式搜索:八数码问题求解
    func solve(start []int) ([]string, int) {
    	// 创建起始节点
    	startNode := &Node{
    		State:     start,
    		Heuristic: manhattanDistance(start),
    		G:         0,
    		PrevMove:  "",
    		PrevNode:  nil,
    	}
    
    	// 创建优先队列
    	pq := make(PriorityQueue, 0)
    	heap.Init(&pq)
    	heap.Push(&pq, startNode)
    
    	visited[listToString(startNode.State)] = true
    
    	// A*搜索
    	for pq.Len() > 0 {
    		currentNode := heap.Pop(&pq).(*Node)
    
    		// 到达目标状态,返回路径
    		if listToString(currentNode.State) == listToString(target) {
    			path := make([]string, 0)
    			for currentNode.PrevNode != nil {
    				path = append(path, currentNode.PrevMove)
    				currentNode = currentNode.PrevNode
    			}
    			return func(slice []string) ([]string, int) {
    				for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
    					slice[i], slice[j] = slice[j], slice[i]
    				}
    				return slice, len(path)
    			}(path)
    		}
    
    		zeroIndex := func(state []int) int {
    			for i, num := range state {
    				if num == 0 {
    					return i
    				}
    			}
    			return -1
    		}(currentNode.State)
    		for _, move := range movables {
    			if canMove(move, zeroIndex) {
    				newState := make([]int, len(currentNode.State))
    				copy(newState, currentNode.State)
    
    				newZeroIndex := zeroIndex + moveOffsets[move]
    				newState[zeroIndex], newState[newZeroIndex] = newState[newZeroIndex], newState[zeroIndex]
    
    				// 创建新节点
    				newNode := &Node{
    					State:     newState,
    					Heuristic: manhattanDistance(newState),
    					G:         currentNode.G + 1,
    					PrevMove:  move,
    					PrevNode:  currentNode,
    				}
    
    				// 如果新状态未被访问,则加入优先队列和已访问集合
    					if !visited[listToString(newState)] {
    						heap.Push(&pq, newNode)
    						visited[listToString(newState)] = true
    					}
    			}
    		}
    	}
    
    	// 没有找到解
    	return nil, -1
    }
    
  • node.go

    package main
    
    // Node 节点结构体
    type Node struct {
    	State     []int  // 当前状态
    	Heuristic int    // 启发函数值
    	G         int    // 初始节点到当前节点
    	PrevMove  string // 上一步移动的方向
    	PrevNode  *Node  // 上一步的节点
    }
    
    // PriorityQueue 优先队列
    type PriorityQueue []*Node
    
    // Len 优先队列的方法:计算长度
    func (pq PriorityQueue) Len() int {
    	return len(pq)
    }
    
    // Less 优先队列的方法:比较优先级
    func (pq PriorityQueue) Less(i, j int) bool {
    	return pq[i].Heuristic+pq[i].G < pq[j].Heuristic+pq[j].G
    }
    
    // Swap 优先队列的方法:交换元素
    func (pq PriorityQueue) Swap(i, j int) {
    	pq[i], pq[j] = pq[j], pq[i]
    }
    
    // Push 优先队列的方法:向队列中插入元素
    func (pq *PriorityQueue) Push(x interface{}) {
    	node := x.(*Node)
    	*pq = append(*pq, node)
    }
    
    // Pop 优先队列的方法:从队列中弹出元素
    func (pq *PriorityQueue) Pop() interface{} {
    	old := *pq
    	n := len(old)
    	node := old[n-1]
    	*pq = old[0 : n-1]
    	return node
    }
    
  • tool.go

    package main
    
    import "fmt"
    
    // 辅助函数:判断是否可移动
    func canMove(move string, zeroIndex int) bool {
    	if move == "up" && zeroIndex >= 3 {
    		return true
    	}
    	if move == "down" && zeroIndex <= 5 {
    		return true
    	}
    	if move == "left" && zeroIndex%3 != 0 {
    		return true
    	}
    	if move == "right" && zeroIndex%3 != 2 {
    		return true
    	}
    	return false
    }
    
    // 辅助函数:将[]int转换为字符串
    func listToString(state []int) string {
    	str := ""
    	for _, num := range state {
    		str += fmt.Sprintf("%d", num)
    	}
    	return str
    }
    
    // 辅助函数:求除了0之外的逆序和
    func reverseSum(arr []int) bool {
    	sum := 0
    
    	for i := 1; i < len(arr); i++ {
    		if arr[i] != 0 {
    			for j := 0; j < i; j++ {
    				if arr[j] > arr[i] {
    					sum++
    				}
    			}
    		}
    	}
    
    	return sum%2 != 0
    }
    
    // 辅助函数:计算绝对值
    func abs(num int) int {
    	if num < 0 {
    		return -num
    	}
    	return num
    }
    

运行效果

Y02114562

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

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

相关文章

基于springboot实现医患档案管理系统项目【项目源码】计算机毕业设计

基于springboot实现医患档案管理系统演示 Java语言简介 Java是由SUN公司推出&#xff0c;该公司于2010年被oracle公司收购。Java本是印度尼西亚的一个叫做爪洼岛的英文名称&#xff0c;也因此得来java是一杯正冒着热气咖啡的标识。Java语言在移动互联网的大背景下具备了显著的…

03.智慧商城——封装请求模块、登录静态页面、图形验证码

01. 登录页静态布局 (1) 准备工作 新建 styles/common.less 重置默认样式 // 重置默认样式 * {margin: 0;padding: 0;box-sizing: border-box; }// 文字溢出省略号 .text-ellipsis-2 {overflow: hidden;-webkit-line-clamp: 2;text-overflow: ellipsis;display: -webkit-box…

【Spring】超详细讲解AOP(面向切面编程)

文章目录 1. 前言2. 什么是AOP3. AOP快速入门4. AOP的核心概念5. 切点表达式6. 切点函数7. 通知8. 总结 1. 前言 本文围绕AOP进行讲解,AOP可以做什么,涉及到了哪些注解,以及各个注解运行的时机,以及Around相较于其它注解有什么不同,并且如果要执行目标方法需要怎么做 2. 什么…

Java实现简单的俄罗斯方块游戏

一、创建新项目 1.首先新建一个项目&#xff0c;并命名为俄罗斯方块。 2.其次新建一个类&#xff0c;命名为Main&#xff0c;或其他的。 二、运行代码 代码如下&#xff1a; package 俄罗斯方块;import java.awt.BorderLayout; import java.awt.Color; import java.awt.Gr…

C# 字节数组按照指定大小拆分保存至TXT文件

1.按照4个字节拆分为一行显示示例代码 byte[] result new byte[] {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08 };using (StreamWriter writer new StreamWriter("output.txt")){for (int i 0; i < result.Length; i 4) //按照四个字节拆分{byte[] tempArray n…

Android10 手势导航

种类 Android10 默认的系统导航有三种&#xff1a; 1.两个按钮的 2.三个按钮的 3.手势 它们分别对应三个包名 frameworks/base/packages/overlays/NavigationBarMode2ButtonOverlay frameworks/base/packages/overlays/NavigationBarMode3ButtonOverlay frameworks/base/packa…

【Python3】【力扣题】290. 单词规律

【力扣题】题目描述&#xff1a; 【Python3】代码&#xff1a; 1、解题思路&#xff1a;哈希。两个字典。分别记录字母对应的单词和单词对应的字母&#xff0c;若不是对应的&#xff0c;则返回False。 知识点&#xff1a;字符串.split()&#xff1a;将字符串按指定字符拆分成…

代码随想录算法训练营|五十三天

判断子序列 392. 判断子序列 - 力扣&#xff08;LeetCode&#xff09; 过程&#xff1a; public class Solution {public bool IsSubsequence(string s, string t) {int[,] dp new int[s.Length 1, t.Length 1];for (int i 1; i < s.Length; i) {for (int j 1; j <…

内置升压的单声道D类音频功率放大器:HT81293

HT81293是一款内置升压的单声道D类音频功率放大器&#xff0c;由锂电池供电时&#xff0c; THDN10%&#xff0c; 能连续输出18W功率&#xff08;4Ω负载&#xff09;。 HT81293A内置可动态调节的升压&#xff0c;可以提供一个适应不同输出功率的电压给D类功放&#xff0c;其可大…

1.mysql安装及基础

目录 概述安装上传jar包解压添加用户组和用户更改权限修改配置文件 my.cnf初始化登录mysql修改密码远程登录生效配置 sql语句分类数据定义语言 结束 概述 mysql安装及基础&#xff0c;后续涉及基础会继续补充。 安装 上传jar包 解压 tar -zxvf mysql-5.7.44-linux-glibc2.1…

六、Nacos快速入门

目录 一、服务注册到Nacos 二、nacos服务分级存储模型 1、作用 2、划分集群 3、根据权重负载均衡 三、环境隔离 1、在未设置namespace时&#xff0c;所有服务都默认在public 2、新建namespace 3、将order-service的namespace更改为dev 4、总结 一、服务注册到Nacos …

2019年五一杯数学建模C题科创板拟上市企业估值解题全过程文档及程序

2019年五一杯数学建模 C题 科创板拟上市企业估值 原题再现 科创板在首届中国国际进口博览会开幕式上宣布设立&#xff0c;是独立于现有主板市场的新设板块。设立科创板并试点注册制是提升服务科技创新企业能力、增强市场包容性、强化市场功能的一项资本市场重大改革举措&…

玩转样本量计算,全靠这个在线免费小工具

相信很多小伙伴都有过这样的经历&#xff1a;做科研设计、撰写论文&#xff0c;设计好主题后摆在眼前的是你最头痛的问题——样本量计算。事实上&#xff0c;样本量计算往往是临床医生做临床研究设计的一大障碍&#xff0c;是临床研究设计、临床知识经验以及统计学知识的结合。…

【机器学习】线性回归算法:原理、公式推导、损失函数、似然函数、梯度下降

1. 概念简述 线性回归是通过一个或多个自变量与因变量之间进行建模的回归分析&#xff0c;其特点为一个或多个称为回归系数的模型参数的线性组合。如下图所示&#xff0c;样本点为历史数据&#xff0c;回归曲线要能最贴切的模拟样本点的趋势&#xff0c;将误差降到最小。 2. 线…

第二证券:产业资本真金白银传递市场信心

本年以来&#xff0c;A股商场继续颤抖&#xff0c;但工业本钱纷繁行为&#xff0c;拿出大笔真金白银掀起增持回购潮。Wind数据闪现&#xff0c;到11月15日记者发稿&#xff0c;本年以来已有逾千家公司发布了股票回购预案&#xff0c;拟回购金额上限估计超1200亿元。同期&#x…

《洛谷深入浅出基础篇》P1551亲戚——集合——并查集P1551亲戚

上链接&#xff1a;P1551 亲戚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1551 上题干&#xff1a; 题目背景 若某个家族人员过于庞大&#xff0c;要判断两个是否是亲戚&#xff0c;确实还很不容易&#xff0c;现在给出某个亲戚关系图…

LINMP搭建wordpress-数据库不分离

目录 一、nginx部署 1.安装nginx前的系统依赖环境检查 2.下载nginx源代码包 3.解压缩源码包 4.创建普通的nginx用户 5.开始编译安装nginx服务 6.创建一个软连接以供集中管理 7.配置nginx环境变量 二、mysql 1.创建普通mysql用户 2.下载mysql二进制代码包 3.创建mys…

windows的远程桌面服务RDS存在弱加密证书的漏洞处理

背景 漏洞扫描检测windows服务器的远程桌面服务使用了弱加密的ssl证书 思路 按照报告描述&#xff0c;试图使用强加密的新证书更换默认证书 解决 生成证书 通过openssl1.1.1生成&#xff08;linux自带openssl&#xff0c;windows安装的是openssl1.1.1w&#xff09;&#x…

基于Vue+SpringBoot的农村物流配送系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2.1 快递信息管理&#xff1a;2.2.2 位置信息管理&#xff1a;2.2.3 配送人员分配&#xff1a;2.2.4 路线规划&#xff1a;2.2.5 个人中心&#xff1a;2.2.6 退换快递处理&#xff1a;…

智能电网短路故障接地故障模拟柜

智能电网短路故障接地故障模拟柜是一种用于模拟智能电网中短路故障和接地故障的设备&#xff0c;它可以模拟电网中的各种故障情况&#xff0c;帮助电力工程师进行故障诊断和维修。智能电网中的短路故障是指电路中出现了异常的电流路径&#xff0c;导致电流过大&#xff0c;可能…