1. 程式人生 > >洛谷P1379八數碼難題

洛谷P1379八數碼難題

題目描述

在3×3的棋盤上,擺有八個棋子,每個棋子上標有1至8的某一數字。棋盤中留有一個空格,空格用0來表示。空格周圍的棋子可以移到空格中。

要求解的問題是:給出一種初始佈局(初始狀態)和目標佈局(為了使題目簡單,設目標狀態為123804765)。

找到一種最少步驟的移動方法,實現從初始佈局到目標佈局的轉變。

輸入輸出格式

輸入格式:

輸入初始狀態,一行九個數字,空格用0表示

輸出格式:

只有一行,該行只有一個數字,表示從初始狀態到目標狀態需要的最少移動次數(測試資料中無特殊無法到達目標狀態資料)

輸入輸出樣例

輸入:

283104765

輸出:

4

分析:

八數碼難題,對於我這個C++蒟蒻來說還真有點難,在經過dalao的指點後終於AC了這道題.

先將思路,題面很短相信大家都能看懂,雖然我的程式碼較長但是應該是最好理解的吧,由題目得知我們有9個數,

其中有一個數字0剩下的數字都能和0交換位置即在數字0能和它的上下左右交換位置(在不超出邊界的情況下),

然後我們需要知道當一個矩陣最後交換到"123804765"這種情況的最小步數,最小又是矩陣從這一點我們可以判斷出,

此題很有可能用BFS來寫,那麼問題又來了我們怎麼標記?轉換成矩陣標記?不不不那樣就太麻煩了而且程式碼也不好實現,

再看題"123804765"其實這已經提示我們可以用字串來標記,那麼怎麼標記,我們可以用map<string,bool> vis來實現這個標記功能,

用map就能存下字元型的陣列了所以我們每次僅需要判斷vis[XX]是否走過就可以完成標記了.

接下來將搜尋的實現.

標記我們知道怎麼實現,那麼關於這道題的搜尋又怎麼實現?

我們可以由題目得知每個數字只能和0進行交換,我們就可以想到我們可以用0進行對矩陣中每個數字進行更新我們可以用swap實現,

對兩個數字進行交換,因為能進行數字交換隻能是單個字元的操作所以我們就又得將這些字元全部壓進一個新的字串裡這裡使用

字串.push_back(字元)即可實現,然後將新的字串進行判斷是否和"123804765"相同如果相同我們就輸出當前的步數,由BFS的最優性

如果滿足當前的情況的那就是最優解,注意這裡一定要想清楚因為我們每次交換一次每次都更新整個字串所以當字串重複時這個情況是不滿足的

所我們還要將字元swap回去,當然因為每次交換都會使Map的值發生改變,所以我們也必須swap回去,知道了這些這道題寫起來就比較容易了.

在搜尋開始前我們當然要進行對這個字串的預處理當然這個要實現很簡單我在這裡就不不解釋了

更詳細的理解請看程式碼

程式碼:

 

#include <iostream>
#include <cstring>
#include <string>
#include <queue>
#include <map>
using namespace std;
int startx,starty,posx=1,posy=1;//我這個變數名這麼清楚可能沒人不懂對吧
string s;
char Map[10][10];
int dirx[4]={1,0,0,-1};//因為只有四個方向
int diry[4]={0,1,-1,0};
map<string,bool> vis;//這裡我們用map來進行判斷重複
struct Node //結構體來存數
{
    int x,y,t;
    string s;//這裡我們宣告s來進行判斷和更新
};
void bfs()
{
    vis[s]=true;//起點肯定要賦值為true啊
    queue<struct Node> que;
    struct Node now;
    now.x=startx;now.y=starty;now.t=0;now.s=s;
    que.push(now);
    while(!que.empty())
    {
        now=que.front();
        que.pop();
        posx=1,posy=1;
        for(int i=0;i<s.length();i++)//其實這裡寫在下面比較好理解但不影響
        {
            Map[posx][posy]=now.s[i];//這裡的操作就是我們將我們每次更新完的字串再用MAP重新賦值再就進行搜尋
            posy++;
            if(posy==4)//如果這一排滿了
            {
                posy=1;//換下一排
                posx++;
            }
        }
        if(now.s=="123804765")//如果滿足情況
        {
            cout<<now.t<<endl;//輸出因為BFS最優的特點我們就可以直接輸出
            return;
        }
        for(int i=0;i<4;i++)
        {
            int xx=dirx[i]+now.x;
            int yy=diry[i]+now.y;
            if(xx<1||xx>3||yy<1||yy>3) continue;//邊界處理
            swap(Map[now.x][now.y],Map[xx][yy]);
            string change;//這裡我們需要重新宣告一個字串來進行判斷和更新操作
            for(int i=1;i<=3;i++)//因為3*3的矩陣
            {
                for(int j=1;j<=3;j++)
                change.push_back(Map[i][j]);//將字元全部壓進字串裡
            }
            if(vis[change])//這裡需要注意這裡很重要當它為重複的情況我們還要swap回去才能continue
            {//因為如果我們當前這個不滿足情況的值會改變會對後面的搜尋產生干擾所以必須swap回去才能continue
                swap(Map[xx][yy],Map[now.x][now.y]);
                continue;
            }
            vis[change]=true;
            swap(Map[xx][yy],Map[now.x][now.y]);//這裡和上面那個是差不多的意思這裡因為我們這個BFS和以往的不同因為我們直接是將答案進行了跟新,所以如果我們不更新為上一個狀態那會對下一個迴圈產生影響這個地方難理解需要好好想一下
            struct Node next;
            next.x=xx;next.y=yy;next.s=change;next.t=now.t+1;//這裡BFS的基本操作
            que.push(next);
        }
    }
    return;
}
int main()
{
    cin>>s;
    for(int i=0;i<s.length();i++)//預處理
    {
        Map[posx][posy]=s[i];
        if(Map[posx][posy]=='0')//0為起點
        {
            startx=posx;//找到它的座標
            starty=posy; 
        }
        posy++;
        if(posy==4)//當這排y為4時這排就滿了因為我是從1開始的所以要到4
        {
            posy=1;//posy賦值為1
            posx++;//進入到下一排
        }
    }
    bfs();
    return 0;
}