1. 程式人生 > 實用技巧 >p1379 八數碼難題 洛谷

p1379 八數碼難題 洛谷

八數碼難題

題目描述

在3×3的棋盤上,擺有八個棋子,每個棋子上標有1至8的某一數字。棋盤中留有一個空格,空格用0來表示。空格周圍的棋子可以移到空格中。要求解的問題是:給出一種初始佈局(初始狀態)和目標佈局(為了使題目簡單,設目標狀態為123804765),找到一種最少步驟的移動方法,實現從初始佈局到目標佈局的轉變。

輸入格式

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

輸出格式

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

輸入輸出樣例

輸入 #1
283104765
輸出 #1
4
-------------------------------快樂的分割線----------------------------------


題目分析:
首先由題意可以知道我們的目的是尋找最少移動次數,注意/*最少*/,這表示我們可以藉助A*或者IDA*來解決這道題(其實這道題和騎士那個挺像的,都是以空格為初始節點求到達目標節點的值,這個題做完之後有興趣的同學可以看一下洛谷p2324)
回到這個題,我們從這個題中可以找出應用A*和IDA*的一般規律:

1.題目中給出限制條件:類似本題,題目中已經明確給出最終狀態

2.題目中要求最優解:本題就是尋找最少步驟的移動方法

這樣,當我們再次遇到類似題目時,優先考慮A*和IDA*演算法

題目詳解:

通過上面的分析,我們已經知道了基本演算法,那如何進行構造呢?

首先,我們發現題目中給出的是一維字串,那麼我們就應當把它構造成二維,讓它符合IDA*演算法的二維背景,所以:

cin>>s;
    for(int i=0;i<9;i++)
    {
        a[i/3+1][i%3+1]=s[i]-'0';
        if(s[i]-'0'==0) x=i/3+1,y=i%3+1;
    }

這樣一來,我們就把一維陣列建成了二維結構

其次,我們要生成一個目標陣列goal來進行如下操作:

1.判斷當前陣列是否到達最終目標

bool  check()
{
    for(int i=1;i<=3;i++)
    for(int j=1;j<=3;j++)
    {
        if(goal[i][j]!=a[i][j]) return
0;//表示不到達 } return 1;//表示到達 }

2.對最優解進行估計(構造估計函式)

bool test(int step)
{
    int cnt=0;
    for(int i=1;i<=3;i++)
    for(int j=1;j<=3;j++)
    if(a[i][j]!=goal[i][j])
    {
        if(++cnt+step>k) return 0;//相當於一個估計函式 
    }
    return 1;
}

對於估計函式,這裡詳細解釋一下。看上面程式碼,我們知道如果當前元素值和目標函式不相同,那麼最好的情況就是,對於每個元素,我們只交換一次就可以到達最終目標,但是這種情況只可能出現在最理想狀態中,實際問題裡面幾乎不會出現這種情況,所以我們稱這種情況為“最優情況”,對於IDA*演算法而言,我們規定了最大搜索範圍,如果我們對於當前情況進行預先估計,發現及時是在最理想的狀態下,最後結果也大於當前搜尋的最大層數,那麼我麼就可以直接放棄這種情況,減小時間複雜度

(不理解可以先去看一下IDA*的演算法基礎)

這樣,我們就完成了對於前置條件的設定,接下來就是IDA*演算法的實現了

void a_(int step,int x,int y,int fa)
{
    if(step==k) 
    {
        if(check()) flat=1;return ;
    }
    
    if(flat) return ;
    
    for(int i=0;i<4;i++)
    {
    int nx=x+dx[i],ny=y+dy[i];
    if(nx<1||nx>3||ny<1||ny>3||fa+i==3) continue;
    swap(a[x][y],a[nx][ny]);
    if(test(step)&&!flat) a_(step+1,nx,ny,i);
    swap(a[x][y],a[nx][ny]);
    }
    
}

通過這些函式的執行,我們最終就可以求出最小步驟

----------------------------------快樂的分割線----------------------------------------

完整程式碼

#include<bits/stdc++.h>
using namespace std;

string s;
int goal[4][4]={
    {0,0,0,0},
    {0,1,2,3},
    {0,8,0,4},
    {0,7,6,5}
};

int a[4][4];
int x,y;

bool flat;

int k;

int dx[4]={0,1,-1,0};
int dy[4]={1,0,0,-1};

bool  check()
{
    for(int i=1;i<=3;i++)
    for(int j=1;j<=3;j++)
    {
        if(goal[i][j]!=a[i][j]) return 0;
    }
    return 1;
}

bool test(int step)
{
    int cnt=0;
    for(int i=1;i<=3;i++)
    for(int j=1;j<=3;j++)
    if(a[i][j]!=goal[i][j])
    {
        if(++cnt+step>k) return 0;//相當於一個估計函式 
    }
    return 1;
}

void a_(int step,int x,int y,int fa)
{
    if(step==k) 
    {
        if(check()) flat=1;return ;
    }
    
    if(flat) return ;
    
    for(int i=0;i<4;i++)
    {
    int nx=x+dx[i],ny=y+dy[i];
    if(nx<1||nx>3||ny<1||ny>3||fa+i==3) continue;
    swap(a[x][y],a[nx][ny]);
    if(test(step)&&!flat) a_(step+1,nx,ny,i);
    swap(a[x][y],a[nx][ny]);
    }
    
}


int main()
{
    cin>>s;
    for(int i=0;i<9;i++)
    {
        a[i/3+1][i%3+1]=s[i]-'0';
        if(s[i]-'0'==0) x=i/3+1,y=i%3+1;
    }
    
    if(check()) 
    {
        cout<<0<<endl;
        return 0;
    }
    
    while(++k)
    {
        a_(0,x,y,-1);
        if(flat) 
        {
            cout<<k<<endl;
            break;
        }
    }
    
    return 0;
    
    
}

----------------END-------------------