1. 程式人生 > >馬踏棋盤問題 — 深搜和貪心演算法

馬踏棋盤問題 — 深搜和貪心演算法

同學面試阿里,被問到了馬踏棋盤的問題,作為非計算機專業的門外漢,完全沒有聽說過,只聽說過馬踏飛燕。抓緊去搜了一下,發現還是個經典演算法,題目是這樣的:

國際象棋的棋盤為8*8的方格棋盤。現將”馬”放在任意指定的方格中,按照”馬”走棋的規則將”馬”進行移動。要求每個方格只能進入一次,最終使得”馬”走遍棋盤的64個方格。

這個問題一般有兩種思路來解決,一種就是用深度優先搜尋,採用遞迴+回溯的方式,一個棋盤可以看成有64層深度的一棵樹,每一個節點最多有8個子節點,採用深搜可以很方便的解決這個問題,但是深搜這個方法時間複雜度太高了,最多要搜8^64次(實際存在邊界和已經訪問的標記,雖然不會這麼多次,也是很大),太過盲目;

還有一種方式就是採用貪心演算法,每次選擇下一步的時候,不像深搜那樣,每次都沿著一圈8個方向順序搜,瞎眼走到黑,而是採用貪心演算法,選擇眼前認為最優的點。這裡什麼是最優的點,就是選擇後續節點最少的那一個,哪一個點的下一步少,就選哪一個。基於貪心演算法可以快速的得搜尋到結果,效率提高很多。

下面是自己除錯的程式碼,採用遞迴的深搜演算法和在此基礎上改進的貪心演算法:

#include <iostream>
#include <vector>
using namespace std;
int move_x[8] = { 1, 2, 2, 1, -1, -2, -2, -1
}; int move_y[8] = { 2, 1, -1, -2, -2, -1, 1, 2 }; void Judge(vector<vector<int>>& pan, vector<vector<int>>& flag, vector<vector<int>>& path, int m, int n, int& edge, int count, int& found) { if (found) //設定找到一次就可以退出了 return; if
(count >= edge*edge) //當記錄次數等於8*8=64次,說明馬已經踏過了所有棋盤 { found += 1; //把記錄的路徑儲存在path中 for (int i = 0; i < edge; i++) { for (int j = 0; j < edge; j++) path[i][j] = flag[i][j]; } return; } if (m > edge-1 || n > edge-1 || m < 0 || n < 0 || flag[m][n] != 0) //邊界條件,出了邊界就退回,懸崖勒馬 return; count++; //滿足在邊界之內,沒有被訪問過,計數加1 flag[m][n] = count; //對應節點記錄訪問順序,第幾次被訪問的 for (int i = 0; i < 8;i++)//八個方向順序搜尋 Judge(pan, flag, path, m + move_x[i], n + move_y[i], edge, count, found); //採用遞迴,進行下一步的搜尋 flag[m][n] = 0; //回溯,對應節點標記清零 } int main() { int m,n; int edge; int found = 0; cin >> edge>> m >> n; vector<vector<int>> pan(edge, vector<int>(edge, 0)); vector<vector<int>> flag(edge, vector<int>(edge, 0)); vector<vector<int>> path(edge, vector<int>(edge, 0)); Judge(pan, flag, path, m, n, edge, 0, found); cout << found<<endl; for (int i = 0; i < edge; i++) { for (int j = 0; j < edge; j++) cout << path[i][j] << " "; cout << endl; } system("pause"); return 0; }

在深搜的基礎上加入貪心演算法,在選擇下一步時候加入了判斷

#include <iostream>
#include <vector>
using namespace std;

int move_x[8] = { 1, 2, 2, 1, -1, -2, -2, -1 }; 
int move_y[8] = { 2, 1, -1, -2, -2, -1, 1, 2 };

void Judge(vector<vector<int>>& pan, vector<vector<int>>& flag, vector<vector<int>>& path, int m, int n, int& edge, int count, int& found)
{
    if (found) //設定找到一次就可以退出了
        return;

    if (count >= edge*edge) //當記錄次數等於8*8=64次,說明馬已經踏過了所有棋盤
    {
        found += 1; 
        //把記錄的路徑儲存在path中
        for (int i = 0; i < edge; i++)
        {
            for (int j = 0; j < edge; j++)
                path[i][j] = flag[i][j];
        }
        return;
    }

    if (m > edge-1 || n > edge-1 || m < 0 || n < 0 || flag[m][n] != 0) //邊界條件,出了邊界就退回,懸崖勒馬
        return; 

    count++; //滿足在邊界之內,沒有被訪問過,計數加1
    flag[m][n] = count; //對應節點記錄訪問順序,第幾次被訪問的

    /*貪心演算法,確定下一次的方向*/
    int count_next[8] = {-1,-1,-1,-1,-1,-1,-1,-1};
    for (int i = 0; i < edge; i++)
    {
        int m_next = m + move_x[i];
        int n_next = n + move_y[i];     
        if (m_next < edge && n_next < edge && m_next >= 0 && n_next >= 0 && flag[m_next][n_next] == 0)
        {
            count_next[i] ++;
            for (int j = 0; j < edge; j++)
            {
                int m_next_next = m_next + move_x[j];
                int n_next_next = n_next + move_y[j];
                if (m_next_next < edge && n_next_next < edge && m_next_next >= 0 && n_next_next >= 0 && flag[m_next_next][n_next_next] == 0)                
                        count_next[i]++;
            }
        }       
    }

    int opt_direct = 0; //記錄下一步的方向
    for (int i = 0; i < edge; i++)
    {
        if (count_next[opt_direct] == -1)
            opt_direct = i;
        if ((count_next[i] < count_next[opt_direct]) && count_next[i] != -1)
        {
            opt_direct = i;
        }
    }

    Judge(pan, flag, path, m + move_x[opt_direct], n + move_y[opt_direct], edge, count, found); //採用遞迴,進行下一步的搜尋

    flag[m][n] = 0; //回溯,對應節點標記清零               
}

int main() 
{
    int m,n;
    int edge;
    int found = 0;
    cin >> edge>> m >> n;
    vector<vector<int>> pan(edge, vector<int>(edge, 0));
    vector<vector<int>> flag(edge, vector<int>(edge, 0));
    vector<vector<int>> path(edge, vector<int>(edge, 0));

    Judge(pan, flag, path, m, n, edge, 0, found);

    cout << found<<endl;

    for (int i = 0; i < edge; i++)
    {
        for (int j = 0; j < edge; j++)
            cout << path[i][j] << " ";
        cout << endl;
    }   
    system("pause");
    return 0;
}

實踐證明,當棋盤是8*8的時候,*把馬兒放在任何一個初始位置,都可以走完全部棋盤,當棋盤是5*5的時候,只有部分初始位置才能走完全部棋盤。

這裡寫圖片描述