1. 程式人生 > >無聊的時候用java實現的推箱子演算法~

無聊的時候用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~~

請原諒我放蕩不羈的要了1分。。。