人工智能 实验8 搜索技术:A*八数码,一字棋游戏

server/2024/11/29 8:21:25/

每日例行赊赞

一、实验目的

(1)掌握搜索技术的相关理论,能根据实际情况选取合适的搜索方法;

(2)进一步熟悉盲目搜索技术,掌握其在搜索过程中的优缺点;

(3)掌握启发式搜索的思想,能针对实际问题选取合适的评价函数;

(4)掌握问题归约的解决问题的思想,掌握与或图的搜索技术并能应用;

(5)深入理解博弈树搜索方法,并能应用于对弈类问题;

(6)根据自身情况,能选择合适的编程语言,实现启发式搜索、博弈树搜索方法、α-β剪枝算法,并能应用于实际AI问题。

二、实验内容

选择一种编程语言(最好为python或java),编程实现下面题目要求。

八数码难题

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格可用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局,找到一种启发式移动方法,实现从初始布局到目标布局的转变,并与实验7 的盲目移动方法对比。

1.需求分析

使用启发式移动方法解决八数码问题

2.数据结构、功能模块设计与说明

数据结构:类,小根堆,哈希表,数组

1A* 算法与估计函数设计

在二维坐标中,以实际距离 + 当前距离和终点的曼哈顿距离为启发值

给每个节点类【每个状态视为一个节点】加入一个字符串属性作为变成现在的样子的操作,然后之前做法的节点的操作数在堆排序换为【启发值】

而是否要入堆的依据则是利用哈希表,如果当前变化出来的字符串,已经在哈希表中存在,显然,它不应该入堆。因为它已经入堆一次

2)判断初始状态和目标状态有没有可行解

当数字序列的逆序对个数为偶数:有解,为奇数:无解

3.核心代码(不要将全部代码贴在报告上)与测试结果说明

 /*** 计算字符串中所有字符'x'与其位置对应的坐标点之间的曼哈顿距离之和。** @param s 输入的字符串* @return 字符串中所有字符'x'与其位置对应的坐标点之间的曼哈顿距离之和*/static int function(String s){int result = 0;for(int i = 0; i < s.length(); i++) {if(s.charAt(i) != 'x'){continue;}int x1 = i/3, y1 = i%3;int x2 = (s.charAt(i)-'1')/3, y2=(s.charAt(i)-'1')%3;result += Math.abs(x1-x2) + Math.abs(y1-y2);}return result;}
static String bfs(){// 小根堆PriorityQueue<Node> heap = new PriorityQueue<>((o1, o2)->o1.count-o2.count);heap.add(new Node(start, "", function(start)));visited.put(start, 0);// 定义方向数组和操作数组int [] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};String[] operations = {"up", "right", "down", "left"};// BFS 主循环while(!heap.isEmpty()) {Node node = heap.poll();// 如果找到目标状态,返回操作顺序if (node.state.equals(end)) { // 结束,返回操作顺序return node.path;}// 获取 'x' 的位置int index = node.state.indexOf('x'); // x的位置int Xstart = index / 3, Ystart = index % 3; // 计算出x的坐标// 遍历四个方向for (int i = 0; i < 4; i++) {char[] oldState = node.state.toCharArray();int Xend = Xstart + dx[i], Yend = Ystart + dy[i];String operate = operations[i];// 检查移动是否越界if (Xend < 0 || Xend >= 3 || Yend < 0 || Yend >= 3)continue;// 交换 'x' 和目标位置swap(oldState, index, Xend * 3 + Yend);// 转换字符数组为字符串String newState = String.valueOf(oldState);// 如果新状态未被访问过if (!visited.containsKey(newState)) {visited.put(newState, visited.get(node.state) + 1);// 将新状态加入堆中,并更新路径和计数heap.add(new Node(newState, node.path + operate + ' ', visited.get(node.state) + 1 + function(newState)));}}}// 如果无法到达终点,返回nullreturn null;
}
 //判断有无解,当数字序列的逆序对个数为偶数:有解,为奇数:无解String sequence = "";for(int i= 0; i < start.length(); i++) {if(start.charAt(i) != 'x') //等价于start[i]sequence += start.charAt(i);}int count = 0;for(int i=0; i < sequence.length(); i++) {for (int j = 0; j < sequence.length(); j++) {if((int) sequence.charAt(i) > (int) sequence.charAt(j))count++;}}if(count % 2 == 0){System.out.println("解存在");System.out.println(bfs());}else{System.out.println("解不存在");}

测试结果

完整代码:

import java.util.*;public class Astar8shuma {static String start = "2831647x5"; //初始状态static String end = "1238x4765"; //目标状态static HashMap<String, Integer> visited = new HashMap<>();static class Node{String state; //状态String path; //上下左右Integer count;//构造函数public Node(String state, String path, Integer count) {this.state = state;this.path = path;this.count = count;}}/*** 交换字符数组中指定位置的两个字符。*/static void swap(char[] a, int x, int y) {char temp = a[x];a[x] = a[y];a[y] = temp;}/*** 计算字符串中所有字符'x'与其位置对应的坐标点之间的曼哈顿距离之和。** @param s 输入的字符串* @return 字符串中所有字符'x'与其位置对应的坐标点之间的曼哈顿距离之和*/static int function(String s){int result = 0;for(int i = 0; i < s.length(); i++) {if(s.charAt(i) != 'x'){continue;}int x1 = i/3, y1 = i%3;int x2 = (s.charAt(i)-'1')/3, y2=(s.charAt(i)-'1')%3;result += Math.abs(x1-x2) + Math.abs(y1-y2);}return result;}static String bfs(){// 小根堆PriorityQueue<Node> heap = new PriorityQueue<>((o1, o2)->o1.count-o2.count);heap.add(new Node(start, "", function(start)));visited.put(start, 0);// 定义方向数组和操作数组int [] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};String[] operations = {"up", "right", "down", "left"};// BFS 主循环while(!heap.isEmpty()) {Node node = heap.poll();// 如果找到目标状态,返回操作顺序if (node.state.equals(end)) { // 结束,返回操作顺序return node.path;}// 获取 'x' 的位置int index = node.state.indexOf('x'); // x的位置int Xstart = index / 3, Ystart = index % 3; // 计算出x的坐标// 遍历四个方向for (int i = 0; i < 4; i++) {char[] oldState = node.state.toCharArray();int Xend = Xstart + dx[i], Yend = Ystart + dy[i];String operate = operations[i];// 检查移动是否越界if (Xend < 0 || Xend >= 3 || Yend < 0 || Yend >= 3)continue;// 交换 'x' 和目标位置swap(oldState, index, Xend * 3 + Yend);// 转换字符数组为字符串String newState = String.valueOf(oldState);// 如果新状态未被访问过if (!visited.containsKey(newState)) {visited.put(newState, visited.get(node.state) + 1);// 将新状态加入堆中,并更新路径和计数heap.add(new Node(newState, node.path + operate + ' ', visited.get(node.state) + 1 + function(newState)));}}}// 如果无法到达终点,返回nullreturn null;}public static void main(String[] args) {//判断有无解,当数字序列的逆序对个数为偶数:有解,为奇数:无解String sequence = "";for(int i= 0; i < start.length(); i++) {if(start.charAt(i) != 'x') //等价于start[i]sequence += start.charAt(i);}int count = 0;for(int i=0; i < sequence.length(); i++) {for (int j = 0; j < sequence.length(); j++) {if((int) sequence.charAt(i) > (int) sequence.charAt(j))count++;}}if(count % 2 == 0){System.out.println("解存在");System.out.println(bfs());}else{System.out.println("解不存在");}//System.out.println(bfs());}
}

编程实现一字棋游戏人机对弈,在九宫格棋盘上人机轮流在棋盘上摆各自的棋子,每次一枚,谁先取得三子一线结果的一方获胜。 (请用极小极大值搜索算法或α-β剪枝算法实现)

1. 需求分析

用极小极大值搜索算法实现一字棋游戏人机对弈

2. 数据结构、功能模块设计与说明

数据结构:Vector动态数组,数组

3. 评估函数 evaluate() 算法设计思路

  1. 判断胜负:
    • 首先调用 isWin() 函数来判断当前玩家是否获胜。
    • 如果当前玩家获胜 (isWin() 返回 1),则返回一个较高的分数(例如 -10 + countEmptyCells()),其中 countEmptyCells() 返回棋盘上剩余空格的数量。这里使用 -10 加上剩余空格数是为了在获胜时给予一定的奖励,同时考虑棋盘剩余空格对局势的影响(剩余空格越多,局势越开放,对获胜者来说可能意味着更多的优势,尽管获胜本身已经确定了主要得分)。
    • 如果对手获胜 (isWin() 返回 -1),则返回一个较低的分数(例如 10 - countEmptyCells()),同理,这里使用 10 减去剩余空格数是为了在对手获胜时给予一定的惩罚。
  2. 平局情况:如果当前既不是当前玩家获胜也不是对手获胜(isWin() 返回 0),则返回 0,表示当前棋盘状态是平局。

4. 极大极小值搜索算法 minimaxSearch(int depth, boolean isMaximizingPlayer) 算法设计思路:

  1. 判断胜负和终止条件:
    • 首先调用 isWin() 函数来判断当前玩家是否获胜。如果已经有玩家获胜,则直接调用 evaluate() 返回评估值,因为此时不需要继续搜索。
    • 如果当前没有位置可以下棋(positions 列表为空),则返回 0,表示平局。
  2. 初始化极值:根据当前玩家是最大化玩家 (isMaximizingPlayer 为 true) 还是最小化玩家 (false),分别初始化 value 为 Integer.MIN_VALUE 或 Integer.MAX_VALUE。这是为了在后续搜索中,最大化玩家寻找最大值,最小化玩家寻找最小值。
  3. 生成所有可能的下棋位置:遍历棋盘,找到所有空格位置,记录在一个 positions 列表中。
  4. 递归搜索:
    • 对每一个可能的下棋位置,假设当前玩家在当前位置下棋(更新棋盘状态)。
    • 递归调用 minimaxSearch() 函数,传入深度加 1 和当前玩家的对手作为参数(!isMaximizingPlayer)。
    • 递归返回后,撤销当前位置的棋子(恢复棋盘状态)。
  5. 更新极值:根据当前玩家是最大化玩家还是最小化玩家,分别更新 value 为当前搜索到的最大值或最小值。
  6. 返回极值:返回最终找到的极值,即当前玩家在当前深度下的最佳得分。

5. 核心代码(不要将全部代码贴在报告上)与测试结果说明

7、// 评估函数
static int evaluate() {int flag = isWin();if (flag != 0) {return flag == 1 ? -10 + countEmptyCells() : 10 - countEmptyCells();}return 0; // 平局?
}
8、// 极大极小值搜索算法
static int minimaxSearch(int depth, boolean isMaximizingPlayer) {int value;if (isWin() != 0) {return evaluate(); // 如果已经赢了,直接返回结果}if (isMaximizingPlayer) {value = Integer.MIN_VALUE;} else {value = Integer.MAX_VALUE;}Vector<Integer> positions = new Vector<>(); // 记录还有哪些位置可以下棋for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (board[i][j] == '_') {positions.add(i * 3 + j); // 记录位置}}}if (positions.isEmpty()) {return 0; // 如果已经没有位置可以下棋,直接返回平局}for (int pos : positions) {int x = pos / 3;int y = pos % 3;board[x][y] = isMaximizingPlayer ? 'O' : 'X'; // 下棋int score = minimaxSearch(depth + 1, !isMaximizingPlayer); // 递归搜索board[x][y] = '_'; // 取消下棋if (isMaximizingPlayer) {value = Math.max(value, score); // 取最大值} else {value = Math.min(value, score); // 取最小值}}return value; // 返回极值
}

测试结果

完整代码: 

import java.util.*;public class TicTacToeGame {static char[][] board = new char[3][3];static int player;// 判断胜负,返回1是玩家赢,-1是电脑赢,0是平局static int isWin() {// 判断行for (int i = 0; i < 3; i++) {if (board[i][0] != '_' && board[i][0] == board[i][1] && board[i][1] == board[i][2]) {return board[i][0] == 'X' ? 1 : -1;}}// 判断列for (int i = 0; i < 3; i++) {if (board[0][i] != '_' && board[0][i] == board[1][i] && board[1][i] == board[2][i]) {return board[0][i] == 'X' ? 1 : -1;}}// 判断对角线if ((board[0][0] != '_' && board[0][0] == board[1][1] && board[1][1] == board[2][2]) ||(board[0][2] != '_' && board[0][2] == board[1][1] && board[1][1] == board[2][0])) {return board[1][1] == 'X' ? 1 : -1;}// 判断是否平局for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (board[i][j] == '_') {return 0; // 还有空位,游戏继续}}}return 0; // 平局}// 评估函数static int evaluate() {int flag = isWin();if (flag != 0) {return flag == 1 ? -10 + countEmptyCells() : 10 - countEmptyCells();}return 0; // 平局?}// 计算空格数static int countEmptyCells() {int result = 0;for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (board[i][j] == '_') {result++;}}}return result;}// 极大极小值搜索算法static int minimaxSearch(int depth, boolean isMaximizingPlayer) {int value;if (isWin() != 0) {return evaluate(); // 如果已经赢了,直接返回结果}if (isMaximizingPlayer) {value = Integer.MIN_VALUE;} else {value = Integer.MAX_VALUE;}Vector<Integer> positions = new Vector<>(); // 记录还有哪些位置可以下棋for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (board[i][j] == '_') {positions.add(i * 3 + j); // 记录位置}}}if (positions.isEmpty()) {return 0; // 如果已经没有位置可以下棋,直接返回平局}for (int pos : positions) {int x = pos / 3;int y = pos % 3;board[x][y] = isMaximizingPlayer ? 'O' : 'X'; // 下棋int score = minimaxSearch(depth + 1, !isMaximizingPlayer); // 递归搜索board[x][y] = '_'; // 取消下棋if (isMaximizingPlayer) {value = Math.max(value, score); // 取最大值} else {value = Math.min(value, score); // 取最小值}}return value; // 返回极值}// 打印棋盘static void printBoard() {for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {System.out.print(board[i][j] + " ");}System.out.print("\n");}}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);while (true) {for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {board[i][j] = '_'; // 初始化棋盘}}do {System.out.println("请选择先手,玩家(0),电脑(1)");System.out.println("请输入:");player = scanner.nextInt();} while (player != 0 && player != 1);int i;for (i = 1; i <= 9; i++) {if (player == 1) { // 电脑先手,电脑下int bestScore = Integer.MIN_VALUE;int bestMove = -1;for (int pos = 0; pos < 9; pos++) {int x = pos / 3;int y = pos % 3;if (board[x][y] == '_') {board[x][y] = 'O'; // 下棋int score = minimaxSearch(0, false); // 极大极小值搜索board[x][y] = '_'; // 取消下棋if (score > bestScore) {bestScore = score; // 更新最佳分数bestMove = pos; // 更新最佳位置}}}board[bestMove / 3][bestMove % 3] = 'O'; // 下棋到最佳位置player = 0; // 玩家下棋} else { // 玩家下棋printBoard(); // 打印棋盘System.out.println("请输入下棋位置,从左上角编号0-8:");int x;do {x = scanner.nextInt(); // 获取用户输入的位置if (x < 0 || x > 8 || board[x / 3][x % 3] != '_') {System.out.println("这个位置不能下,请重新输入:"); // 输入验证失败提示} else {break; // 输入有效,退出循环}} while (true); // 确保输入有效位置board[x / 3][x % 3] = 'X'; // 下棋到指定位置player = 1; // 电脑下棋}int winner = isWin(); // 判断是否有人获胜if (winner != 0) { // 有人获胜,结束游戏printBoard(); // 打印最终棋盘状态if (winner == 1) {System.out.println("恭喜您获胜!\n"); // 玩家获胜提示} else if (winner == -1) {System.out.println("电脑获胜!\n"); // 电脑获胜提示}break; // 退出当前游戏循环,准备下一次游戏或退出程序}}if (i >= 9) { // 如果棋盘已满,平局结束游戏printBoard(); // 打印最终棋盘状态System.out.println("平局!\n"); // 平局提示}System.out.println("再玩一次?(y/n)"); // 询问是否再玩一次char option = scanner.next().charAt(0); // 获取用户输入的选择字符if (option != 'y') { // 如果用户选择不再玩,退出程序循环,结束程序break; // 退出程序循环,结束程序}}}
}

实验存在的问题与体会:

两个实验题都遇到了不少的问题:

第一题的困难点在于怎么将A*算法融合在bfs算法里面。最后却被验证有无解的逆序对给难住了,算法没问题,找了半天错误,后来关了重新打开就没事了。

体会:用时更少,可以避免多余的搜索

第二题的体会就很搞笑了,下棋要么是平局,要么是电脑赢。算法的问题导致了电脑有时候不太“聪明”,明明还有一步就赢了,偏要多走一步

参考资料:【人工智能】搜索技术(八数码 + 一字棋游戏)_人工智能一字棋-CSDN博客


http://www.ppmy.cn/server/145852.html

相关文章

ctfshow

1,web153 大小写绕过失败 使用.user.ini 来构造后⻔ php.ini是php的⼀个全局配置⽂件&#xff0c;对整个web服务起作⽤&#xff1b;⽽.user.ini和.htaccess⼀样是⽬录的配置⽂件&#xff0c;.user.ini就是⽤户⾃定义的⼀个php.ini&#xff0c;我们可以利⽤这个⽂件来构造后⻔和…

html+css+js打字游戏网页

1. 效果 2. html代码 <!doctype html> <html><head><meta charset"utf-8" /><title>打字练习</title><!--引入第三方动画库--><link rel"stylesheet" href"animate.css"><style>html {h…

Unity类银河战士恶魔城学习总结(P150 End Screen结束重启按钮)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址&#xff1a;https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了死亡后重新启动游戏&#xff0c;并且加入了游戏管理器 加入了重新开始游戏的按钮 GameManager.cs using System.Collection…

富文本编辑器图片上传并回显

1.概述 在代码业务需求中&#xff0c;我们会经常涉及到文件上传的功能&#xff0c;通常来说&#xff0c;我们存储文件是不能直接存储到数 据库中的&#xff0c;而是以文件路径存储到数据库中&#xff1b;但是存储文件的路径到数据库中又会有一定的问题&#xff0c;就是 浏览…

【论文阅读】Learning to Learn Task-Adaptive Hyperparameters for Few-Shot Learning

学习任务自适应超参数以实现小样本学习 引用&#xff1a;Baik, Sungyong, et al. “Learning to learn task-adaptive hyperparameters for few-shot learning.” IEEE Transactions on Pattern Analysis and Machine Intelligence 46.3 (2023): 1441-1454. 论文地址&#xff1…

Python爬虫爬取网页小说

分析 注意&#xff1a;不同小说url不同&#xff0c;不同小说需采用的正则也不同 1.安装requests包 pip install requests2.导入必要的库 re模块用于进行正则表达式相关的操作&#xff0c;比如使用正则表达式在获取到的网页文本内容中匹配提取特定格式的信息。 resquests模块用…

Python学习35天

# 定义父类 class Computer: CPUNone MemoryNone diskNone def __init__(self,CPU,Memory,disk): self.disk disk self.Memory Memory self.CPU CPU def get_details(self): return f"CPU:{self.CPU}\tdisk:{self.disk}\t…

C语言蓝桥杯组题目

系列文章目录 文章目录 系列文章目录前言题目第一题.1, 2, 3, 4 能组成多少个互不相同且无重复数字的三位数&#xff1f;都是多少&#xff1f;第二题: 一个整数&#xff0c;它加上100后是一个完全平方数&#xff0c;再加上168又是一个完全平方数&#xff0c;请问该数是多少&…