搜索算法学问不小...
总结:
1. 状态表示用整数最快, 可是转化状态的代码不好写, 用字符串挺爽的, 可有些地方涉及到数字运算, 代码又不自然, 整来整去, 还是用byte[]好了...性能没比字符串强多少...
2. open表用LinkedList就挺好, 支持队列和堆栈两种模型, 这点在双向广度优先搜索时候挺方便, closed表千万别用List类型, 用HashMap或者HashSet性能上才可接受, 而且前者优于后者...
3. 完美哈希函数, 也就是那个全排列的哈希函数, 能够不浪费一点儿空间, 实现一一映射, 函数的设计涉及到变进制数的概念, 数学的力量还是无比强大地...
4. 双向广度优先搜索区分方向要用两个open和closed, 相遇时刻拿出另一个方向上状态相同的节点比较麻烦, 需要考虑究竟用什么当做键, 用什么当做值, 把两个方向上的搜索代码抽取到一个方法中会使性能降低很多, 且需要抛异常来结束算法, 不抽取则代码又挺难看, 难办. 双向广度优先搜索路径输出是个麻烦事儿, 索性砍掉State类中记录动作的属性, 在输出grid的时候现算更挺好, 方便且免转化...
5. 编程上的细节, 什么该预先算出, 用什么方法实现节点扩展最爽, 怎么实现demo最酷, 怎么来考察运行性能, 这些都需要加强...
下面给出代码...
普通广度优先搜索:
LinkedList<State> open = new LinkedList<State>(); Map<State, Integer> closed = new HashMap<State, Integer>(); State now; List<State> nodes; public void search() { open.add(EightNum.src); while (!open.isEmpty()) { now = open.poll(); closed.put(now, 0); if (now.equals(EightNum.des)) break; nodes = EightNum.expend(now); for (State n : nodes) if (!closed.containsKey(n)) open.add(n); } EightNum.showPath(now, null); }
双向广度优先搜索:
LinkedList<State> open1 = new LinkedList<State>(); LinkedList<State> open2 = new LinkedList<State>(); Map<Integer, State> closed1 = new HashMap<Integer, State>(); Map<Integer, State> closed2 = new HashMap<Integer, State>(); State now, s; List<State> nodes; public void search() { State end1 = EightNum.src; State end2 = EightNum.des; open1.add(end1); open2.add(end2); while (!open1.isEmpty() || !open2.isEmpty()) { if (!open1.isEmpty()) { do { now = open1.poll(); closed1.put(now.hashCode(), now); nodes = EightNum.expend(now); for (State n : nodes) if ((s = closed2.get(n.hashCode())) != null) { EightNum.showPath(now, s); return; } else if (!closed1.containsKey(n.hashCode())) open1.add(n); } while (!now.equals(end1)); end1 = open1.peekLast(); } if (!open2.isEmpty()) { do { now = open2.poll(); closed2.put(now.hashCode(), now); nodes = EightNum.expend(now); for (State n : nodes) if ((s = closed1.get(n.hashCode())) != null) { EightNum.showPath(s, now); return; } else if (!closed2.containsKey(n.hashCode())) open2.add(n); } while (!now.equals(end2)); end2 = open2.peekLast(); } } }
A*算法:
PriorityQueue<State> open = new PriorityQueue<State>(256, new Comparator<State>() { public int compare(State s1, State s2) { return s1.G + s1.H - s2.G - s2.H; } }); Map<State, Integer> closed = new HashMap<State, Integer>(); State now; List<State> nodes; public void search() { EightNum.src.G = 0; EightNum.src.H = manhattan(EightNum.src.grid); open.add(EightNum.src); while (!open.isEmpty()) { now = open.poll(); closed.put(now, 0); if (now.equals(EightNum.des)) break; nodes = EightNum.expend(now); for (State n : nodes) if (!closed.containsKey(n)) { n.G = now.G + 1; n.H = manhattan(n.grid); open.add(n); } } EightNum.showPath(now, null); } private int manhattan(byte[] bs) { byte[] tmp = EightNum.getXYs(bs); int sum = 0; for (int i = 0; i < 8; i++) sum += Math.abs(tmp[i] / 3 - EightNum.XYs[i] / 3) + Math.abs(tmp[i] % 3 - EightNum.XYs[i] % 3); return sum; }
这里的open表要用优先级队列, 可是A*算法框架要更新open表和closed表中的某些节点的F值, PriorityQueue对遍历的支持不太理想, 索性就去掉了更新值的部分, 每次直接插入算了. A*的启发式函数要用曼哈顿距离, 那个"不在家"个数不太理想, 扩展的节点有时竟然比普通的广度优先搜索还多...
状态空间元素的表示:
class State { byte[] grid; State prev; int G, H; int hash = -1; public State(byte[] g, State p) { grid = g; prev = p; } public State(byte[] g, State p, int G, int H) { this(g, p); this.G = G; this.H = H; } public int hashCode() { if (hash != -1) return hash; int sum = 0; byte[] invs = EightNum.getInvNums(grid); for (int i = 0; i < 7; i++) sum += invs[i] * EightNum.facts[i]; sum += (8 - grid[8]) * EightNum.facts[7]; return hash = sum; } public boolean equals(Object o) { byte[] bs = ((State) o).grid; for (byte i = 0; i < bs.length; i++) if (bs[i] != grid[i]) return false; return true; } }
这个全排列的完美哈希函数在性能上好像没帮多大忙, 尤其用字符串表示grid的时候, 可能是我不会用吧. 缓存一下hash值, 貌似能快个几十毫秒...
辅助数据, 计算与测试...
static final byte rules[][] = {{1,3},{-1,1,3},{-1,3}, {-3,1,3},{-1,-3,1,3},{-1,-3,3}, {-3,1},{-1,-3,1},{-1,-3}}; static final char moves[] = {'U','.','L','.','R','.','D'}; static final int facts[] = {1,2,6,24,120,720,5040,40320}; static byte[] XYs = new byte[8]; static State src, des; static long count, timer; public static List<State> expend(State now) { List<State> nodes = new LinkedList<State>(); byte[] bs = now.grid; byte d, k = bs[8]; byte[] ops = rules[k]; for (byte i = 0; i < ops.length; i++) { byte[] grid = Arrays.copyOf(bs, 9); byte op = ops[i]; d = (byte) (k + op); if (op == -3) { byte b = grid[k-3]; grid[k-3] = grid[k-2]; grid[k-2] = grid[k-1]; grid[k-1] = b; } else if (op == 3) { byte b = grid[k+2]; grid[k+2] = grid[k+1]; grid[k+1] = grid[k]; grid[k] = b; } grid[8] = d; nodes.add(new State(grid, now)); } count += nodes.size(); return nodes; } public static void shuffle() { byte[] g2, g1 = getRandom(); do { g2 = getRandom(); } while (!canSolve(g1, g2)); System.out.println("SRC"); showGrid(g1); System.out.println("DES"); showGrid(g2); src = new State(g1, null); des = new State(g2, null); XYs = getXYs(g2); } private static byte[] getRandom() { List<Byte> bs = new ArrayList<Byte>(9); for (byte i = 1; i < 9; i++) bs.add(i); Collections.shuffle(bs); bs.add((byte) (new Random().nextInt(9))); byte[] ret = new byte[9]; for (byte i = 0; i < 9; i++) ret[i] = bs.get(i); return ret; } private static boolean canSolve(byte[] src, byte[] des) { byte[] in1 = getInvNums(src); byte[] in2 = getInvNums(des); int sum = 0; for (byte b : in1) sum += b; for (byte b : in2) sum += b; return sum % 2 == 0; } public static byte[] getInvNums(byte[] grid) { byte[] invs = new byte[7]; for (byte i = 1; i < 8; i++) { byte sum = 0; for (byte j = 0; j < i; j++) if (grid[j] > grid[i]) sum++; invs[i-1] = sum; } return invs; } public static byte[] getXYs(byte[] bs) { byte[] xys = new byte[8]; for (byte i = 0; i < bs[8]; i++) xys[bs[i]-1] = i; for (byte i = bs[8]; i < 8; i++) xys[bs[i]-1] = (byte) (i + 1); return xys; } public static void showPath(State now, State next) { LinkedList<State> path = new LinkedList<State>(); for ( ; now.prev != null; now = now.prev) path.push(now); for ( ; next != null; next = next.prev) path.add(next); for (int i = 0, s = now.grid[8], t; i < path.size(); i++, s = t) { now = path.get(i); t = now.grid[8]; //System.out.print(moves[t-s+3]); //showGrid(now.grid); } System.out.format(" | %7d | %3d | %5d\n", count, path.size(), System.currentTimeMillis() - timer); count = 0; } private static void showGrid(byte[] grid) { char[] cs = new char[9]; for (byte i = 0, j = 0; i < 8; i++) { if (i == grid[8]) cs[j++] = ' '; cs[j++] = (char) (grid[i] + '0'); } System.out.println("-------"); System.out.format("%c %c %c\n", cs[0], cs[1], cs[2]); System.out.format("%c %c %c\n", cs[3], cs[4], cs[5]); System.out.format("%c %c %c\n", cs[6], cs[7], cs[8]); System.out.println("-------"); } public static void main(String[] args) { shuffle(); timer = System.currentTimeMillis(); System.out.println("name | nodes | len | time"); System.out.print("BFS "); new BFS().search(); timer = System.currentTimeMillis(); System.out.print("BiBFS"); new BiBFS().search(); timer = System.currentTimeMillis(); System.out.print("AStar"); new AStar().search(); } /* 随机数据运行结果: SRC DES ------- ------- 6 1 6 2 2 5 3 ---> 5 4 1 4 7 8 8 3 7 ------- ------- 相关统计: ----------------------------- name | nodes | len | time BFS | 155132 | 20 | 401 BiBFS | 2562 | 20 | 10 AStar | 646 | 20 | 0 ----------------------------- */
PS: 算法与API两手抓, 两手都要硬...
相关推荐
**八数码问题** 八数码问题(也称滑动拼图或15拼图)是一个经典的逻辑谜题,玩家需要通过移动空格处的方块,将初始排列的数字方块按照特定顺序排列好。在这个问题中,我们通常有一个2x3的网格,上面有1到15的数字和...
"八数码问题C语言代码" 该代码是使用 C 语言实现的八数码问题解决方案。八数码问题是一种经典的人工智能问题,目的是将一组初始状态的棋盘通过有限步骤变换为目标状态的棋盘。该代码使用了广度优先搜索算法来解决八...
《MATLAB实现A*算法解决八数码问题》 在人工智能领域,解决复杂问题的方法多种多样,其中搜索算法占据着重要地位。八数码问题(又称滑动拼图游戏)就是一个经典的搜索问题实例,它考验了算法的效率和智能程度。本文...
在这个实验报告中,它被应用于解决著名的八数码问题,也称为滑动拼图游戏。 八数码问题是一个典型的状态空间搜索问题,玩家需要通过移动一个空格与其他数字进行交换,将初始布局调整到目标布局。游戏的目标通常是将...
通过广度优先遍历解决八数码问题 在计算机科学和人工智能领域中,对于搜索问题的解决方法有很多,广度优先遍历(Breadth-First Search,BFS)是一种常用的方法。八数码问题(Eight Puzzle)是一种经典的搜索问题,...
数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。...解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。
八数码问题,又称滑动拼图游戏,是一个经典的计算机科学问题,主要研究如何通过最少的移动次数将一个打乱的拼图恢复到预设的目标状态。在这个问题中,通常使用一个3x3的网格,包含8个数字方块和一个空格。目标是通过...
启发式函数解决八数码问题 启发式函数解决八数码问题是人工智能领域中的一个经典问题,八数码问题是指在 3*3 九宫格棋盘上,摆有 8 个将牌,每一个将牌都刻有 1-8 数码中的某一个数码。棋盘中留有一个空格,允许其...
①使用深度优先搜索来解决八数码问题 ②使用广度优先搜索来解决八数码问题 ③使用过程式表示和实现八数码问题 以及相关代码详细注释 过程式知识表示是将有关某一问题领域的知识, 连同如何使用这些知识的方法,均...
**八数码问题(Eight Puzzle Problem)** 八数码问题是一个经典的逻辑谜题,源自19世纪的智力游戏“十五拼图”。在这个游戏中,我们有一个3x3的网格,其中8个格子内填充了数字1到8,还有一个空格。目标是通过在相邻...
湘潭大学人工智能实验状态空间法求解八数码问题 本文档包含湘潭大学人工智能课程实验之实验一:采用状态空间法求解八数码问题的详细内容。实验的主要目的是熟悉人工智能系统中的问题求解过程、状态空间的盲目搜索...
《八数码问题与A*算法实现详解》 八数码问题,又称滑动拼图或15拼图,是一个经典的计算机科学问题,属于图灵完全问题的范畴。它涉及到在一个3x3的网格上,通过空格与其他数字进行交换,使得初始布局最终转变为预设...
### 八数码问题Java程序实现解析 #### 一、八数码问题概述 八数码问题(也称为8拼图问题)是一种经典的搜索问题,在人工智能领域中常用来作为算法研究的例子。该问题在一个3×3的棋盘上进行,棋盘上有8个数字块(1...
A*算法是路径搜索领域中一种非常高效的启发式搜索算法,它在解决八数码问题时表现出色。八数码问题,又称滑动拼图游戏,是一个经典的计算机科学问题,玩家需要通过移动空格来重新排列一组数字,使得它们最终形成一个...
A* 算法解决八数码问题 A* 算法是一种启发式搜索算法,常用于解决复杂的问题。八数码问题是经典的搜索问题,目的是从初始状态到达目标状态,通过交换空格和数字达到目标状态。A* 算法可以高效地解决八数码问题。 A...
【八数码问题】,又称滑动拼图游戏,是一个经典的逻辑谜题,通过移动数字方块至目标布局来解决。本问题的实现涉及到三种搜索策略:回溯、图搜索和启发式算法。 **回溯策略**是基于深度优先搜索的算法,它采用递归的...
【八数码问题实验报告1】 本实验主要涉及的是在人工智能领域中的一种经典问题——八数码问题,使用C++编程语言实现A*算法进行求解。A*算法是一种启发式搜索算法,它结合了实际走过的代价(g(n))和预估到目标的代价...
八数码问题,又称滑动拼图游戏,是人工智能中经典的搜索问题之一,用于教授和理解状态空间搜索算法。本实验重点探讨了如何用C语言实现A*算法来解决八数码问题。 A*算法是一种启发式搜索算法,由人工智能先驱Peter ...
"八数码问题",也称为滑动拼图游戏,是人工智能领域中经典的搜索问题,用于演示和理解不同的路径搜索算法。在这个场景中,我们关注的是使用A*(A星)算法在MATLAB环境下实现该问题的版本。A*算法是一种启发式搜索...