• 【人工智能】Astar算法求解8数码问题(QDU)
  • 【人工智能】利用α-β搜索的博弈树算法编写一字棋游戏(QDU)
  • 【人工智能】Fisher 线性分类器的设计与实现(QDU)
  • 【人工智能】感知器算法的设计实现(QDU)
  • 【人工智能】SVM 分类器的设计与应用(QDU)
  • 卷积神经网络 CNN 框架的实现与应用(写的比较水,就没放上)

实验目的

熟悉和掌握启发式搜索的定义、估价函数和算法过程,并利用 A* 算法求解 N 数码难题,理解求解流程和搜索顺序。

实验原理

盲⽬的搜索⽅法没有利⽤问题本身的特性信息,在决定要被扩展的节点时,都没有考虑该节点在解的路径上的可能性有多⼤,是否有利于问题求解以及求出的解是否最优;

启发式搜索要⽤到问题⾃身的某些特性信息,在选择节点时充分利⽤与问题有关的特征信息,估计出节点的重要性,在搜索时选择重要性较⾼的节点以利于求得最优解。

启发性信息和估价函数

⽤于指导搜索过程,且与具体问题有关的控制性信息称为启发性信息;⽤于评价节点重要性的函数称为估价函数

估价函数记作:
f(x)=g(x)+h(x)f(x)=g(x)+h(x) f(x)=g(x)+h(x)

  • g(x)g(x)g(x)为从初始节点S0S_0S0​到节点xxx已经实际付出的代价

  • h(x)h(x)h(x)是从节点xxx到⽬标节点SgS_gSg​的最优路径的估计代价,体现了问题的启发性信息,称为启发函数

  • f(x)f(x)f(x)表示从初始节点经过节点xxx到达⽬标节点的最优路径的代价估价值,其作⽤是⽤来评估OPEN表中各节点的重要性,决定其次序

实验内容

八数码问题

问题描述:

3×3九宫棋盘,放置数码为1 -8的8个棋牌,剩下一个空格,只能通过棋牌向空格的移动来改变棋盘的布局。根据给定初始布局(即初始状态)和目标布局(即目标状态),如何移动棋牌才能从初始布局到达目标布局,找到合法的走步序列。

问题分析:

对于八数码问题的解决,首先要考虑是否有答案。每一个状态可认为是一个1×9的矩阵,由数学知识可知,计算这两个有序数列的逆序值,如果两者都是偶数或奇数,则可通过变换到达,否则这两个状态不可达。这样,就可以在具体解决问题之前判断出问题是否可解,从而可以避免不必要的搜索。

广度优先搜索

搜索过程:

宽度优先搜索算法的主要涉及到两个对象数组的使用,分别是OPEN表和CLOSED表,用来记录已扩展但未搜索的节点和已搜索的节点。由于宽度搜索是盲目的,所以并不需要计算估计函数的值,只需要根据OPEN表节点的顺序依次进行遍历即可。

扩展节点的方法:首先找到数值“0”所在的坐标,根据坐标选择可扩展的方向,例如坐标为(2,0)只可向右扩展和向上扩展。同时,为了避免死循环,扩展的方向不可与父节点的方向相反。

扩展节点后,将其放入OPEN表的尾部,然后再进行新一轮的遍历,直到找到与目标节点相同的节点或者OPEN为空为止。

流程图如下:

全局择优搜索

全局择优搜索(Global Optimization Search)属于启发式搜索,即利用已知问题中的启发式信息指导问题求解,而非蛮力穷举的盲目搜索

此处,启发函数定义为:f(n)=g(n)+h(n)f(n) = g(n) + h(n)f(n)=g(n)+h(n),其中g(n)g(n)g(n)表示当前结点的深度,h(n)表示定义为当前节点与目标节点差异的度量。

搜索过程:

与宽度优先搜索相比,启发性的全局择优搜索则需要通过计算估计函数的值来决定搜索的方向,而不是盲目的进行遍历。

同样地,全局择优算法也会涉及OPEN表和CLOSED表的使用。但与宽度优先搜索不同的是,每一次扩展子节点都要计算该节点的估计函数值,并且,在将某节点的所有扩展子节点加入OPEN表的尾部后,需要根据估计函数值对OPEN表进行从小到大排序,之后再从OPEN表取出第一个节点放入CLOSED表中,进行新一轮的遍历,直到找到与目标节点相同的节点或者OPEN为空为止。

流程图如下:

A* 算法

A* 算法与全局择优算法均是启发性的搜索算法,但全局择优算法仅适合于状态空间是树状结构的情况,如果状态空间是一个有向图的话,在OPEN表中可能会出现重复的节点,节点的重复将导致大量的冗余搜索,使效率大大降低。

A*算法的优势在于 : 如果某一问题有解,那么利用 A* 搜索算法对该问题进行搜索一定能以搜索到最优的解结束。

搜索过程:

A* 算法在全局择优算法的基础上对其进行了改进:在扩展节点的时候加入了对子节点存在性的判断。如果扩展的某个新子节点已经在OPEN表或CLOSED表中出现过并且新子节点的估价值更小,则将原节点删除,并将新节点添加到OPEN表中,否则保留原节点,不进行改动。

流程图如下:

启发函数

本实验共涉及到两种启发函数h(n)h(n)h(n),具体定义如下:

  • h(n)h(n)h(n)定义为当前节点与目标节点差异的度量:即当前节点与目标节点格局相比,位置不符的数字个数。

实验原理:从数值0遍历到数值8,将每个数字不在位的个数作为启发信息。

  • h(n)h(n)h(n)定义为当前节点与目标节点距离的度量:当前节点与目标节点格局相比,位置不符的数字移动到目标节点中对应位置的最短距离之和。

实现原理:从数值0遍历到数值8,将每个数字从当前位置移动到目标位置的欧拉距离作为启发信息。

实验表格

表 1 不同启发函数 h(n)h(n)h(n) 求解 8 数码问题的结果比较

启发函数 h(n)
不在位数 欧拉距离 0
初始状态 130824765 130824765 130824765
目标状态 123804765 123804765 123804765
最优解 --第 0 步--
1|3|  
---------
8|2|4
---------
7|6|5
--第 1 步--
1|  |3
---------
8|2|4
---------
7|6|5
--第 2 步--
1|2|3
---------
8|  |4
---------
7|6|5
--第 0 步--
1|3|  
---------
8|2|4
---------
7|6|5
--第 1 步--
1|  |3
---------
8|2|4
---------
7|6|5
--第 2 步--
1|2|3
---------
8|  |4
---------
7|6|5
--第 0 步--
1|3|  
---------
8|2|4
---------
7|6|5
--第 1 步--
1|  |3
---------
8|2|4
---------
7|6|5
--第 2 步--
1|2|3
---------
8|  |4
---------
7|6|5
扩展节点数 2 2 4
生成节点数 3 3 7
运行时间 0.0308ms 0.1244ms 0.126ms

表 2 不同启发函数 h(n)h(n)h(n) 求解 15 数码问题的结果比较

启发函数 h(n)
不在位数 欧拉距离 0
初始状态 1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,0
1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,0
1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,0
目标状态 1,2,3,4,5,6,7,8,9,10,
11,12,0,13,14,15
1,2,3,4,5,6,7,8,9,10,
11,12,0,13,14,15
1,2,3,4,5,6,7,8,9,10,
11,12,0,13,14,15
最优解 --第 0 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
13|14|15|    
--第 1 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
13|14|    |15
--第 2 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
13|    |14|15
--第 3 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
    |13|14|15
--第 0 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
13|14|15|    
--第 1 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
13|14|    |15
--第 2 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
13|    |14|15
--第 3 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
    |13|14|15
--第 0 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
13|14|15|    
--第 1 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
13|14|    |15
--第 2 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
13|    |14|15
--第 3 步--
  1|  2|  3|  4
-------------------
  5|  6|  7|  8
-------------------
  9|10|11|12
-------------------
    |13|14|15
扩展节点数 3 3 7
生成节点数 4 4 13
运行时间 0.1444ms 0.3127ms 2.3763ms

实验设计

本实验采用Java高级程序设计语言实现八数码问题及十五数码问题的可视化,对不同启发函数下的不同算法进行性能分析。

算法设计

广度优先搜索

该算法的实现并没有使用OPEN和CLOSED表对节点进行操作,甚至都没有使用链表结构。主要依赖于Java中自带的队列数据结构,调用自带函数,用队列的”入队“与”出队“实现广度优先搜索。

将起点入队后只要队列非空,持续执行取队首、出队,先判断该节点是否为目标状态,若为目标状态则结束,否则由该节点进行四连通扩展,通过Map记录每种状态是否出现过,若未出现过则将扩展子节点入队。每次扩展节点都要保存每个子节点对应的父节点,这是为了方便输出路径时直接根据Map向前遍历即可。

全局择优搜索

该算法使用自定义链表类作为OPEN表,该链表的首节点为空,从其next节点开始保存状态信息;使用以String为键、以Boolean为值的Map作为CLOSED表,这是因为CLOSED表只需记录某个节点是否还可以进行扩展,因此无需为其创建链表。该算法并未采用对OPEN表按估计函数值从小到大对节点继续排序,而是每次去遍历OPEN表,找到估计函数值最小的节点作为扩展节点,这样可以有效的降低时间复杂度。

先向OPEN表中起点及其信息,只要OPEN表的首节点有后继节点就死循环,循环中先通过自定义函数找到OPEN链表中估计函数值最小的节点,如果该节点的状态与目标状态一致,则说明到达目标状态,退出;否则在此状态下确定空格的位置,让其与四个方向的数字交换,得到四种不同的状态,对于CLOSED表中存在的状态不予以考虑;若剩余的状态在不OPEN表则将其加入到OPEN表中。

A* 算法

与”全局择优搜索“基本一致,唯一不同的是:对于不在CLOSED表中的扩展子节点,若扩展到的状态在OPEN表中存在则还需要判断是已保存在OPEN表中的该状态对应的估计函数值与由当前节点扩展到该状态的估计函数值的大小关系,若由当前节点扩展到该状态的估计函数值更小,则需要将原节点删除并将该状态对应的新节点加入OPEN表中;若扩展到的状态在OPEN表中不存在,则该状态对应的节点无条件加入到OPEN表。

程序设计

初始界面

功能介绍

左侧九宫格(或十六宫格)对应初始状态,右侧九宫格(或十六宫格)对应目标状态,两种状态均可由用户向文本框中输入,若输入不合法则弹出警告框,又若输入的初始状态无法到达目标状态,则会根据算法判断逆序数的奇偶性,若无解会弹出警告框。

用户需先对靠上的下拉列表进行操作选择”八数码“还是”十五数码“,点击其下方的”确定“后宫格将切换成对应的样式;在方格内输入初始状态和目标状态后通过靠下的下拉列表选择采用”估价函数1“还是”估价函数2“,点击其下方的”确定“后开始执行算法。

左下方的选择卡总共三个TAB,分别对应三种算法,每个TAB中的文本域显示对应算法的执行过程,即从初始状态变换到目标状态的操作方式。右下方的文本域显示三种算法在选取不同的估价函数后得到结果的最佳步数和时间,可以根据显示对算法性能进行对比分析。

程序要求

  • 一个方格中最多存在两个字符
  • 表示空格子的方格必须存在且要在空格中输入一个空格字符
  • 初始状态和目标状态包括的数字(或字符串)必须完全一致

若不满足这些要求接下来的操作会弹出错误提示框无法继续执行,甚至程序崩溃。

若输入的初始状态无法到达目标状态,则弹出警告框。

程序局限性

  • 当一个方格内的输入存在两位字符时,在左下方的文本域中会出现宫格格式不标准的情况
  • 未对“程序要求”中的错误情况进行健壮性处理,即存在程序报错的情况,未用try-catch捕获并处理
  • 未在宫格中可视化空格的移动过程
  • 代码冗余度高

运行截图

采用启发函数1解决八数码问题,左侧九宫格为初始状态,右侧九宫格为目标状态。下图为默认状态,用户可以根据需要向九宫格中输入初始状态和目标状态。点击“八数码”下方的“确定”表示选定解决八数码问题,点击“估价函数1”下方的“确定”表示选定使用“估价函数1”作为启发函数解决此问题。

下方左侧选择卡中的文本域显示不同算法执行每一步得到的状态,右侧文本域显示采用不同算法解决该问题所需的时间与计算得到的最短移动次数。

操作靠上的下拉列表切换成”十五数码“后点击其下方的”确定“,可以实现将”初始状态“和”目标状态“的输入框转换为4×44×44×4的格式。输入完成后操作靠下的下拉列表切换启发函数,点击其下方的”确定“后执行三种算法,时间与步数将不覆盖地显示在右下侧的文本域中,完整移动过程将覆盖地显示与不同算法各自的执行过程。

附录(代码)

Window.java 程序

其中包含了界面设计的代码和三个核心算法。

package exp1;import javax.swing.*;
import java.awt.*;
import java.util.*;/*** @author LJR* @create 2021-11-04 18:52* @description*  要求:*      1. 空格子内必须是一个空格!*      2. 必须先确定是“八数码”还是“十五数码”!否则出错*  不足:*      1. 当一个方格内的输入存在两位字符时,在文本域中会出现显示不标准的情况*      2. 未对“要求”中的错误情况进行健壮性处理*      3. 未可视化空格的移动过程*      4. 代码冗余度高*/public class Window extends JFrame {private static final long serialVersionUID = -6740703588976621222L;int size = 8;int row_max = 0, col_max = 0; // 数码表的行数与列数int width = 0, height = 0; // 数码表每格宽高int left_x = 0, left_y = 0, right_x = 0, right_y = 0; // 初始数码表的左上角坐标,目标数码表的右上角坐标int fontsize = 0;int nums_original[] = {1, 3, 0, 8, 2, 4, 7, 6, 5}; // 八数码的测试int nums_target[] = {1, 2, 3, 8, 0, 4, 7, 6, 5};public Window() {super("A* 算法求解 8 数码问题");Container c = this.getContentPane();c.add(getJButton());c.setBackground(Color.white);this.setSize(700, 850);this.setUndecorated(false);this.setLocationRelativeTo(null);this.setVisible(true);this.setResizable(false);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}JButton jButton, jb; // 确定按钮JLabel label1, label2; // 文字标题JComboBox box; // 下拉列表JComboBox EorF; // 下拉列表JTextField jf[][] = new JTextField[3][20]; // 九宫格或十六宫格JTextArea textarea1, textarea2, textarea3; // 左下方三个文本域JTextArea ja; // 右下方文本域int v1steps = 0, v2steps = 0, v3steps = 0; // 全局择优、A*、BFS对应的步数double v1time = 0, v2time = 0, v3time = 0; // 全局择优、A*、BFS对应的用时public JPanel getJButton() {JPanel jP = new JPanel();jP.setOpaque(false);jP.setLayout(null);// 设置空布局,即绝对布局// “确认”按钮jb = new JButton("确认");jb.setBounds(293, 220, 100, 35);// 设置位置及大小jb.setFont(new Font("华文行楷", Font.PLAIN, 18));jb.setFocusPainted(false);jButton = new JButton("确认");jButton.setBounds(293, 315, 100, 35);// 设置位置及大小jButton.setFont(new Font("华文行楷", Font.PLAIN, 18));jButton.setFocusPainted(false);jP.add(jb);jP.add(jButton);// "初始状态"JLabel start = new JLabel("初始状态");start.setBounds(110, 130, 500, 35);start.setFont(new Font("华文行楷", Font.PLAIN, 20));jP.add(start);// "目标状态"JLabel target = new JLabel("目标状态");target.setBounds(490, 130, 500, 35);target.setFont(new Font("华文行楷", Font.PLAIN, 20));jP.add(target);// 第一行文字label1 = new JLabel("请分别填入初始状态(左)和目标状态(右)");label1.setBounds(110, 30, 500, 35);label1.setFont(new Font("华文行楷", Font.PLAIN, 25));jP.add(label1);// 第二行文字label2 = new JLabel("点击“确定”按钮查看移动过程和分析结果");label2.setBounds(140, 80, 500, 35);label2.setFont(new Font("华文行楷", Font.PLAIN, 22));jP.add(label2);// 下拉列表EorF = new JComboBox();EorF.setBounds(277, 170, 130, 40);EorF.setFont(new Font("华文行楷", Font.PLAIN, 18));EorF.addItem(" 八数码");EorF.addItem(" 十五数码");box = new JComboBox();box.setBounds(277, 265, 130, 40);box.setFont(new Font("华文行楷", Font.PLAIN, 18));box.addItem(" 估价函数 1");box.addItem(" 估价函数 2");jP.add(EorF);jP.add(box);// 显示数码时的格式
//        int nums_original[] = {1, 3, 0, 8, 2, 4, 7, 6, 5}; // 八数码的测试
//        int nums_target[] = {1, 2, 3, 8, 0, 4, 7, 6, 5};
//        int nums_original[] = {1, 2, 3, 4, 5, 6, 7, 8, 0}; // 八数码的测试
//        int nums_target[] = {1, 2, 3, 4, 6, 5, 7, 0, 8};
//        int nums_original[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 11, 12, 13, 14, 15}; // 十五数码的测试
//        int nums_target[] = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};if (size == 8) {row_max = col_max = 3;width = height = 60;left_x = 60;left_y = 170;right_x = 445;right_y = 170;fontsize = 30;} else if (size == 15) {row_max = col_max = 4;width = height = 45;left_x = 60; // 40left_y = 170;right_x = 445; // 465right_y = 170;fontsize = 25;}// 为八数码按钮绑定点击监听jb.addActionListener((actionEvent ->{int eightOrFifteen = EorF.getSelectedIndex();if (eightOrFifteen == 0) {size = 8;row_max = col_max = 3;width = height = 60;left_x = 60;left_y = 170;right_x = 445;right_y = 170;fontsize = 30;nums_original = new int[]{1, 3, 0, 8, 2, 4, 7, 6, 5}; // 八数码的测试nums_target = new int[]{1, 2, 3, 8, 0, 4, 7, 6, 5};// 删除十六宫格文本域组件,并显示九宫格组件for (int i = 0;i < 16;i ++) {try {jf[0][i].setVisible(false);jf[1][i].setVisible(false);} catch (Exception e) {}}for (int row = 0; row < row_max; row++) // 按顺序依次创建方格并进行排列for (int col = 0; col < col_max; col++) {int left_ch = nums_original[row * row_max + col];int right_ch = nums_target[row * row_max + col];jf[0][row * row_max + col] = new JTextField(left_ch + ""); // 创建文本框jf[1][row * row_max + col] = new JTextField(right_ch + "");jf[0][row * row_max + col].setBounds(col * width + left_x, row * height + left_y, width, height);jf[1][row * row_max + col].setBounds(col * width + right_x, row * height + right_y, width, height);jf[0][row * row_max + col].setFont(new Font("黑体", Font.PLAIN, fontsize));jf[1][row * row_max + col].setFont(new Font("黑体", Font.PLAIN, fontsize));jf[0][row * row_max + col].setOpaque(false); // 文本框透明jf[1][row * row_max + col].setOpaque(false);jf[0][row * row_max + col].setDocument(new LimitedLength()); // 限制文本框中的内容不超过两个字符jf[1][row * row_max + col].setDocument(new LimitedLength());jf[0][row * row_max + col].setText(left_ch == 0 ? " " : left_ch + ""); // 文本框中的默认数字jf[1][row * row_max + col].setText(right_ch == 0 ? " " : right_ch + "");jf[0][row * row_max + col].setVisible(true);jf[1][row * row_max + col].setVisible(true); // 设置可见后相当于覆盖jP.add(jf[0][row * row_max + col]);jP.add(jf[1][row * row_max + col]);}} else {size = 15;row_max = col_max = 4;width = height = 45;left_x = 60; // 40left_y = 170;right_x = 445; // 465right_y = 170;fontsize = 25;nums_original = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0}; // 十五数码的测试nums_target = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 13, 14, 15};// 删除九宫格文本域组件for (int i = 0;i < 16;i ++) {try {jf[0][i].setVisible(false);jf[1][i].setVisible(false);} catch (Exception e) {}}for (int row = 0; row < row_max; row++)for (int col = 0; col < col_max; col++) {int left_ch = nums_original[row * row_max + col];int right_ch = nums_target[row * row_max + col];jf[0][row * row_max + col] = new JTextField(left_ch + "");jf[1][row * row_max + col] = new JTextField(right_ch + "");jf[0][row * row_max + col].setBounds(col * width + left_x, row * height + left_y, width, height);jf[1][row * row_max + col].setBounds(col * width + right_x, row * height + right_y, width, height);jf[0][row * row_max + col].setFont(new Font("黑体", Font.PLAIN, fontsize));jf[1][row * row_max + col].setFont(new Font("黑体", Font.PLAIN, fontsize));jf[0][row * row_max + col].setOpaque(false); // 文本框透明jf[1][row * row_max + col].setOpaque(false);jf[0][row * row_max + col].setDocument(new LimitedLength()); // 限制文本框中的内容不超过两个字符jf[1][row * row_max + col].setDocument(new LimitedLength());jf[0][row * row_max + col].setText(left_ch == 0 ? " " : left_ch + ""); // 文本框中的默认数字jf[1][row * row_max + col].setText(right_ch == 0 ? " " : right_ch + "");jf[0][row * row_max + col].setVisible(true);jf[1][row * row_max + col].setVisible(true);jP.add(jf[0][row * row_max + col]);jP.add(jf[1][row * row_max + col]);}}}));// 为估价函数按钮绑定点击监听jButton.addActionListener((actionEvent -> {boolean flag = true, empty = false;/* 将初始文本框和目标文本框中的内容存在String数组中,排序后逐一比较,要求完全相同方可执行算法 */String[] left = new String[size + 1], right = new String[size + 1]; // 用于排序,方便判断初始和目标包含的数字是否一致String[] left_copy = new String[size + 1], right_copy = new String[size + 1]; // 原位置for (int i = 0; i <= size; i++) {left[i] = new String(jf[0][i].getText());right[i] = new String(jf[1][i].getText());left_copy[i] = new String(jf[0][i].getText());right_copy[i] = new String(jf[1][i].getText());}Arrays.sort(left); // 排序Arrays.sort(right);for (int i = 0; i <= size; i++) { // 判断是否存在不同的字符串,是否存在重复字符串if (!left[i].equals(right[i]) || (i != size && left[i].equals(left[i + 1]))) {flag = false;break;}if (left[i].equals(" ")) empty = true; // 必须要存在空格}if (flag && empty) { // 左右文本框内容不存在异常String string = box.getSelectedItem().toString();char option = string.charAt(string.length() - 1);int eightOrFifteen = EorF.getSelectedIndex();if (calDe(left_copy) % 2 != calDe(right_copy) % 2)JOptionPane.showMessageDialog(null, "无解", "错误", JOptionPane.ERROR_MESSAGE);else {/* 左侧GOS文本域 */textarea1.setText(""); // 清空文本域new GOS(left_copy, right_copy, Integer.parseInt(option + ""), eightOrFifteen);/* 左侧A*文本域 */textarea2.setText("");new Astar(left_copy, right_copy, Integer.parseInt(option + ""), eightOrFifteen);/* 左侧BFS文本域 */textarea3.setText("");new BFS(left_copy, right_copy, eightOrFifteen);/* 选择的估价函数 */if (option == '1') ja.append("h1(n),启发函数定义为当前节点与目标节点差异的度量:即当前节点与目标节点格局相比,位置不符的数字个数。\n\n");else if(option == '2') ja.append("h2(n),启发函数定义为当前节点与目标节点距离的度量:当前节点与目标节点格局相比,位置不符的数字移动到目标节点中对应位置的最短距离之和。\n\n");/* 右侧GOS时间与步数 */ja.append("全局择优算法所需时间为:" + v1time + "ms," + "所需步数为:" + v1steps + "\n\n");v1steps = 0;/* 右侧A*时间与步数 */ja.append("A*算法所需时间为:" + v2time + "ms," + "所需步数为:" + v2steps + "\n\n");v2steps = 0;/* 右侧BFS时间与步数 */ja.append("BFS算法所需时间为:" + v3time + "ms," + "所需步数为:" + v3steps + "\n\n");v3steps = 0;ja.append("------------------------------------------\n");/* 文本域自动滚动 */ja.setCaretPosition(ja.getText().length());}} else { // 弹出警告框JOptionPane.showMessageDialog(null, "初始数码与目标数码异常", "错误", JOptionPane.ERROR_MESSAGE);}}));// 初始数码与目标数码的显示(默认,即刚打开程序得到的界面)for (int row = 0; row < row_max; row++)for (int col = 0; col < col_max; col++) {int left_ch = nums_original[row * row_max + col];int right_ch = nums_target[row * row_max + col];jf[0][row * row_max + col] = new JTextField(left_ch + "");jf[1][row * row_max + col] = new JTextField(right_ch + "");jf[0][row * row_max + col].setBounds(col * width + left_x, row * height + left_y, width, height);jf[1][row * row_max + col].setBounds(col * width + right_x, row * height + right_y, width, height);jf[0][row * row_max + col].setFont(new Font("黑体", Font.PLAIN, fontsize));jf[1][row * row_max + col].setFont(new Font("黑体", Font.PLAIN, fontsize));jf[0][row * row_max + col].setOpaque(false); // 文本框透明jf[1][row * row_max + col].setOpaque(false);jf[0][row * row_max + col].setDocument(new LimitedLength()); // 限制文本框中的内容不超过两个字符jf[1][row * row_max + col].setDocument(new LimitedLength());jf[0][row * row_max + col].setText(left_ch == 0 ? " " : left_ch + ""); // 文本框中的默认数字jf[1][row * row_max + col].setText(right_ch == 0 ? " " : right_ch + "");jP.add(jf[0][row * row_max + col]);jP.add(jf[1][row * row_max + col]);}// 选择卡/* 将三个文本域分别加入到三个JScrollPane(滚动条面板)中,将三个滚动条面板加入同一个JTabbedPane(选择卡)的不同tab中 */JTabbedPane tabbedPane = new JTabbedPane();tabbedPane.setBounds(40, 410, 275, 360);/* 第一个文本域 */textarea1 = new JTextArea();textarea1.setLineWrap(true); // 文本自动换行textarea1.setFont(new Font("宋体", Font.PLAIN, 15)); // 设置文本域字体textarea1.setEditable(false); // 设置不可编辑文本域,只能通过程序执行修改JScrollPane scrollPane1 = new JScrollPane(textarea1); // 创建滚动条面板scrollPane1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); // 总是出现滚动条/* 第二个文本域 */textarea2 = new JTextArea();textarea2.setFont(new Font("宋体", Font.PLAIN, 15));textarea2.setLineWrap(true);textarea2.setEditable(false);JScrollPane scrollPane2 = new JScrollPane(textarea2);scrollPane2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);/* 第三个文本域 */textarea3 = new JTextArea();textarea3.setFont(new Font("宋体", Font.PLAIN, 15));textarea3.setLineWrap(true);textarea3.setEditable(false);JScrollPane scrollPane3 = new JScrollPane(textarea3);scrollPane3.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);/* 设置选择卡 */tabbedPane.setFont(new Font("宋体", Font.PLAIN, 12)); // 设置tab按键中的字体tabbedPane.addTab("全局择优搜索", scrollPane1); // 设置tab按键中的文字tabbedPane.addTab("A*搜索", scrollPane2);tabbedPane.addTab("宽度优先搜索", scrollPane3);jP.add(tabbedPane); // 添加选择卡面板// 文本域(显示用时)ja = new JTextArea();JScrollPane sp = new JScrollPane();sp.setBounds(370, 410, 275, 360); // 创建滚动条面板,也不用设置文本域的位置了ja.setFont(new Font("宋体", Font.PLAIN, 12));ja.setLineWrap(true); // 自动换行sp.setViewportView(ja); // 将文本域加入到滚动条面板中sp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); // 总是出现滚动条ja.setEditable(false); // 设置不可编辑文本域,只能通过程序执行修改jP.add(sp); // 只需将滚动条面板加入jP中,无需将文本域加入到jP中// 左侧文本域上方文字信息:"执行过程"JLabel label3 = new JLabel("执行过程");label3.setBounds(120, 220, 275, 330);label3.setFont(new Font("华文行楷", Font.PLAIN, 25));jP.add(label3);// 右侧文本域上方文字信息:"时间和最短移动次数"JLabel label4 = new JLabel("时间和最短移动次数");label4.setBounds(395, 220, 275, 330);label4.setFont(new Font("华文行楷", Font.PLAIN, 25));jP.add(label4);//        jP.setFocusable(false); // 让光标开始不聚焦return jP;}public int calDe(String[] s) { // 计算逆序对数int sum = 0;for (int i = 0; i <= size; i++) {for (int j = i + 1; j <= size; j++) {if (s[j].equals(" ")) continue;if (s[i].compareTo(s[j]) > 0) sum++;}}return sum;}// A*class Astar {final int offset[][] = new int[][]{{-1, 1, 0, 0}, {0, 0, -1, 1}};int evaluate_function = 1; // 启发函数1int eightOrFifteen = 0; // 0:8 1:15int size; // 方格总个数int size_unit; // 方格宽高Map<String, Boolean> closed_list = new HashMap<String, Boolean>();String[] original_status;String goal; // 即空格串MyLink open_list = new MyLink();AllNodes allNodes; // AllNodes是父类,单纯为了继承其目标状态public Astar(String[] original_status, String[] target_status, int evaluate_function, int eightOrFifteen) {allNodes = new AllNodes(target_status);// 赋值this.evaluate_function = evaluate_function;this.eightOrFifteen = eightOrFifteen;this.original_status = original_status;this.size = (eightOrFifteen == 0 ? 9 : 16); // 总个数this.size_unit = (eightOrFifteen == 0 ? 3 : 4); // 每行每列个数this.goal = new String(" ");v2time = AstarAlgorithm()*1.0/1e6;}public Node getMinfNode() { // 获取OPEN中最小f对应的节点Node pp = open_list.head;Node p = pp.next;Node pres = pp;Node res = p;while (p != null) {if (p.f < res.f) {res = p;pres = pp;}p = p.next;pp = pp.next;}pres.next = res.next;closed_list.put(changeStringArrayTo(res.status), true);return res;}public void printPath(Node tnode) { // 显示路径,将路径添加到文本域中if (v2steps == 0) v2steps = tnode.g;if (tnode == null) return;printPath(tnode.parent);textarea2.append("\n--第 " + tnode.g + " 步--\n");for (int i = 0; i < size_unit; i++) {for (int j = 0; j < size_unit; j++) {if (j == 0) textarea2.append(" ");textarea2.append("" + tnode.status[i * size_unit + j] + (j == size_unit - 1 ? '\n' : '|'));}if (i != size_unit - 1) {textarea2.append(" ");for (int j = 0; j < 2 * size_unit - 1; j++)textarea2.append("-");textarea2.append("\n");}}}// 这个函数是为了方便Map的键值不用使用数组,将每个方格中的字符串以三个dollar符隔开,而译码的时候也按照三个dollar符切开即可;// 之所以选三个dollar符是因为方格最多可以输入两个字符,三个字符作为分隔符最有效public String changeStringArrayTo(String[] str_arr) {String res = new String("");for (int i = 0; i < size; i++) res += str_arr[i] + "$$$";return res;}public long AstarAlgorithm() {long starttime2 = System.nanoTime();System.currentTimeMillis();open_list.addNode(-1, original_status, null, evaluate_function, eightOrFifteen); // 插入起点int tmp_cnt = 0;while (open_list.head.next != null) {Node current_node = getMinfNode();
//            for (int i = 0;i < size;i ++) System.out.print(current_node.status[i] + " ");System.out.println(); // 测试输出if (current_node.evaluate(evaluate_function, eightOrFifteen) == 0) {long endtime = System.nanoTime();// 找到最短路System.out.println(tmp_cnt);printPath(current_node);return endtime - starttime2; // 时间}tmp_cnt++;int goal_position = 0; // 获取goal位置while (!current_node.status[goal_position].equals(goal)) goal_position++;for (int k = 0; k < 4; k++) { // 四个方向int x = goal_position / size_unit, y = goal_position % size_unit;int tx = x + offset[0][k], ty = y + offset[1][k];if (tx < 0 || ty < 0 || tx >= size_unit || ty >= size_unit) continue;String[] temp_status = new String[size]; // 拷贝一份for (int i = 0; i < size; i++) { // 拷贝并实现交换if (i == x * size_unit + y)temp_status[i] = new String(current_node.status[tx * size_unit + ty]);else if (i == tx * size_unit + ty)temp_status[i] = new String(current_node.status[x * size_unit + y]);else temp_status[i] = new String(current_node.status[i]);}if (closed_list.containsKey(changeStringArrayTo(temp_status))) continue;Node pp = open_list.head;Node p = pp.next;while (p != null) {if (p.status == temp_status) break;p = p.next;pp = pp.next;}int h = new Node(temp_status, evaluate_function, eightOrFifteen).evaluate(evaluate_function, eightOrFifteen);if (p == null || p.f > current_node.g + 1 + h) { // 比较fopen_list.addNode(current_node.g, temp_status, current_node, evaluate_function, eightOrFifteen);}}}return 0;}}// BFSclass BFS {final int offset[][] = new int[][]{{-1, 1, 0, 0}, {0, 0, -1, 1}};public int eightOrFifteen = 0; // 0:8 1:15public int size; // 方格总个数public int size_unit; // 方格宽高public Map<String, String> st = new HashMap<String, String>();public String[] original_status;public String target_string;public String goal = " ";public Queue<String> q = new LinkedList<String>();public BFS(String[] original_status, String[] target_status, int eightOrFifteen) {// 赋值this.size = (eightOrFifteen == 0 ? 9 : 16);this.size_unit = (eightOrFifteen == 0 ? 3 : 4);this.eightOrFifteen = eightOrFifteen;this.original_status = original_status;this.target_string = changeStringArrayTo(target_status); // size的赋值要于此之前!v3time = BFSAlgorithm()*1.0/1e6;}public String changeStringArrayTo(String[] str_arr) {String res = new String("");for (int i = 0; i < size; i++) res += str_arr[i] + "$$$";return res;}// 解码public String[] recoveryToStringArray(String str) {return str.split("\\$\\$\\$");}public void printPath(String cur) {int idx = 0;Stack<String> stack = new Stack<String>(); // 路径输出使用的是栈do {stack.push(cur);cur = st.get(cur);} while (cur != null);while (!stack.empty()) {String[] s = recoveryToStringArray(stack.peek());stack.pop();textarea3.append("\n--第 " + idx + " 步--\n");for (int i = 0; i < size_unit; i++) {for (int j = 0; j < size_unit; j++) {if (j == 0) textarea3.append(" ");textarea3.append("" + s[i * size_unit + j] + (j == size_unit - 1 ? '\n' : '|'));}if (i != size_unit - 1) {textarea3.append(" ");for (int j = 0; j < 2 * size_unit - 1; j++)textarea3.append("-");textarea3.append("\n");}}idx++;}v3steps = idx - 1;}public long BFSAlgorithm() {long starttime = System.nanoTime();String ori = changeStringArrayTo(original_status);q.add(ori);st.put(ori, null);while (!q.isEmpty()) {String top = q.peek();q.poll();String[] stringArrayOfTop = recoveryToStringArray(top);if (top.equals(target_string)) {long endtime = System.nanoTime();printPath(top);return endtime - starttime;}int goal_position = 0;while (!stringArrayOfTop[goal_position].equals(goal)) goal_position++;for (int k = 0; k < 4; k++) {int x = goal_position / size_unit, y = goal_position % size_unit;int tx = x + offset[0][k], ty = y + offset[1][k];if (tx < 0 || ty < 0 || tx >= size_unit || ty >= size_unit) continue;String[] temp_string = new String[size]; // 拷贝一份for (int i = 0; i < size; i++) { // 拷贝并实现交换if (i == x * size_unit + y) temp_string[i] = new String(stringArrayOfTop[tx * size_unit + ty]);else if (i == tx * size_unit + ty)temp_string[i] = new String(stringArrayOfTop[x * size_unit + y]);else temp_string[i] = new String(stringArrayOfTop[i]);}String temp = changeStringArrayTo(temp_string);if (st.containsKey(temp)) continue;st.put(temp, top);q.add(temp);}}return 0;}}// GOSclass GOS {final int offset[][] = new int[][]{{-1, 1, 0, 0}, {0, 0, -1, 1}};int evaluate_function = 1; // 启发函数1int eightOrFifteen = 0; // 0:8 1:15int size; // 方格总个数int size_unit; // 方格宽高Map<String, Boolean> closed_list = new HashMap<String, Boolean>();String[] original_status;String goal;MyLink open_list = new MyLink();AllNodes allNodes;public GOS(String[] original_status, String[] target_status, int evaluate_function, int eightOrFifteen) {allNodes = new AllNodes(target_status);// 赋值this.evaluate_function = evaluate_function;this.eightOrFifteen = eightOrFifteen;this.original_status = original_status;this.size = (eightOrFifteen == 0 ? 9 : 16);this.size_unit = (eightOrFifteen == 0 ? 3 : 4);this.goal = new String(" ");v1time = GOSAlgorithm()*1.0/1e6;}public Node getMinfNode() {Node pp = open_list.head;Node p = pp.next;Node pres = pp;Node res = p;while (p != null) {if (p.f < res.f) {res = p;pres = pp;}p = p.next;pp = pp.next;}pres.next = res.next;closed_list.put(changeStringArrayTo(res.status), true);return res;}public void printPath(Node tnode) {if (v1steps == 0) v1steps = tnode.g;if (tnode == null) return;printPath(tnode.parent);textarea1.append("\n--第 " + tnode.g + " 步--\n");for (int i = 0; i < size_unit; i++) {for (int j = 0; j < size_unit; j++) {if (j == 0) textarea1.append(" ");textarea1.append("" + tnode.status[i * size_unit + j] + (j == size_unit - 1 ? '\n' : '|'));}if (i != size_unit - 1) {textarea1.append(" ");for (int j = 0; j < 2 * size_unit - 1; j++)textarea1.append("-");textarea1.append("\n");}}}public String changeStringArrayTo(String[] str_arr) {String res = new String("");for (int i = 0; i < size; i++) res += str_arr[i] + "$$$";return res;}public long GOSAlgorithm() {long starttime = System.nanoTime();open_list.addNode(-1, original_status, null, evaluate_function, eightOrFifteen); // 插入起点while (open_list.head.next != null) {Node current_node = getMinfNode();
//            for (int i = 0;i < size;i ++) System.out.print(current_node.status[i] + " ");System.out.println(); // 测试输出if (current_node.evaluate(evaluate_function, eightOrFifteen) == 0) {long endtime = System.nanoTime();// 找到最短路printPath(current_node);return endtime - starttime;}int goal_position = 0; // 获取goal位置while (!current_node.status[goal_position].equals(goal)) goal_position++;for (int k = 0; k < 4; k++) {int x = goal_position / size_unit, y = goal_position % size_unit;int tx = x + offset[0][k], ty = y + offset[1][k];if (tx < 0 || ty < 0 || tx >= size_unit || ty >= size_unit) continue;String[] temp_status = new String[size]; // 拷贝一份for (int i = 0; i < size; i++) { // 拷贝并实现交换if (i == x * size_unit + y)temp_status[i] = new String(current_node.status[tx * size_unit + ty]);else if (i == tx * size_unit + ty)temp_status[i] = new String(current_node.status[x * size_unit + y]);else temp_status[i] = new String(current_node.status[i]);}if (closed_list.containsKey(changeStringArrayTo(temp_status))) continue;Node pp = open_list.head;Node p = pp.next;while (p != null) {if (p.status == temp_status) break;p = p.next;pp = pp.next;}int h = new Node(temp_status, evaluate_function, eightOrFifteen).evaluate(evaluate_function, eightOrFifteen);if (p == null) {open_list.addNode(current_node.g, temp_status, current_node, evaluate_function, eightOrFifteen);}}}return 0;}}}

MyLink.java 程序

A*算法和全局择优搜索中使用的自定义链表数据结构,其中包含节点类和计算启发信息的函数。

import javafx.scene.Parent;/*** @author LJR* @create 2021-11-05 15:38*/class AllNodes {public static String[] target_status; // 静态public AllNodes() {}public AllNodes(String[] status) {target_status = status;}
}class Node extends AllNodes {public int f, g, h;public String[] status;public Node next = null; // 链表中的nextpublic Node parent = null; // 结果路径中的parent的状态public Node() {}public Node(Node node, int eightOrFifteen) {f = node.f;g = node.g;h = node.h;status = new String[(eightOrFifteen==0?9:16)];for (int i = 0;i < (eightOrFifteen==0?9:16);i ++) status[i] = new String(node.status[i]);parent = node.parent;next = node.next;}public Node(String[] s, int option, int eightOrFifteen) {this.status = s;this.h = evaluate(option, eightOrFifteen);this.f = g + this.h;}public Node(int g, String[] s, int option, int eightOrFifteen) { // 0:8  1:15this.status = s;this.h = evaluate(option, eightOrFifteen);this.f = g + this.h;this.g = g;}public Node(int g, String[] s, Node parent_node, int option, int eightOrFifteen) { // 0:8  1:15this(g, s, option, eightOrFifteen);this.parent = parent_node;}// 计算h(n)public int evaluate(int option, int eightOrFifteen) {int res = 0;int size = (eightOrFifteen == 0 ? 9 : 16);int size_unit = (eightOrFifteen == 0 ? 3 : 4);if (option == 1) {//            System.out.println("h(n)定义为当前节点与目标节点差异的度量:即当前节点与目标节点格局相比,位置不符的数字个数。");for (int i = 0; i < size; i++)res += (status[i].equals(target_status[i]) ? 0 : 1);} else if (option == 2) {//            System.out.println("h(n)定义为当前节点与目标节点距离的度量:当前节点与目标节点格局相比,位置不符的数字移动到目标节点中对应位置的最短距离之和。");for (int i = 0; i < size; i ++) {for (int j = 0;j < size; j ++) {if (status[i].equals(target_status[j])) {res += Math.abs((i / size_unit) - (j / size_unit)) + Math.abs((i % size_unit) - (j % size_unit));break;}}}}return res;}}public class MyLink {public Node head = new Node(-1, null, -1, -1);public void addNode(int preNodeg, String[] status, Node parent, int option, int eightOrFifteen) {Node newNode = new Node(preNodeg + 1, status, parent, option, eightOrFifteen); // 实例化一个节点if (head.next == null) {head.next = newNode;return;}Node tmp = head.next;while (tmp.next != null) {tmp = tmp.next;}tmp.next = newNode;}}

LimitedLength.java 程序

该程序用于限制用户向方格中输入的字符数不得大于2。

import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;public class LimitedLength extends PlainDocument {private static final long serialVersionUID = 1L;private int max_length = 2;@Overridepublic void insertString(int offs, String str, AttributeSet a) throws BadLocationException {if (str == null) return ;if(getLength() + str.length() > max_length) return ;else super.insertString(offs, str, a);}
}

【人工智能】Astar算法求解8数码问题(QDU)相关推荐

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

    目录 实验一 A*算法求解8数码问题 一.实验目的 二.实验原理 三.实验结果 四.实验总结 附录代码 推荐文章 实验一 A*算法求解8数码问题 一.实验目的 熟悉和掌握启发式搜索的定义.估价函数和算 ...

  2. 【人工智能导论】A*算法求解15数码问题 Java

    完整源码 - Eclipse项目文件 - GitHub地址 题目描述 关于本算法 两个晚上写完的,不足之处多多指教- 启发函数的选择: 一开始选用不在位数码个数+节点深度作为启发函数,效果不是很好. ...

  3. 15数码 java_A*算法求解15数码问题

    目录 一.问题描述 二.算法简介 三.算法步骤 四.评估函数 五.参考资料 六.源代码(Java实现) 一.问题描述 利用A*算法进行表1到表2的转换,要求空白块移动次数最少. 转换规则为:空白块只可 ...

  4. A*算法求解15数码问题

    目录 一.问题描述 二.算法简介 三.算法步骤 四.评估函数 五.参考资料 六.源代码(Java实现) 一.问题描述 利用A*算法进行表1到表2的转换,要求空白块移动次数最少. 转换规则为:空白块只可 ...

  5. A*算法求解N数码问题(AI实验一)

    1.实验题目 A*算法求解N数码问题,要求程序内,输入给定初始状态和目标状态,输出所需步数,过程状态及时间(程序可直接运行,无需再手动输入). 2.实验目的及要求 熟悉和掌握启发式搜索的定义.估价函数 ...

  6. 使用AStar算法求解二维迷宫问题

    严正声明:本文系作者davidhopper原创,未经许可,不得转载. 题目描述 定义一个二维数组N*M(其中2<=N<=10;2<=M<=10),如5 × 5数组下所示: in ...

  7. Python利用A*算法解决八数码问题

    资源下载地址:https://download.csdn.net/download/sheziqiong/86790565 资源下载地址:https://download.csdn.net/downl ...

  8. 基于Python实现的AStar求解八数码问题

    资源下载地址:https://download.csdn.net/download/sheziqiong/86776612 资源下载地址:https://download.csdn.net/downl ...

  9. 八数码问题a*算法c语言,八数码问题的A*算法求解

    A*算法是启发式搜素算法中较为出名和高效的算法之一,其关键是对于启发式函数的实际,启发式函数h(x)需要尽可能的接近实际的 .下面是人工智能八数码问题使用A*算法求解的源码放在博客上记录一下.程序使用 ...

  10. 人工智能作业 - A*算法程序求解八数码

    文章目录 A*算法 简介 思路 A*伪代码 八数码问题 题目描述 注意事项 实验过程 解决思路 伪代码 二维压缩为一维 检查是否有解 其他 代码实现 h1和h2的对比 关于曼哈顿距离 参考链接 A*算 ...

最新文章

  1. 《精通Python设计模式》学习结构型之MVC模式
  2. C++之shared_ptr总结
  3. docker file 打包jar_Spring Boot 的项目打包成的 JAR 包,制作成 docker 镜像并运行
  4. 数据库的七种传播方式
  5. 成功解决ValueError: min_samples_split must be an integer greater than 1 or a float in (0.0, 1.0]; got th
  6. hdu 2602 Bone Collector(01背包)
  7. mysql目录下没有配置文件_MySQL没有my.cnf配置文件如何解决
  8. kali linux 双显卡,Kali上双显卡驱动的安装
  9. mysql 给指定用户指定数据库
  10. django集成原有数据库
  11. 2022年考研计算机组成原理_2 数据表示和运算
  12. jquery事件绑定的几种用法
  13. Linux c/c++开发常用头文件
  14. 职称计算机 将计算机broad_1下的e盘映射为k盘网络驱动器,职称计算机考试(网络基础)试题与答案操作.doc...
  15. 中国现代文学专题形考2022
  16. 学编程的学习技巧_快速学习编程的10个关键技巧
  17. Segment Routing
  18. MySQL知识点整理汇总
  19. 通过电话拨号上网的家用计算机,拨号上网需计算机、电话线、帐号和()
  20. 105个软件测试工具大放送

热门文章

  1. 电池SOH仿真系列-基于LSTM神经网络的电池SOH估算方法
  2. Python算法教程:找出图的连通分量
  3. 软著申请说明书及源程序模板
  4. stata 将数据集变量名称导出_Stata 15 统计数据分析软件
  5. S32K144 S32K148 UDS诊断 BOOTLOADER开发 ISO14229 15765 软件定 基于UDS协议的CAN总线Bootloader设计 具体价格以咨询为主 UDS 诊断
  6. LM算法——列文伯格-马夸尔特算法(最速下降法,牛顿法,高斯牛顿法)(完美解释负梯度方向)
  7. 基于正点原子stm32mini板的串行通信原理
  8. mysql查看授权_mysql查看用户授权信息的具体方法
  9. 微信怎么自动加好友java_Xposed-微信自动加好友功能实现
  10. idea如何导出maven项目