馬踏棋盤問題 — 深搜和貪心演算法
阿新 • • 發佈:2019-01-30
同學面試阿里,被問到了馬踏棋盤的問題,作為非計算機專業的門外漢,完全沒有聽說過,只聽說過馬踏飛燕。抓緊去搜了一下,發現還是個經典演算法,題目是這樣的:
國際象棋的棋盤為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;
}