1. 程式人生 > >藍橋杯 歷屆試題-九宮重排

藍橋杯 歷屆試題-九宮重排

問題描述

  如下面第一個圖的九宮格中,放著 1~8 的數字卡片,還有一個格子空著。與空格子相鄰的格子中的卡片可以移動到空格中。經過若干次移動,可以形成第二個圖所示的局面。
jgcp1jgcp2
  我們把第一個圖的局面記為: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就能解決,但這中間會碰到一些問題,像用散列表實現標記陣列,還是挺有意思的。