目标
掌握A*算法的基本原理,熟悉和掌握启发式搜索的定义、估价函数和算法过程
掌握启发搜索的特点以及与盲目搜索的不同
程实现A*算法求解八数码问题的,理解求解流程和搜索顺序
项目内容
八数码问题是在3*3的九宫格棋盘上,摆有8个刻有1~8数码的将牌。棋盘中有一个空格,允许紧邻空格的某一将牌可以移到空格中,这样通过平移将牌可以将某一将牌布局变换为另一布局。针对给定的一种初始布局和目标状态,如何移动将牌,实现从初始状态到目标状态的转变。如下图1表示了一个具体的八数码问题求解。
程序设计的总体思路:
首先创建一个Table类,里面有两个属性:char aChar和int aInt,这是将来要放进open表和closed表中的内容,接着创建一个八数码对象EightDigital,里面包含属性如下:
int[] itSelf=new int[9];
Table table=new Table();
List<Boolean> canMoveDirection=new ArrayList<>();
int level;
EightDigital father;
这里采用一维数组存储八数码棋盘,table用来表示棋盘所在位置和估价函数的值,canMoveDirection用来存放当前棋盘能移动的方向,存放顺序是左上下右,level表示当前棋盘所在层数,father用来存放棋盘的来源。
对于棋盘的移动,首先判断棋盘中数字0所在的位置,如果0的下标是0,3,6就不能往左移动,如果下标是0,1,2就不能往上移动,如果下标是2,5,8就不能往右移动,如果下标是6,7,8就不能往下移动,此外,如果棋盘是由父棋盘状态向下运行得到,则当前棋盘就不能往上移动,其他方向同理,移动时交换数字位置即可。
程序设计开始首先让用户输入初始状态和目标状态,接着判断初始状态的逆序数个数和目标状态的逆序数个数奇偶性是否相同,如果不同说明状态无法到达,相同则开始计算。
A*算法(估价函数为不在位个数)思路:
- 初始化八数码初始状态,以及open表和closed表。
- 计算初始状态和目标状态的不在位个数和当前所在层数,如果不在位个数为0,则转(7)。
- 将open表中第一个元素弹出加入到closed表中。
- 获取当前状态能移动的方向,并分别移动棋盘并记录到open表中,并创建EightDigital对象加入到List列表当中。
- 对open表进行排序,数字小的排前面,数字相同则字母小的排前面。
- 将open表第一个排序的table与List表中元素作对比,找到具有相同table的EightDigital对象,并作为初始状态,并更新可移动的方向,转(2)。
- 返回当前八数码棋盘对象。
A*算法(估价函数为曼哈顿距离)思路:
同A*算法(估价函数为不在位个数)思路一致,区别在于第二步计算的是初始状态和目标状态的曼哈顿距离和当前所在层数。
曼哈顿距离计算:
将一维数组分层3×3的矩阵,对于初始棋盘的每个数(除了0),算出他的坐标,这里假设为(x1,y1),在找到终止棋盘那个数的对应坐标,假设为(x2,y2),则距离为|x1-x2|+|y1-y2|,最终不断累加就得到了曼哈顿距离。
宽度优先搜索算法思路:
同A*算法(估价函数为不在位个数)思路一致,区别在于第二步只要计算当前所在层数(即令估计代价h(n)=0的A*算法)。
深度优先搜索算法思路:
- 初始化八数码初始状态,以及open表和closed表。
- 计算初始状态和目标状态的不在位个数和当前所在层数,如果不在位个数为0,则转(7)。
- 判断当前层数是否是第8层(设置深度遍历到第八层就会重新选择节点),如果深度是第8层的话,将open表中最后一个元素弹出加入到closed表中,将该状态作为初始状态并转(2),否则继续执行(4)。
- 将open表中最后一个元素弹出加入到closed表中。
- 获取当前状态能移动的方向,并分别移动棋盘并记录到open表中,并创建EightDigital对象加入到List列表当中。
- 将open表最后一个table与List表中元素作对比,找到具有相同table的EightDigital对象,并作为初始状态,并更新可移动的方向,转(2)。
- 返回当前八数码棋盘对象。
Table表对象: public class Table { char aChar; int aInt; public Table() { } public Table(char aChar, int aInt) { this.aChar = aChar; this.aInt = aInt; } public char getaChar() { return aChar; } public int getaInt() { return aInt; } @Override public String toString() { return aChar + "(" + aInt + ')'; } } EightDigital八数码棋盘对象(包含A*算法、广度和深度优先算法): import java.util.*; //2 8 3 1 6 4 7 0 5 //2 0 3 1 8 4 7 6 5 //1 2 3 0 8 4 7 6 5 //1 2 3 8 0 4 7 6 5 public class EightDigital { int[] itSelf=new int[9]; Table table=new Table(); List<Boolean> canMoveDirection=new ArrayList<>(); int level; EightDigital father; public EightDigital() { } public EightDigital(int[] itSelf, Table table, List<Boolean> canMoveDirection, int level, EightDigital father) { this.itSelf = itSelf; this.table = table; this.canMoveDirection = canMoveDirection; this.level = level; this.father = father; } @Override public String toString() { return "EightDigital{" + "itSelf=" + Arrays.toString(itSelf) + ", father=" + father + '}'; } /** * 将EightDigital中的棋盘添加到List<int[]>列表中 * @param ed 八数码对象 * @param path 数组列表 */ public static void convertToList(EightDigital ed, List<int[]> path) { if(ed!=null){ convertToList(ed.father,path); path.add(ed.itSelf); } } /** * 获取曼哈顿距离 * @param start 初始棋盘 * @param end 目标棋盘 * @return 曼哈顿距离 */ public static int getManhattanDistance(int[] start, int[] end) { int distance = 0; for (int i = 0; i < start.length; i++) { if (start[i] != 0) { int startRow = i / 3; int startCol = i % 3; int endIndex = findIndex(end, start[i]); int endRow = endIndex / 3; int endCol = endIndex % 3; distance += Math.abs(startRow - endRow) + Math.abs(startCol - endCol); } } return distance; } /** * 查找指定数字在数组中的位置 */ private static int findIndex(int[] array, int target) { for (int i = 0; i < array.length; i++) { if (array[i] == target) { return i; } } return -1; } /** * 深度优先搜索 * @param start 初始状态 * @param end 目标状态 * @param open open表 * @param closed closed表 * @param character open和closed表中存储的字母 * @param tableCount 控制字母顺序 * @param canMoveList 能移动的方向 * @param openList open表输出列表 * @param closedList closed表输出列表 * @param eightDigitals 八数码棋盘列表(生成节点数) * @return 目标八数码棋盘 */ public static EightDigital DFS(int[] start, int[] end, List<Table> open, List<Table> closed, char character, int tableCount, List<Boolean> canMoveList, List<String> openList, List<String> closedList, List<EightDigital> eightDigitals) { //初始状态 EightDigital startEightDigital = new EightDigital(); startEightDigital.itSelf = start; startEightDigital.father = null; startEightDigital.level = 0; String startOpen = " open表 \n" + "-------------------------------------------\n" + "初始化:" + "( " + open.get(0).toString() + " )\n"; String startClosed = " closed表 \n" + "-------------------------------------------\n" + "初始化:" + "( )\n"; openList.add(startOpen); closedList.add(startClosed); //循环次数 int count=0; StringBuilder openSb=new StringBuilder(); StringBuilder closedSb=new StringBuilder(); int executeTime=1; while (true) { if (startEightDigital != null) { count=startEightDigital.level; count++; } int differentNum = getDifferentNum(start, end); if(differentNum==0){ closed.add(open.get(open.size()-1)); closedSb.append(executeTime).append("次循环后:\n( "); for (Table table:closed) { closedSb.append(table.toString()).append(" "); } closedSb.append(" )\n"); closedList.add(closedSb.toString()); closedSb.delete(0,closedSb.length()); open.clear(); openSb.append(executeTime).append("次循环后:\n"); openSb.append(closed.get(closed.size() - 1).getaChar()).append("为目的状态,则推出成功,结束搜索"); openList.add(openSb.toString()); return startEightDigital; }else if(count == 8){ //弹出open表的最后一个元素加到close表中 closed.add(open.get(open.size()-1)); closedSb.append(executeTime).append("次循环后:\n( "); for (Table table:closed) { closedSb.append(table.toString()).append(" "); } closedSb.append(" )\n"); closedList.add(closedSb.toString()); closedSb.delete(0,closedSb.length()); canMoveList.clear(); open.remove(open.size()-1); openSb.append(executeTime).append("次循环后:\n( "); for (Table table:open) { openSb.append(table.toString()).append(" "); } openSb.append(")\n"); openList.add(openSb.toString()); startEightDigital=getEightDigital(open.get(open.size()-1),eightDigitals); openSb.delete(0,openSb.length()); executeTime++; if (startEightDigital != null) { start=startEightDigital.itSelf; canMoveList=startEightDigital.canMoveDirection; } continue; } int startZeroIndex = getZeroIndex(start); int[] startTemp; //弹出open表的最后一个元素加到close表中 closed.add(open.get(open.size()-1)); closedSb.append(executeTime).append("次循环后:\n( "); for (Table table:closed) { closedSb.append(table.toString()).append(" "); } closedSb.append(" )\n"); closedList.add(closedSb.toString()); closedSb.delete(0,closedSb.length()); open.remove(open.size()-1); int num; if(canMoveLeft(startZeroIndex)&&canMoveList.get(0)) { //将0往左移动 //普通的赋值会改变start的值,所以使用Arrays.copyOf startTemp = Arrays.copyOf(start, start.length); int temp = startTemp[startZeroIndex]; startTemp[startZeroIndex] = startTemp[startZeroIndex - 1]; startTemp[startZeroIndex - 1] = temp; //第几层 num = count; Table table1 = new Table((char) (character + tableCount), num); //open表加入一个状态 open.add(table1); //改变字母的变量+1 tableCount++; EightDigital eightDigital1=new EightDigital(startTemp,table1,getMoveAvailable(start,startTemp),count,startEightDigital); eightDigitals.add(eightDigital1); } if(canMoveUp(startZeroIndex)&&canMoveList.get(1)){ //将0往上移动 startTemp=Arrays.copyOf(start, start.length); int temp = startTemp[startZeroIndex]; startTemp[startZeroIndex] = startTemp[startZeroIndex - 3]; startTemp[startZeroIndex - 3] = temp; num=count; Table table2 = new Table((char) (character + tableCount), num); //open表加入一个状态 open.add(table2); //改变字母的变量+1 tableCount++; EightDigital eightDigital2=new EightDigital(startTemp,table2,getMoveAvailable(start,startTemp),count,startEightDigital); eightDigitals.add(eightDigital2); } if(canMoveDown(startZeroIndex)&&canMoveList.get(2)){ //将0往下移动 startTemp=Arrays.copyOf(start, start.length); int temp=startTemp[startZeroIndex]; startTemp[startZeroIndex]=startTemp[startZeroIndex+3]; startTemp[startZeroIndex+3]=temp; num=count; Table table3 = new Table((char) (character + tableCount), num); //open表加入一个状态 open.add(table3); //改变字母的变量+1 tableCount++; EightDigital eightDigital3=new EightDigital(startTemp,table3,getMoveAvailable(start,startTemp),count,startEightDigital); eightDigitals.add(eightDigital3); } if(canMoveRight(startZeroIndex)&&canMoveList.get(3)){ //将0往右移动 startTemp=Arrays.copyOf(start, start.length); int temp = startTemp[startZeroIndex]; startTemp[startZeroIndex] = startTemp[startZeroIndex + 1]; startTemp[startZeroIndex + 1] = temp; num=count; Table table4 = new Table((char) (character + tableCount), num); //open表加入一个状态 open.add(table4); //改变字母的变量+1 tableCount++; EightDigital eightDigital4=new EightDigital(startTemp,table4,getMoveAvailable(start,startTemp),count,startEightDigital); eightDigitals.add(eightDigital4); } canMoveList.clear(); openSb.append(executeTime).append("次循环后:\n( "); for (Table table:open) { openSb.append(table.toString()).append(" "); } openSb.append(")\n"); openList.add(openSb.toString()); startEightDigital=getEightDigital(open.get(open.size()-1),eightDigitals); openSb.delete(0,openSb.length()); executeTime++; if (startEightDigital != null) { start=startEightDigital.itSelf; canMoveList=startEightDigital.canMoveDirection; } } } /** * A*算法(估价函数为错位数) * @param start 初始状态 * @param end 目标状态 * @param open open表 * @param closed closed表 * @param character open和closed表中存储的字母 * @param tableCount 控制字母顺序 * @param canMoveList 能移动的方向 * @param openList open表输出列表 * @param closedList closed表输出列表 * @param eightDigitals 八数码棋盘列表(生成节点数) * @param valuation 估价函数,0表示错位数,1表示曼哈顿距离,2表示宽度优先搜索 * @return 目标八数码棋盘 */ public static EightDigital aStar(int[] start, int[] end, List<Table> open, List<Table> closed, char character, int tableCount, List<Boolean> canMoveList, List<String> openList, List<String> closedList, List<EightDigital> eightDigitals,int valuation) { //初始状态 EightDigital startEightDigital = new EightDigital(); startEightDigital.itSelf=start; startEightDigital.father=null; startEightDigital.level=0; String startOpen=" open表 \n" + "-------------------------------------------\n" + "初始化:"+"( "+open.get(0).toString()+" )\n"; String startClosed=" closed表 \n" + "-------------------------------------------\n" + "初始化:"+"( )\n"; openList.add(startOpen); closedList.add(startClosed); //循环次数 int count=0; StringBuilder openSb=new StringBuilder(); StringBuilder closedSb=new StringBuilder(); int executeTime=1; while (true) { if (startEightDigital != null) { count=startEightDigital.level; count++; } int differentNum = getDifferentNum(start, end); if(differentNum==0){ closed.add(open.get(0)); closedSb.append(executeTime).append("次循环后:\n( "); for (Table table:closed) { closedSb.append(table.toString()).append(" "); } closedSb.append(" )\n"); closedList.add(closedSb.toString()); closedSb.delete(0,closedSb.length()); open.clear(); openSb.append(executeTime).append("次循环后:\n"); openSb.append(closed.get(closed.size() - 1).getaChar()).append("为目的状态,则推出成功,结束搜索"); openList.add(openSb.toString()); return startEightDigital; } int startZeroIndex = getZeroIndex(start); int[] startTemp; //弹出open表的第一个元素加到close表中 closed.add(open.get(0)); closedSb.append(executeTime).append("次循环后:\n( "); for (Table table:closed) { closedSb.append(table.toString()).append(" "); } closedSb.append(" )\n"); closedList.add(closedSb.toString()); closedSb.delete(0,closedSb.length()); open.remove(0); int num = 0; if(canMoveLeft(startZeroIndex)&&canMoveList.get(0)) { //将0往左移动 //普通的赋值会改变start的值,所以使用Arrays.copyOf startTemp = Arrays.copyOf(start, start.length); int temp = startTemp[startZeroIndex]; startTemp[startZeroIndex] = startTemp[startZeroIndex - 1]; startTemp[startZeroIndex - 1] = temp; if (valuation == 0) {//错位数估计函数 num = getDifferentNum(startTemp, end) + count; } else if (valuation == 1) {//曼哈顿距离估计函数 num = getManhattanDistance(startTemp, end); } else if (valuation == 2) {//宽度优先搜索算法 num = count; } Table table1 = new Table((char) (character + tableCount), num); //open表加入一个状态 open.add(table1); //改变字母的变量+1 tableCount++; EightDigital eightDigital1=new EightDigital(startTemp,table1,getMoveAvailable(start,startTemp),count,startEightDigital); eightDigitals.add(eightDigital1); } if(canMoveUp(startZeroIndex)&&canMoveList.get(1)){ //将0往上移动 startTemp=Arrays.copyOf(start, start.length); int temp = startTemp[startZeroIndex]; startTemp[startZeroIndex] = startTemp[startZeroIndex - 3]; startTemp[startZeroIndex - 3] = temp; if(valuation==0){//错位数估计函数 num=getDifferentNum(startTemp, end) + count; }else if(valuation==1){//曼哈顿距离估计函数 num=getManhattanDistance(startTemp, end); }else if(valuation==2){//宽度优先搜索算法 num=count; } Table table2 = new Table((char) (character + tableCount), num); //open表加入一个状态 open.add(table2); //改变字母的变量+1 tableCount++; EightDigital eightDigital2=new EightDigital(startTemp,table2,getMoveAvailable(start,startTemp),count,startEightDigital); eightDigitals.add(eightDigital2); } if(canMoveDown(startZeroIndex)&&canMoveList.get(2)){ //将0往下移动 startTemp=Arrays.copyOf(start, start.length); int temp=startTemp[startZeroIndex]; startTemp[startZeroIndex]=startTemp[startZeroIndex+3]; startTemp[startZeroIndex+3]=temp; if(valuation==0){//错位数估计函数 num=getDifferentNum(startTemp, end) + count; }else if(valuation==1){//曼哈顿距离估计函数 num=getManhattanDistance(startTemp, end); }else if(valuation==2){//宽度优先搜索算法 num=count; } Table table3 = new Table((char) (character + tableCount), num); //open表加入一个状态 open.add(table3); //改变字母的变量+1 tableCount++; EightDigital eightDigital3=new EightDigital(startTemp,table3,getMoveAvailable(start,startTemp),count,startEightDigital); eightDigitals.add(eightDigital3); } if(canMoveRight(startZeroIndex)&&canMoveList.get(3)){ //将0往右移动 startTemp=Arrays.copyOf(start, start.length); int temp = startTemp[startZeroIndex]; startTemp[startZeroIndex] = startTemp[startZeroIndex + 1]; startTemp[startZeroIndex + 1] = temp; if(valuation==0){//错位数估计函数 num=getDifferentNum(startTemp, end) + count; }else if(valuation==1){//曼哈顿距离估计函数 num=getManhattanDistance(startTemp, end)+count; }else if(valuation==2){//宽度优先搜索算法 num=count; } Table table4 = new Table((char) (character + tableCount), num); //open表加入一个状态 open.add(table4); //改变字母的变量+1 tableCount++; EightDigital eightDigital4=new EightDigital(startTemp,table4,getMoveAvailable(start,startTemp),count,startEightDigital); eightDigitals.add(eightDigital4); } canMoveList.clear(); if(valuation!=2) sort(open); openSb.append(executeTime).append("次循环后:\n( "); for (Table table:open) { openSb.append(table.toString()).append(" "); } openSb.append(")\n"); openList.add(openSb.toString()); startEightDigital=getEightDigital(open.get(0),eightDigitals); openSb.delete(0,openSb.length()); executeTime++; if (startEightDigital != null) { start=startEightDigital.itSelf; canMoveList=startEightDigital.canMoveDirection; } } } /** * 比对open表和八数码的列表,获取八数码对象 * @param table open表的内容 * @param eightDigitals 八数码列表 * @return 八数码对象 */ public static EightDigital getEightDigital(Table table, List<EightDigital> eightDigitals) { for (EightDigital eightDigital : eightDigitals) { if (eightDigital.table.equals(table)) { return eightDigital; } } return null; } /** * 对open表进行排序,先比较数字大小,在比较字母大小(升序排列) * @param open open表 */ public static void sort(List<Table> open) { open.sort((table1, table2) -> { if (table1.aInt != table2.aInt) { return Integer.compare(table1.aInt, table2.aInt); } else { return Character.compare(table1.aChar, table2.aChar); } }); } /** * 判断下一步可以往哪些方向走 * @param start 初始状态 * @param target 初始状态走一步后的状态 * @return 可以走的方向(左上下右) */ public static List<Boolean> getMoveAvailable(int[] start, int[] target) { int[] startTemp; List<Boolean> canMoveList = new ArrayList<>(); int startZeroIndex = getZeroIndex(start); if(canMoveLeft(startZeroIndex)){ //将0往左移动 startTemp=Arrays.copyOf(start, start.length); int temp=startTemp[startZeroIndex]; startTemp[startZeroIndex]=startTemp[startZeroIndex-1]; startTemp[startZeroIndex-1]=temp; int differentNum = getDifferentNum(startTemp, target); if(differentNum==0){ canMoveList.add(true); canMoveList.add(true); canMoveList.add(true); canMoveList.add(false); return canMoveList; } } if(canMoveUp(startZeroIndex)) { //将0往上移动 startTemp=Arrays.copyOf(start, start.length); int temp = startTemp[startZeroIndex]; startTemp[startZeroIndex] = startTemp[startZeroIndex - 3]; startTemp[startZeroIndex - 3] = temp; int differentNum = getDifferentNum(startTemp, target); if(differentNum==0){ canMoveList.add(true); canMoveList.add(true); canMoveList.add(false); canMoveList.add(true); return canMoveList; } } if(canMoveDown(startZeroIndex)){ //将0往下移动 startTemp=Arrays.copyOf(start, start.length); int temp=startTemp[startZeroIndex]; startTemp[startZeroIndex]=startTemp[startZeroIndex+3]; startTemp[startZeroIndex+3]=temp; int differentNum = getDifferentNum(startTemp, target); if(differentNum==0){ canMoveList.add(true); canMoveList.add(false); canMoveList.add(true); canMoveList.add(true); return canMoveList; } } if(canMoveRight(startZeroIndex)) { //将0往右移动 startTemp=Arrays.copyOf(start, start.length); int temp = startTemp[startZeroIndex]; startTemp[startZeroIndex] = startTemp[startZeroIndex + 1]; startTemp[startZeroIndex + 1] = temp; int differentNum = getDifferentNum(startTemp, target); if(differentNum==0){ canMoveList.add(false); canMoveList.add(true); canMoveList.add(true); canMoveList.add(true); return canMoveList; } } System.out.println("判断出错!"); return null; } /** * 是否能向下移动 * @param zeroIndex 0所在下标 * @return true表示可以向下移动,false表示不能向下移动 */ private static boolean canMoveDown(int zeroIndex) { return zeroIndex > 8 || zeroIndex < 6; } /** * 是否能向右移动 * @param zeroIndex 0所在下标 * @return true表示可以向右移动,false表示不能向右移动 */ private static boolean canMoveRight(int zeroIndex) { return zeroIndex != 2 && zeroIndex != 5 && zeroIndex != 8; } /** * 是否能向左移动 * @param zeroIndex 0所在下标 * @return true表示可以向左移动,false表示不能向左移动 */ private static boolean canMoveLeft(int zeroIndex) { return zeroIndex != 0 && zeroIndex != 3 && zeroIndex != 6; } /** * 是否能向上移动 * @param zeroIndex 0所在下标 * @return true表示可以向上移动,false表示不能向上移动 */ private static boolean canMoveUp(int zeroIndex) { return zeroIndex > 2 || zeroIndex < 0; } /** * 获取初始状态和目标状态不一样的个数 * @param start 初始状态 * @param end 目标状态 * @return 个数 */ public static int getDifferentNum(int[] start, int[] end) { int count=0; for (int i = 0; i < start.length; i++) { if(start[i]!=end[i]){ count++; } } int i=getZeroIndex(start); int j=getZeroIndex(end); if(i==j){ return count; } return count-1; } /** * 获取数组中0的位置 * @param start 棋盘状态 * @return 0的位置 */ private static int getZeroIndex(int[] start) { for(int i=0;i<start.length;i++){ if(start[i]==0){ return i; } } return -1; } /** * 看是否能达到目标状态 * @param start 起始状态 * @param end 目标状态 * @return 能达到则返回true,否则返回false */ public static boolean canArrive(int[] start, int[] end) { int num1=reverseNum(start); int num2 = reverseNum(end); return num1 % 2 == num2 % 2; } /** * 求逆序数的个数 * @param start 棋盘状态 * @return 逆序数个数 */ private static int reverseNum(int[] start) { int count=0; for(int i=0;i<start.length;i++){ if(start[i]!=0){ for(int j=i+1;j< start.length;j++){ if(start[j]!=0&&start[j]<start[i]){ count++; } } } } return count; } }
主界面设计:
代码:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
public class EightDigitalFrame extends JFrame {
private int[] start=new int[9];
private int[] end=new int[9];
private static int currentStateIndex = 0; // 当前状态的索引
private static final List<int[]> results=new ArrayList<>();
List<String> openList=new ArrayList<>();
List<String> closedList=new ArrayList<>();
public EightDigitalFrame() {
// 设置窗口标题和大小
setTitle("八数码棋盘求解");
setSize(1170, 550);
setLocationRelativeTo(null);
setResizable(false);
// 创建一个面板,用于放置组件
JPanel panel = new JPanel();
panel.setLayout(null); // 使用绝对布局
// 创建9个文本框,放在左上角,分成三行
JTextField[] textFields = new JTextField[9];
for (int i = 0; i < 9; i++) {
textFields[i] = new JTextField();
// 设置位置和大小,根据i的值计算行和列
int row = i / 3; // 行数,从0开始
int col = i % 3; // 列数,从0开始
textFields[i].setBounds(125 + col * 90, 30 + row * 90, 90, 90);
textFields[i].setFont(new Font("楷体", Font.BOLD, 75));
textFields[i].setHorizontalAlignment(SwingConstants.CENTER); // 设置文本水平居中
textFields[i].setEditable(false); // 设置文本不可编辑
textFields[i].setBackground(Color.white);
panel.add(textFields[i]); // 添加到面板
}
// 创建一个较小的文本框,放在9个文本框下面
JTextField smallTextField = new JTextField();
smallTextField.setBounds(205, 320, 110, 20);
smallTextField.setEditable(false); // 设置文本不可编辑
smallTextField.setBorder(null); // 设置文本框边框不可见
smallTextField.setHorizontalAlignment(SwingConstants.CENTER); // 设置文本水平居中
smallTextField.setBackground(Color.LIGHT_GRAY);
panel.add(smallTextField);
// 创建两个按钮,放在小文本框下面
JButton button1 = new JButton("手动输入初始状态");
JButton button2 = new JButton("手动输入目标状态");
button1.setBounds(115, 360, 135, 40);
button2.setBounds(265, 360, 135, 40);
panel.add(button1);
panel.add(button2);
// 创建5个按钮,放在两个按钮下面
JButton[] buttons = new JButton[5];
for (int i = 0; i < 5; i++) {
buttons[i] = new JButton();
buttons[i].setBounds(10 + i * 100, 420, 90, 50);
buttons[i].setEnabled(false);
panel.add(buttons[i]);
}
buttons[0].setText("<<");
buttons[1].setText("<");
buttons[2].setText("计算");
buttons[3].setText(">");
buttons[4].setText(">>");
buttons[2].setEnabled(true);
// 创建一个较小的文本框,放在选项框旁边
JTextField functionTextField = new JTextField();
functionTextField.setBounds(530, 10, 280, 30);
functionTextField.setEditable(false); // 设置文本不可编辑
functionTextField.setBorder(null); // 设置文本框边框不可见
functionTextField.setFont(new Font("楷体", Font.BOLD, 20));
functionTextField.setText("请选择求解方法:");
panel.add(functionTextField);
// 创建一个选项框,放在右上角
JComboBox<String> comboBox = new JComboBox<>();
comboBox.addItem("A*算法(以错位数为估计函数)");
comboBox.addItem("A*算法(以曼哈顿距离为估计函数)");
comboBox.addItem("宽度优先搜索");
comboBox.addItem("深度优先搜索");
comboBox.setFont(new Font("楷体", Font.BOLD, 15));
comboBox.setBounds(830, 10, 290, 30);
panel.add(comboBox);
// 创建三个文本框
JTextArea[] rightTextFields = new JTextArea[3];
JScrollPane[] scrollPanes = new JScrollPane[3];
for (int i = 0; i < 3; i++) {
rightTextFields[i] = new JTextArea();
rightTextFields[i].setLineWrap(true); // 自动换行
rightTextFields[i].setWrapStyleWord(true); // 断行不断字
scrollPanes[i] = new JScrollPane(rightTextFields[i]);
scrollPanes[i].setBounds(530+i*200, 50, 190, 420);
scrollPanes[i].setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); // 纵向滚动条
panel.add(scrollPanes[i]);
}
//输入初始状态
button1.addActionListener(e -> {
EightDigitalChessboard edc=new EightDigitalChessboard();
edc.setCallback(array -> {
start=array;
for(int i=0;i<9;i++){
textFields[i].setText(String.valueOf(start[i]));
}
});
});
//输入目标状态
button2.addActionListener(e -> {
EightDigitalChessboard edc=new EightDigitalChessboard();
edc.setCallback(array -> {
end=array;
for(int i=0;i<9;i++){
textFields[i].setText(String.valueOf(end[i]));
}
});
});
//计算按钮点击事件
buttons[2].addActionListener(e -> {
rightTextFields[0].setText("");
rightTextFields[1].setText("");
rightTextFields[2].setText("");
results.clear();
currentStateIndex=0;
openList.clear();
closedList.clear();
if(isNull(start)||isNull(end)){
JOptionPane.showMessageDialog(null, "请先输入起始状态和目标状态!");
return;
}
buttons[3].setEnabled(true);
buttons[4].setEnabled(true);
//open表
List<Table> open=new ArrayList<>();
//closed表
List<Table> closed=new ArrayList<>();
boolean flag= EightDigital.canArrive(start,end);
if(flag){
int num= EightDigital.getDifferentNum(start,end);
if(num==0){
JOptionPane.showMessageDialog(null, "初始状态与目标状态一致!程序已终止!");
return;
}
List<Boolean> canMoveList=new ArrayList<>();
for (int i=0;i<4;i++) {
canMoveList.add(true);
}
List<EightDigital> eightDigitals=new ArrayList<>();
long startTime = System.nanoTime();
EightDigital path;
if(comboBox.getSelectedIndex()==0){
//错位数
open.add(new Table('s',num));
path= EightDigital.aStar(start,end,open,closed,'A',0,canMoveList,openList,closedList,eightDigitals,0);
}else if(comboBox.getSelectedIndex()==1){
//曼哈顿
open.add(new Table('s',EightDigital.getManhattanDistance(start,end)));
path= EightDigital.aStar(start,end,open,closed,'A',0,canMoveList,openList,closedList,eightDigitals,1);
}else if(comboBox.getSelectedIndex()==2){
//广度
open.add(new Table('s',0));
path= EightDigital.aStar(start,end,open,closed,'A',0,canMoveList,openList,closedList,eightDigitals,2);
}else{
//深度
open.add(new Table('s',0));
path= EightDigital.DFS(start,end,open,closed,'A',0,canMoveList,openList,closedList,eightDigitals);
}
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1000000; // 转换为毫秒
rightTextFields[2].append("函数运行时间:" + duration + " 毫秒");
rightTextFields[2].append("\n扩展节点个数:"+(closed.size()-2));
rightTextFields[2].append("\n生成节点个数:"+eightDigitals.size());
for (String value : openList) {
rightTextFields[0].append(value);
}
for (String s : closedList) {
rightTextFields[1].append(s);
}
if(path!=null) {
EightDigital.convertToList(path, results);
}
updateTextFields(textFields,results.get(0));
smallTextField.setText((currentStateIndex+1)+"/"+results.size());
}else {
JOptionPane.showMessageDialog(null, "目标状态不可达!程序已终止!");
}
});
//<按钮点击事件
buttons[1].addActionListener(e -> {
buttons[3].setEnabled(true);
buttons[4].setEnabled(true);
if (currentStateIndex > 0) {
currentStateIndex--;
updateTextFields(textFields, results.get(currentStateIndex));
}
if(currentStateIndex == 0) {
buttons[1].setEnabled(false);
buttons[0].setEnabled(false);
}
smallTextField.setText((currentStateIndex+1)+"/"+results.size());
});
//>按钮点击事件
buttons[3].addActionListener(e -> {
buttons[1].setEnabled(true);
buttons[0].setEnabled(true);
if (currentStateIndex < results.size() - 1) {
currentStateIndex++;
updateTextFields(textFields, results.get(currentStateIndex));
}
if(currentStateIndex == results.size()-1) {
buttons[3].setEnabled(false);
buttons[4].setEnabled(false);
}
smallTextField.setText((currentStateIndex+1)+"/"+results.size());
});
//<<按钮点击事件
buttons[0].addActionListener(new ActionListener() {
private Timer timer;
@Override
public void actionPerformed(ActionEvent e) {
buttons[3].setEnabled(true);
buttons[4].setEnabled(true);
int delay = 1000; // 设置延迟时间为1秒
timer = new Timer(delay, evt -> {
if (currentStateIndex > 0) {
currentStateIndex--;
updateTextFields(textFields, results.get(currentStateIndex));
smallTextField.setText((currentStateIndex+1)+"/"+results.size());
}else {
buttons[1].setEnabled(false);
buttons[0].setEnabled(false);
timer.stop(); // 停止计时器
}
});
timer.start(); // 启动计时器
}
});
//>>按钮点击事件
buttons[4].addActionListener(new ActionListener() {
private Timer timer;
@Override
public void actionPerformed(ActionEvent e) {
buttons[1].setEnabled(true);
buttons[0].setEnabled(true);
int delay = 1000; // 设置延迟时间为1秒
timer = new Timer(delay, evt -> {
if (currentStateIndex < results.size() - 1) {
currentStateIndex++;
updateTextFields(textFields, results.get(currentStateIndex));
smallTextField.setText((currentStateIndex + 1) + "/" + results.size());
} else {
buttons[3].setEnabled(false);
buttons[4].setEnabled(false);
timer.stop(); // 停止计时器
}
});
timer.start(); // 启动计时器
}
});
// 将面板添加到窗口
add(panel);
// 设置窗口可见
setVisible(true);
}
/**
* 更新棋盘
* @param textFields 棋盘格子
* @param state 棋盘状态
*/
private static void updateTextFields(JTextField[] textFields, int[] state) {
for (int i = 0; i < 9; i++) {
textFields[i].setText(String.valueOf(state[i]));
}
}
/**
* 判断是否输入了初始状态或目标状态
* @param arr 棋盘
* @return 是/否
*/
private static boolean isNull(int[] arr){
return arr[0] == 0 && arr[1] == 0;
}
public static void main(String[] args) {
// 创建一个EightDigitalFrame对象,调用构造方法
new EightDigitalFrame();
}
}
手动输入棋盘界面设计:
import javax.swing.*;
import java.awt.*;
import java.util.HashSet;
import java.util.Set;
public class EightDigitalChessboard extends JFrame {
private Callback callback;
public EightDigitalChessboard() {
// 设置窗口标题和大小
setTitle("自定义八数码棋盘");
setSize(335, 415);
setLocationRelativeTo(null);
setResizable(false);
// 创建一个面板,用于放置组件
JPanel panel = new JPanel();
panel.setLayout(null); // 使用绝对布局
// 创建9个文本框,放在左上角,分成三行
JTextField[] textFields = new JTextField[9];
for (int i = 0; i < 9; i++) {
textFields[i] = new JTextField();
// 设置位置和大小,根据i的值计算行和列
int row = i / 3; // 行数,从0开始
int col = i % 3; // 列数,从0开始
textFields[i].setBounds(20 + col * 90, 20 + row * 90, 90, 90);
textFields[i].setFont(new Font("楷体", Font.BOLD, 75));
textFields[i].setHorizontalAlignment(SwingConstants.CENTER); // 设置文本水平居中
panel.add(textFields[i]); // 添加到面板
}
JButton button1 = new JButton("确定");
button1.setBounds(85, 305, 135, 40);
panel.add(button1);
button1.addActionListener(e -> {
Set<Integer> numbers = new HashSet<>();
boolean valid = true;
StringBuilder sb=new StringBuilder();
for (JTextField textField : textFields) {
try {
int number = Integer.parseInt(textField.getText());
if (number < 0 || number > 8 || numbers.contains(number)) {
valid = false;
break;
} else {
numbers.add(number);
sb.append(number);
}
} catch (NumberFormatException ex) {
valid = false;
break;
}
}
if (valid && numbers.size() == 9) {
String[] numberArray = sb.toString().split("");
int[] intArray = new int[numberArray.length];
for (int i = 0; i < numberArray.length; i++) {
intArray[i] = Integer.parseInt(numberArray[i]);
}
callback.onArraySelected(intArray);
dispose();
} else {
JOptionPane.showMessageDialog(null, "数字不符合要求!请重新输入!");
}
});
// 将面板添加到窗口
add(panel);
// 设置窗口可见
setVisible(true);
}
public void setCallback(Callback callback) {
this.callback = callback;
}
}
interface Callback {
void onArraySelected(int[] array);
}