1. 程式人生 > >廣度優先搜尋-八數碼問題

廣度優先搜尋-八數碼問題

演算法簡介:廣度優先搜尋

問題

給定一個一幅圖和一個起點s,回答“從s到給定的頂點v是否存在一條路徑?如果有,找出其中最短的那條(所含邊數最少)。“

思路

邊數最少,很自然想到從從經過1條邊能到達的節點有哪些?然後經過這些邊再到達的節點有哪些?這樣我不就能夠想出來最短的路徑了嗎?沒錯,這是非常簡單的想法,然而真正的廣度優先演算法也是這樣,簡單而有效。

解決方法

上面這幅圖我要找到從1到12的最短路徑,則我會從1經過1條邊可以到達的節點(2 3 4)搜尋,發現沒有,接下來搜尋節點(2 3 4)通過1條邊能夠到達的頂點(5 6 7 8),發現沒有,接下來搜尋節點(5 6 7 8),發現沒有,接下來搜尋節點(5 6 7 8)通過1條邊能夠到達的節點(9 10 11 12),搜尋到最後一個,發現搜尋到了。如果你每次經過一條邊則記錄加1,則現在經過了三條邊,即最短路徑是3條邊,

實現方案

當已經理解了演算法思想,接下來就應該實現了。最重要的一步是思考使用什麼樣的資料結構,想想這裡的搜尋一個集合,那自然就是遍歷他們,接下來還要遍歷他們通過一條邊能夠到達節點,普通的想法是先判斷他們,如果沒有,則再遍歷他們,然後生成的所有節點再加入一個新的集合當中。

問題:發現了沒,這裡遍歷了兩遍集合。還有就是新的節點加入一個新的集合,每次都要宣告新的陣列嗎?這是有多麻煩。

解決方案:這裡遍歷了兩遍,其實我先生成新的節點和後生成新的節點只是多佔用一點空間而已,我要是在遍歷第一個節點如果判斷不是就直接生成新的節點也可以,這樣就不用遍歷第二遍了,加入新的集合解決起來有點棘手,如果我可以仍然放在集合裡面繼續遍歷就好了,對!就是這樣,所以在集合裡會有順序的關係,不能我先遍歷第一個節點,接著直接遍歷新的節點,所以說我們的資料就要有順序之分,所以想想有什麼樣的資料儲存方式能夠有順序,常見的陣列就夠了,如果稍微學過資料結構,連結串列也可以。

程式設計的思路:遍歷陣列,從第一個節點開始,如果不是,則生成新節點加入到陣列的最後一個節點後面,所以如果是c語言,一定要將陣列宣告大一些。然後不斷遍歷即可,直到找到。

八數碼實現

問題

八數碼問題也稱為九宮問題。在3×3的棋盤,擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。要求解決的問題是:給出一個初始狀態和一個目標狀態,找出一種從初始轉變成目標狀態的移動棋子步數最少的移動步驟。所謂問題的一個狀態就是棋子在棋盤上的一種擺法。解八數碼問題實際上就是找出從初始狀態到達目標狀態所經過的一系列中間過渡狀態。

思路

移動棋子最少,當然是使用廣度優先搜尋來解決。所以這裡的每個節點就要是一個3
*3的矩陣。如果在c語言中,可以使用結構體。

struct node
{
    int xy[3][3];
};

實現方案

  • 空白棋子用0代替
  • 接受初始節點的資訊和目標節點的資訊
  • 找到空白棋子很簡單,直接遍歷就好,但是如何返回它的x和y座標,試著能不能使用一個數字代替,後來發現確實可以。
int loction(int num)
{
    int i;
    for (i = 0; i < 9; i++)
        if (sh[num].xy[i / 3][i % 3] == 0) return i;
}
  • 從初始節點開始判斷,然後擴充套件,即上下左右移動,當然我們要考慮具體的位置,比如說已經到邊界了,就不能越出邊界。還要考慮以前移動過的方向,所以記錄下來以前移動過的方向,可以直接加在結構體裡。(程式碼如下所示)每次擴充套件的節點就加在陣列後面。
struct node
{
    int xy[3][3];
    int dir;
};
  • 如何實現判斷相等,就是如何標誌該狀態的唯一性,方案1:9個數字拼接生成字串直接判斷是否相等。方案二:由於數也不大,所以直接使用9個數字直接生成一個數,使用long long 宣告生成的數字即可。
long long sign(int num)
{
    long long  sum;
    sum = sh[num].xy[0][0]*100000000 + sh[num].xy[0][1]*10000000 + sh[num].xy[0][2]*1000000 + sh[num].xy[1][0]*100000 + sh[num].xy[1][1]*10000 + sh[num].xy[1][2]*1000 + sh[num].xy[2][0]*100 + sh[num].xy[2][1]*10 + sh[num].xy[2][2];
    return sum;
}
  • 如果保證能夠到達目標節點,可以一直搜尋下去,如果不知道能不能,則可以設定搜尋次數。

程式碼框架

具體程式碼實現

#include<stdio.h>

struct node
{
    int xy[3][3];
    int dir;
};
struct node sh[102], end;
int count = 1;

void init()
{
    printf("輸入起始節點的位置:\n");
    int i, j;
    for (i = 0; i < 3; i++)
        for (j = 0; j < 3; j++)
            scanf("%d", &sh[0].xy[i][j]);
    sh[0].dir = -1;
    printf("輸入目標節點的位置:\n");
    for (i = 0; i < 3; i++)
        for (j = 0; j < 3; j++)
            scanf("%d", &sh[101].xy[i][j]);
    sh[101].dir = -1;
}

//找出0的位置
int loction(int num)
{
    int i;
    for (i = 0; i < 9; i++)
        if (sh[num].xy[i / 3][i % 3] == 0) return i;
}


//進行標記
long long sign(int num)
{
    long long  sum;
    sum = sh[num].xy[0][0]*100000000 + sh[num].xy[0][1]*10000000 + sh[num].xy[0][2]*1000000 + sh[num].xy[1][0]*100000 + sh[num].xy[1][1]*10000 + sh[num].xy[1][2]*1000 + sh[num].xy[2][0]*100 + sh[num].xy[2][1]*10 + sh[num].xy[2][2];
    return sum;
}

void mobile(int num)
{

    int temp;
    int loc;
    int up = 1, down = 1, left = 1, right = 1;
    loc = loction(num);
    int stand = sh[num].dir;
    //dir的0 1 2 3分別代表左 上 右 下
    if (loc / 3 != 0 && stand != 1)
    {
        sh[count] = sh[num];
        temp = sh[count].xy[loc / 3][loc % 3];
        sh[count].xy[loc / 3][loc % 3] = sh[count].xy[loc / 3 - 1][loc % 3];
        sh[count].xy[loc / 3 - 1][loc % 3] = temp;
        sh[count].dir = 3;
        count++;
    };
    if (loc / 3 != 2 && stand != 3)
    {
        sh[count] = sh[num];
        temp = sh[count].xy[loc / 3][loc % 3];
        sh[count].xy[loc / 3][loc % 3] = sh[count].xy[loc / 3 + 1][loc % 3];
        sh[count].xy[loc / 3 + 1][loc % 3] = temp;
        sh[count].dir = 1;
        count++;
    }
    if (loc % 3 != 0 && stand != 0)
    {
        sh[count] = sh[num];
        temp = sh[count].xy[loc / 3][loc % 3];
        sh[count].xy[loc / 3][loc % 3] = sh[count].xy[loc / 3][loc % 3 - 1];
        sh[count].xy[loc / 3][loc % 3 - 1] = temp;
        sh[count].dir = 2;
        count++;
    }
    if (loc % 3 != 2 && stand != 2)
    {
        sh[count] = sh[num];
        temp = sh[count].xy[loc / 3][loc % 3];
        sh[count].xy[loc / 3][loc % 3] = sh[count].xy[loc / 3][loc % 3 + 1];
        sh[count].xy[loc / 3][loc % 3 + 1] = temp;
        sh[count].dir = 0;
        count++;
    }

}
void display(int num)
{
    int i, j;
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
            printf("%d ", sh[num].xy[i][j]);
        printf("\n");
    }
}

int search()
{
    int i = 0;
    while (1)
    {
        printf("\n");
        display(i);
        printf("\n");
        if (i == 100)
        {
            printf("超出了上限次數\n");
            return 0;
        }
        if (sign(i) == sign(101))
        {
            printf("在第%d次找到了", i);
            display(i);
            return i;
        }
        mobile(i);
        i++;
    }
}

int main()
{
    init();
    search();
    return 0;
}
//測試用例
/*
2 8 3
1 6 4
7 0 5
1 2 3
7 8 4
0 6 5
*/

參考資料:Sedgewick R, Wayne K D. Algorithms. 4th[J]. 2011.