无聊的时候用java实现的推箱子算法~

相信对于推箱子这种东西大家都一定很熟悉了吧,本人身为一个宅男最近迷上了一款叫做游戏俱乐部的game,听上去是不是很高大上的样子。。。结果----这TM不就是个推箱子么!!!


当时被这个关卡卡的欲仙欲死。。。所以果断拿出了自己大学的存货,自动推箱子搞起!

-------------------------------------------------------------------无情的分割线-------------------------------------------------------------------

废话说了这么多不会被打死吧。。。

正题开始,首先是思路:

first:做过ACM的一看就知道这TM不就是个图论的题目么,所以一开始自己想到的就是进行DFS(深度优先搜索),存储每次走过的地图肯定是必要的,用于防止进行重复的搜索。然后。。。然后就是让他自己搜吧。。。虽然觉得很不靠谱,走一步算一步是不。

关于地图的存储,因为有一部分元素是可以重叠放置的,所以用了一个类似二进制的存储方式,就是4种物件分别有是否存在状态,使得用一个数字可以表示多个物件。


1:是否存在目的地

2:是否存在箱子

4:是否存在人

8:是否存在墙壁


这样就解决了地图存储问题。使用short[][]就存下了(这里没有做更小的存储了,本来用java不用c++就是怕麻烦不是。。。)

之后用这个思路进行了一系列思考,发现数据量还是太大了,一步一步慢慢走简直不能忍,随随便便就爆表了,所以漫漫优化之路开始了。


1.在不移动箱子的情况下其实无论人在哪里对于map来说是没有影响的,所以填充可移动区域可以让需要存储地图的数量有一个大的下降。例如之前那副地图:

8888888
8103018
8002008
8320238
8012108
8403008
8888888

经过变换之后就成了:

8888888
8103018
8002008
8320238
8452108
8443008
8888888

这样就把存储量缩小了四分之三。至于怎样填充,相信对图论有一点了解的都可以随便想出方案,我这里用的是BFS:

/**
	 * 填充可移动位置
	 */
	public void findStart() {
		//位置队列
		Queue<Part> manList = new LinkedList<Part>();
		for (short i = 0; i < H; i++) {
			for (short j = 0; j < W; j++) {
				//能站人的地方都加入队列
				if (map[i][j] == 4 || map[i][j] == 5) {
					manList.offer(new Part(i, j));
				}
				//这里用于同时记录箱子的位置,以后会用到,用于减少遍历次数
				if (map[i][j] == 2 || map[i][j] == 3) {
					boxList.offer(new Part(i, j));
				}
			}
		}
		//提出没一个站立位置并将其周围填充之后加入队列
		while (!manList.isEmpty()) {
			Part temp = manList.poll();
			fillMovable(manList, (short) (temp.x - 1), temp.y);
			fillMovable(manList, (short) (temp.x + 1), temp.y);
			fillMovable(manList, temp.x, (short) (temp.y - 1));
			fillMovable(manList, temp.x, (short) (temp.y + 1));
		}
	}


2.关于箱子的移动方式, alai04的博文给了我启示,类似于八皇后问题,直接用整幅地图的BFS搜索会比较靠谱。因为可以确定箱子的位置和在不移动箱子情况下人能到的位置,所以箱子可移动的位置也就能确定了,再加上之前存储的所有箱子的位置,这样就能计算出箱子每动作一次地图能更新的情况。

一次BFS就是每个箱子往不同可移动位置进行一次移动。

/**
	 * 获取子节点地图(进行一次BFS搜索)
	 * 
	 * @return
	 */
	public LinkedList<Map> getChildMap() {
		LinkedList<Map> list = new LinkedList<Map>();
		for (Part part : boxList) {
			Map temp;
			temp = move(part, -1, 0);
			if (null != temp)
				list.offer(temp);
			temp = move(part, 1, 0);
			if (null != temp)
				list.offer(temp);
			temp = move(part, 0, -1);
			if (null != temp)
				list.offer(temp);
			temp = move(part, 0, 1);
			if (null != temp)
				list.offer(temp);
		}
		return list;
	}

	/**
	 * 移动箱子
	 * 
	 * @param box
	 * @param x
	 * @param y
	 * @return
	 */
	private Map move(Part box, int x, int y) {
		Map temp = null;
		//如果箱子可以向当前方向移动
		if ((map[box.x + x][box.y + y] == 0 || map[box.x + x][box.y + y] == 1
				|| map[box.x + x][box.y + y] == 4 || map[box.x + x][box.y + y] == 5)
				&& (map[box.x - x][box.y - y] == 4 || map[box.x - x][box.y - y] == 5)) {
			temp = this.clone();
			// TODO待优化
			for (int i = 0; i < H; i++) {
				for (int j = 0; j < W; j++) {
					if (temp.map[i][j] == 4 || temp.map[i][j] == 5) {
						temp.map[i][j] -= 4;
					}
				}
			}
			temp.map[box.x][box.y] += 4;
			temp.map[box.x][box.y] -= 2;
			temp.map[box.x + x][box.y + y] += 2;
			//因为箱子已经移动,所以需要重新计算可以站立的位置
			temp.fatherMap = this;
			temp.findStart();
			temp.getCode();
		}
		return temp;
	}

3.至于什么时候结束搜索,基本上就只有三种情况:

所有目的地被填充完毕-------计算完成退出程序。

有箱子被推到角落并且不是在目的地--------说明不是正确的路线,搜索不再往下走。

当前地图在以前已经被达成过--------说明是重复路线,搜索不再往下走。


4.关于地图的存储,因为要保证不能重复,我是用的是hashSet,并重写了equals和hashCode的实现,用来自动判断地图是否重复。

<span style="white-space:pre">	</span>public void getCode() {
		hashCode = 0;
		int i = 0;
		for (short[] temp : map) {
			for (short temp2 : temp) {
				hashCode += ((int) Math.pow(7, NUM - i)) * temp2;
				++i;
			}
		}
	}
<span style="white-space:pre">	</span>@Override
	public int hashCode() {
		return hashCode;
	}

	@Override
	public boolean equals(Object obj) {
		Map o = (Map) obj;
		if (this.hashCode != o.hashCode)
			return false;
		boolean flag = true;
		for (int i = 0; i < H; i++) {
			for (int j = 0; j < W; j++) {
				if (o.map[i][j] != this.map[i][j]) {
					flag = false;
					break;
				}
			}
			if (!flag)
				break;
		}
		return flag;
	}

最后就是完成之后的地图显示问题,我的解决方案就是每个节点存储自己父亲节点的地址,当节点发现自己已经完成之后根据地址向上查找直到树顶。


好了,java版的推箱子就这么完成了~~试了一下效率还不错~基本上java解不出来的c++估计也悬,时间复杂度主要取决于箱子的数量与空地的数量。话说做完这么一个东西还是蛮有成就感的说。

本人第一次写这种博文,有什么意见或者想法欢迎来交流~~

bye~bye~~

源码下载地址:http://download.csdn.net/detail/u010236185/8239077

请原谅我放荡不羁的要了1分。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值