藍橋杯 歷屆試題-九宮重排
阿新 • • 發佈:2019-01-08
問題描述
如下面第一個圖的九宮格中,放著 1~8 的數字卡片,還有一個格子空著。與空格子相鄰的格子中的卡片可以移動到空格中。經過若干次移動,可以形成第二個圖所示的局面。
我們把第一個圖的局面記為:12345678.
把第二個圖的局面記為:123.46758
顯然是按從上到下,從左到右的順序記錄數字,空格記為句點。
本題目的任務是已知九宮的初態和終態,求最少經過多少步的移動可以到達。如果無論多少步都無法到達,則輸出-1。
解題思路
看起來非常像一個全排列問題,但這道題不是。
首先這道題要求的狀態的轉換是有限制要求的,即每一次移動只能移動空格和空格的上下左右,且與他移動的還必須存在,即不能越界。其次這道題要求的是最短路徑,想到這裡就很明顯了,把當前9個數的位置資訊當成一個節點,通過移動格子能轉換到的狀態也就是他的鄰接點,把它當成一副圖用bfs來做就ok了。
不過要注意一點就是其實不需要建圖,每一個狀態的相鄰節點都能通過空格的位置推出。
不過這道題有一個坑點,如何判斷一個節點是否已經訪問過,這很重要,如果沒法判斷,那麼程式一旦碰到環就會死迴圈,那就建個數組標記一下,可是怎麼標記啊,一個節點有9個資訊,總不能用9維的陣列吧。所以一個好的解決方法就是用散列表,這樣問題就解決了。
程式要求最小步數,那麼就在第一次訪問到每一節點時標記一下從誰訪問的,最後直接從終止節點開始一路推到開始節點就可以了。
程式碼
#include <iostream>
#include <cstring>
#define QMAX 370000
#define HASHMAX 10000
int htable[HASHMAX];
int next[QMAX];
int queue[QMAX][9];
int zeros[QMAX];
int vis[QMAX];
int front;
int rear;
int result[9];
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
int pre[QMAX];
int hash(int pos)
{
int h = 0;
for(int i = 0; i < 9; i++)
{
h = h*10+queue[pos][i];
h%=HASHMAX;
}
return h;
}
bool insert(int pos)
{
int h = hash(pos);
for(int n = htable[h]; n!=-1; n=next[n])
{
if(memcmp(queue[n], queue[pos], sizeof(int)*9) == 0)
return false;
}
next[pos] = htable[h];
htable[h] = pos;
return true;
}
int bfs()
{
pre[0] = -1;
while(front < rear)
{
int *cur = queue[front++];
if(memcmp(cur, result, sizeof(int)*9) == 0)
return front-1;
int zx = zeros[front-1]/3;
int zy = zeros[front-1]%3;
for(int i = 0; i < 4; i++)
{
int nx = zx+dx[i];
int ny = zy+dy[i];
if(nx < 0 || nx >=3 || ny < 0 || ny >= 3)
continue;
int (*next)[3] = (int (*)[3])queue[rear++];
memcpy((int *)next, cur, sizeof(int)*9);
next[zx][zy] = next[nx][ny];
next[nx][ny] = 0;
zeros[rear-1] = nx*3+ny;
pre[rear-1] = front-1;
if(!insert(rear-1))
rear--;
}
}
return -1;
}
int main()
{
memset(htable, -1, sizeof(htable));
for(int i = 0; i < 9; i++)
{
char ch;
std::cin >> ch;
if(ch == '.') {
queue[rear][i] = 0;
zeros[rear] = i;
}
else
queue[rear][i] = ch-'0';
}
rear++;
for(int i = 0; i < 9; i++)
{
char ch;
std::cin >> ch;
if(ch == '.')
result[i] = 0;
else
result[i] = ch-'0';
}
int tot = -1;
for(int p = bfs(); p!=-1; p=pre[p])
tot++;
std::cout << tot << std::endl;
}
總的來說這道題就是一個水題,雖然當時自己也做了半天。現在看來這道題其實內容很簡單,一個無向無權圖的最短路徑,直接用bfs就能解決,但這中間會碰到一些問題,像用散列表實現標記陣列,還是挺有意思的。