算法設計與分析 第四周
滑動謎題
1.題目描述
2.選題原因
上一週做了圖算法,本週學習了廣度優先與深度優先,恰好上課遇到了
8數碼
問題,在搜索題庫後發現,有一道滑動謎題
,類似於5數碼問題
,恰好是困難難度,因此想要嘗試求解。
3.題目分析
廣度優先求解問題的算法思路較爲簡單清晰,完全靠着暴力求解即可。本題目中,每一張圖都用了一個
二維vector
來表示。如果按照廣度優先的思路,這麼多vector
,很容易爆炸,並且如此多的可能性,很難標記那些節點是已經到達過的。如果直接拿vector
來比較,時間複雜度將會是十分巨大的。
在bfs處理中,常常會採取使用使用數組標記的方法來對應不同的節點,這給了我們很好的思路。因爲相比較於存儲720個vector<vector<int> >
,並且不斷地遍歷查詢來說,使用數組,並且直接通過角標來訪問要快速很多。因此我們的問題轉換爲了如何將每一幅圖一一映射到相對短的整數數組上。
最簡單的方法當然是就按照順序來存,每一位對應好個、十、百位,例如:[(1, 2, 3); (4, 5, 0)]
就轉化爲了123450
。當然,這樣存儲的數據量太過於大,因此,我們可以將10進制
化爲6進制
來存儲。
因此,對於排列:a b c d e f
=>a * 6 ^ 6 + b * 6 ^ 5 + c * 6 ^ 4 + d * 6 ^ 3 + e * 6 ^ 2 + f * 6 ^ 1
就將大小縮小到了原來的(6/10)^6
,約等於原先的0.0467
,大大縮短了長度。
4.算法
按照原先的思路,我們只需要按部就班的進行即可。
節點的數據結構包括4位int
分別是:圖的代號
到達當前圖經歷了多少步
0所在的x座標
0所在的y座標
在以排列操作的時候,將二維轉換爲一維,轉化後b~[x * 3 + y] = b[x][y]
- 1.初始化一個數組(6 ^ 7)爲
0
,用來標記節點是否被探查過。- 2.將初始圖轉化成節點。
- 3.創建一個存儲節點的隊列。
- 4.將初始節點放入隊列,並標記該節點探尋過,進入循環。
- 5.循環條件:隊列不爲空
- 5.1取出頭節點,當前節點所代表的值是否等於11190(1 2 3 4 5 0 代表值),若等於,則直接返回該節點的步驟值;否則繼續。
- 5.2判斷是否能夠左移,如果能夠,則構造左移後的節點,放入隊列。
- 5.3判斷是否能夠右移,如果能夠,則構造右移後的節點,放入隊列。
- 5.4判斷是否能夠上移,如果能夠,則構造上移後的節點,放入隊列。
- 5.5判斷是否能夠下移,如果能夠,則構造下移後的節點,放入隊列。
- 5.6將隊列首踢出隊列
- 6.若循環結束還沒有返回值,說明無解,返回
-1
5.核心代碼
5.1排列與代表值轉換公式
排列到代表值
int now = board[0][0] * 7776 + board[0][1] * 1296 + board[0][2] * 216 + board[1][0] * 36 + board[1][1] * 6 + board[1][2] * 1;
代表值到排列
int temp = bfs.front()[0];
trans.push_back(temp / 7776);
temp %= 7776;
trans.push_back(temp / 1296);
temp %= 1296;
trans.push_back(temp / 216);
temp %= 216;
trans.push_back(temp / 36);
temp %= 36;
trans.push_back(temp / 6);
temp %= 6;
trans.push_back(temp);
5.2判斷是否能夠移動並添加值
//測試左移
if (y != 0) {
vector<int> left(4);
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 + y - 1];
trans[x * 3 + y - 1] = temp;
int leftValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[leftValue]) {
test[leftValue] = true;
left[0] = leftValue;
left[1] = step + 1;
left[2] = x;
left[3] = y - 1;
bfs.push(left);
}
//復原
trans[x * 3 + y - 1] = trans[x * 3 + y];
trans[x * 3 + y] = temp;
}
5.3初版結果
結果發現,我們的程序運行得很慢,還需要繼續優化。
6.優化
6.1存儲優化
首先,將存儲了各個節點狀態的數組由
int
數組變爲bool
數組,將存儲減少了32倍。
bool test[46656] = {false}; //用來判斷圖形是否被放入queue
6.2數據結構優化
將原先一維
vector<int>
存儲的排列改爲int
數組。
//還原數組
int trans[6]; //存儲queue中的首元素
int temp = bfs.front()[0];
trans[0] = temp / 7776;
temp %= 7776;
trans[1] = temp / 1296;
temp %= 1296;
trans[2] = temp / 216;
temp %= 216;
trans[3] = temp / 36;
temp %= 36;
trans[4] = temp / 6;
temp %= 6;
trans[5] = temp;
6.3訪問優化
通過
queue.front()
來訪問首元素需要很多的複雜度,因此通過直接通過四次訪問將首元素的值拷貝下來,下次直接使用這四個值,來節省訪問隊列的開銷。
int step = bfs.front()[1];
int x = bfs.front()[2];
int y = bfs.front()[3];
7.最終結果
8.源代碼
class Solution {
public:
int slidingPuzzle(vector<vector<int>>& board) {
//將圖形轉換爲數字,每一幅圖都轉換成一個數字,公式爲 第一位 * 6^6 + 第二位 * 6 ^ 5.......
//每個圖像存儲在一個vector<int> (4) 中,第一位是代號;第二位是當前走了多少步;第三位和第四位是 0 座標
bool test[46656] = {false}; //用來判斷圖形是否被放入queue
int now = board[0][0] * 7776 + board[0][1] * 1296 + board[0][2] * 216 + board[1][0] * 36 + board[1][1] * 6 + board[1][2] * 1;
int end = 11190; //[1 2 3 4 5 0] 圖形的數字代號
vector<int> nowValue(4); //構造首個
nowValue[0] = now;
nowValue[1] = 0;
for (int i = 0; i < 2; i++) { //定位 0 座標
for (int j = 0; j < 3; j++) {
if (board[i][j] == 0) {
nowValue[2] = i;
nowValue[3] = j;
}
}
}
queue<vector<int> > bfs;
bfs.push(nowValue);
while (!bfs.empty()) { //廣度優先搜索
if (bfs.front()[0] == end) { //達成
return bfs.front()[1];
} else {
//從代號得到圖形,存儲在trans中
int trans[6]; //存儲queue中的首元素
int temp = bfs.front()[0];
trans[0] = temp / 7776;
temp %= 7776;
trans[1] = temp / 1296;
temp %= 1296;
trans[2] = temp / 216;
temp %= 216;
trans[3] = temp / 36;
temp %= 36;
trans[4] = temp / 6;
temp %= 6;
trans[5] = temp;
int step = bfs.front()[1];
int x = bfs.front()[2];
int y = bfs.front()[3];
//測試左移
if (y != 0) {
vector<int> left(4);
//左移變換
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 + y - 1];
trans[x * 3 + y - 1] = temp;
//計算值
int leftValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[leftValue]) {
test[leftValue] = true;
left[0] = leftValue;
left[1] = step + 1;
left[2] = x;
left[3] = y - 1;
bfs.push(left);
}
//復原
trans[x * 3 + y - 1] = trans[x * 3 + y];
trans[x * 3 + y] = temp;
}
//測試右移
if (y != 2) {
vector<int> right(4);
//右移變換
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 + y + 1];
trans[x * 3 + y + 1] = temp;
//計算值
int rightValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[rightValue]) {
test[rightValue] = true;
right[0] = rightValue;
right[1] = step + 1;
right[2] = x;
right[3] = y + 1;
bfs.push(right);
}
//復原
trans[x * 3 + y + 1] = trans[x * 3 + y];
trans[x * 3 + y] = temp;
}
//測試上移
if (x == 1) {
vector<int> up(4);
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 - 3 + y];
trans[x * 3 - 3 + y] = temp;
int upValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[upValue]) {
test[upValue] = true;
up[0] = upValue;
up[1] = step + 1;
up[2] = x - 1;
up[3] = y;
bfs.push(up);
} //復原
trans[x * 3 - 3 + y] = trans[x * 3 + y];
trans[x * 3 + y] = temp;
}
//測試下移
if (x == 0) {
vector<int> down(4);
temp = trans[x * 3 + y];
trans[x * 3 + y] = trans[x * 3 + 3 + y];
trans[x * 3 + 3 + y] = temp;
int downValue = trans[0] * 7776 + trans[1] * 1296 + trans[2] * 216 + trans[3] * 36 + trans[4] * 6 + trans[5] * 1;
if (!test[downValue]) {
test[downValue] = true;
down[0] = downValue;
down[1] = step + 1;
down[2] = x + 1;
down[3] = y;
bfs.push(down);
}
}
bfs.pop();
}
}
return -1;
}
};