一颗贪婪的星的博客

若非福祸,岂能相依

A*算法求解15数码问题

问题描述

利用A*算法进行表1到表2的转换,要求空白块移动次数最少。

转换规则为:空白块只可以与上下左右四个方向的相邻数字交换。

       表1 起始状态                     表2 目标状态

         

算法简介

A*算法是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。该算法综合了Best-First Search和Dijkstra算法的优点:在进行启发式搜索提高算法效率的同时,基于评估函数计算损失,保证找到一条最优路径。

算法能否找到最优解的关键在于评估函数的选择,A*算法的评估函数表示为:f(n) = g(n) + h(n)

f(n) 是从初始状态经由状态n到目标状态的代价估计

g(n) 是在状态空间中从初始状态到状态n的实际代价

h(n) 是从状态n到目标状态的最佳路径的估计代价

例如在8数码问题中,g(n) 表示状态空间树中搜索的层数,h(n) 表示状态n与目标状态中元素位置不同的元素个数。

算法步骤

设定两个集合,open集,close集

1.  将起始点加入open集(设置父亲节点为空)

2.  在open集中选着一个f(n)值最小的节点作为当前节点

    2.1 将当前节点从open集中移除,添加到close集

    2.2 如果当前节点为终点节点,那么结束搜索

    2.3 处理当前节点的所有邻接节点,规则如下:

       如果不在open集中,那么就将其添加到open集,并将该节点的父节点为当前节点

       如果已经添加到open集中,重新计算f(n)值,如果f(n)值小于先前的f(n)值,那么就更新open集中相应节点的f(n)

       如果该节点不可通过或者已经被添加到close集,那么不予处理

3、如果open集不为空,那么转到步骤2继续执行。


评估函数

1.   f(n) = 状态n状态空间树中的搜索深度 + 状态n与目标状态不同的元素个数

效果:与8数码问题使用了相同的评估函数,大概跑了30W步无法求出解

评价:效果极差,15数码问题的状态空间树要远复杂于8 数码问题,且15数码问题中空白块的移动更为复杂,此评估函数不适用。

2.   f(n) = 状态n状态空间树中的搜索深度 + 状态n与目标状态各个位置数字偏差的绝对值

效果:随着搜索的进行,空白块的移动集中在表格上部,表格下部几乎不移动 ,无法求出解

评价:因为下部数字较大,移动后差值较大造成评估值较大,因此搜索集中在了数值较小的部分,效果很差。

3.   f(n) = 状态n状态空间树中的搜索深度 + 状态n与目标状态各个元素的路径差值(一维数组各元素的距离差之和)

效果:空白块最终移动55步得到目标状态。

评价:效果比较理想,但h(n)还可继续优化。

4.   f(n) = 状态n状态空间树中的搜索深度 + 状态n与目标状态各个元素的曼哈顿距离

效果:空白块最终移动41步得到目标状态。

评价:效果理想。

实际上,1和2的评估函数效果大致相同,都将搜索局限在了一部分导致无法计算出问题的解。3实际是以一维数组各元素的距离差之和估计状态n到目标状态的曼哈顿距离,但此估计方式和计算平面两点的曼哈顿距离存在较大误差,因此只求解出可行解。

源代码(Java实现)

public class Node {
	private final static int goalMatrix[][] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 0 } }; // 奇序列
	private final static int init[] = { 11, 9, 4, 15, 1, 3, 0, 12, 7, 5, 8, 6, 13, 2, 10, 14 };
	private final static int initX = 1;
	private final static int initY = 2;
	public int data[];
	public float cost;
	public int level;
	public int x, y;
	public int preDirection;
	public Node pre;
	public Node() {
		data = new int[16];
		cost = 0;
		level = 0;
		x = 0;
		y = 0;
		preDirection = -1;
		pre = null;
	}
	public Node(Node node) {
		data = new int[16];
		for (int i = 0; i < data.length; i++) {
			data[i] = node.data[i];
		}
		cost = 0;
		level = node.level;
		x = node.x;
		y = node.y;
		preDirection = node.preDirection;
	}
	public void initData() {
		for (int i = 0; i < data.length; i++) {
			data[i] = init[i];
		}
		x = initX;
		y = initY;
		cost = level + costDistance(data, goalMatrix);
	}
	public int costDistance(int[] a, int[][] b) { // 计算二维数组元素之间的曼哈顿距离
		int c[][] = new int[4][4];
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				c[i][j] = a[4 * i + j];
			}
		}
		int cost = 0;
		boolean flag = false;
		for (int x1 = 0; x1 < 4; x1++) {
			for (int y1 = 0; y1 < 4; y1++) {
				flag = false;
				for (int x2 = 0; x2 < 4; x2++) {
					for (int y2 = 0; y2 < 4; y2++) {
						if (c[x1][y1] == b[x2][y2]) {
							cost += (Math.abs(x1 - x2) + Math.abs(y1 - y2));
							flag = true;
						}
						if (flag) {
							break;
						}
					}
					if (flag) {
						break;
					}
				}
			}
		}
		return cost;
	}
	public int hasSameData(List<Node> list) { // 判断重复状态
		boolean isFind = false;
		float minCost = 1000000000;
		for (int j = 0; j < list.size(); j++) {
			if (isSame(data, list.get(j).data)) {
				isFind = true;
				if (list.get(j).cost < minCost) {
					minCost = list.get(j).cost;
				}
			}
		}
		if (isFind) {
			for (int i = 0; i < list.size(); i++) {
				if (list.get(i).cost == minCost && isSame(data, list.get(i).data)) {
					return i;
				}
			}
		}
		return -1;
	}
	public boolean isSame(int[] a, int[] b) {
		boolean flag = true;
		for (int i = 0; i < a.length; i++) {
			if (a[i] != b[i]) {
				flag = false;
				break;
			}
		}
		return flag;
	}
	public Node change(int direction) { // 空白块移动
		Node node = new Node(this);
		int i = 0, j = 0;
		switch (direction) {
		case 0: // 上
			i = node.x - 1;
			j = node.y;
			node.preDirection = 1;
			break;
		case 1: // 下
			i = node.x + 1;
			j = node.y;
			node.preDirection = 0;
			break;
		case 2: // 左
			i = node.x;
			j = node.y - 1;
			node.preDirection = 3;
			break;
		case 3: // 右
			i = node.x;
			j = node.y + 1;
			node.preDirection = 2;
			break;
		default:
			break;
		}
		if (i >= 0 && i <= 3 && j >= 0 && j <= 3) { // 边界限定
			int temp = node.data[4 * node.x + node.y];
			node.data[4 * node.x + node.y] = node.data[4 * i + j];
			node.data[4 * i + j] = temp;
			node.x = i;
			node.y = j;
			node.level++;
			node.cost();
			node.pre = this;
		} else {
			return null;
		}
		return node;
	}
	public void cost() { // 计算评估函数
		cost = 0;
		cost += (level + costDistance(data, goalMatrix));
	}
	public boolean isFinish() {
		boolean result = true;
		for (int i = 0; i < data.length; i++) {
			if (data[i] != goal[i]) {
				result = false;
				break;
			}
		}
		return result;
	}
	public void show() {
		System.out.println("**************");
		for (int i = 0; i < data.length; i++) {
			System.out.printf("%-4d", data[i]);
			if ((i + 1) % 4 == 0) {
				System.out.println();
			}
		}
		System.out.println("level:" + level);
		System.out.println("cost:" + cost);
		System.out.println("**************");
	}
	public void finish() {
		Stack<Node> stack = new Stack<>();
		int steps = 0;
		stack.push(this);
		while (pre != null) {
			stack.push(pre);
			pre = pre.pre;
		}
		steps = stack.size() - 1;
		while (!stack.empty()) {
			stack.pop().show();
		}
		System.out.println("finish in " + steps + " steps");
	}
}
public class Main {
	public static void main(String[] a) {
		Node node = new Node();
		node.initData();
		function(node, new ArrayList<Node>());
	}
	static void function(Node n, List<Node> openList) {
		openList.add(n);
		while (!openList.isEmpty()) {
			int index = minIndex(openList);
			Node node = openList.get(index);
			openList.remove(index);
			if (node.isFinish()) {
				node.finish();
				return;
			} else {
				addNode(node, openList);
			}
		}
	}
	public static void addNode(Node n, List<Node> openList) {
		int direction = 0;
		while (direction < 4) {
			Node node = n.change(direction);
			if (node != null && n.preDirection != direction) {
				int indexOpen = node.hasSameData(openList);
				if (indexOpen >= 0) {
					if (node.cost < openList.get(indexOpen).cost) {
						openList.get(indexOpen).cost = node.cost;
						openList.get(indexOpen).pre = node.pre;
					}
				} else {
					openList.add(node);
				}
			}
			direction++;
		}
	}
	public static int minIndex(List<Node> list) {
		int index = 0;
		for (int i = 1; i < list.size(); i++) {
			if (list.get(i).cost < list.get(index).cost) {
				index = i;
			}
		}
		return index;
	}
}
阅读更多
文章标签: A*算法 15数码
个人分类: 搜索算法
想对作者说点什么? 我来说一句

15数码问题源代码(c++)

2011年12月22日 19KB 下载

使用A*算法实现8数码问题的求解

2012年10月27日 502KB 下载

没有更多推荐了,返回首页

不良信息举报

A*算法求解15数码问题

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭