馬踏棋盤之貪心演算法優化
問題描述:
曾經用簡單的深度優先搜尋方法、遞迴的形式對馬踏棋盤進行搜尋,執行效率不甚理想。(部落格見馬踏棋盤之遞迴實現)。
所以現在用貪心演算法將其優化了一下。
問題解析:
主要的思想沒有變,還是用深度優先搜尋,只是在選下一個結點的時候做了貪心演算法優化,其思路如下:
從起始點開始,根據“馬”的走法,它的下一步的可選擇數是有0—8個的。
我們知道,當下一步的可選擇數為0的時候,進行回溯。當下一步的可選擇數有1個的時候,我們直接取那一步就行了。但是如果下一步的可選擇數有多個的時候呢?
在上一篇部落格中,我們是任意取一個的,只要它在棋盤內,且未遍歷就可以了。
但其實我們怎麼選下一步,對搜尋的效率影響是非常大的!
先介紹一下“貪心演算法”。百科裡面的定義是:貪心演算法(又稱貪婪演算法),是指在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的僅是在某種意義上的區域性最優解。它只考慮區域性的最優,從而讓總體也最優。就我們這個馬踏棋盤來說,我們每走一步都取最優的那個選擇,從而讓整體的演算法也最優。
但是我們選擇下一步的時候(假設有a、b、c、d四個點可以選擇),怎樣選才算是最優呢?
答案是:哪一個點的下一步少,就選哪一個。
我們選擇a、b、c、d之中的某一個點作為下一步,選哪個比較好,就看哪個點的後續下一步比較少。例如:馬走到a點後的下一步有3個選擇;而b的下一步有2個;c有1個,d有2個。那麼我們的最優選擇是:c點!
為什麼要這樣選呢?網上的解釋是:“選擇最難走的路,才能走的遠”嗚。。。好像太抽象了。
我的理解是:有些選擇的後續下一步很少,例如c點,如果不先遍歷它的話以後可能會很難遍歷到它。
甚至極端一點的情況是,如果現在不遍歷它,以後都遍歷不到了。遍歷不成功的時候只能回溯,一直回溯到此刻的點,然後選了c點以後才能完成,這就浪費了大量的時間。
下面放出所有程式碼,詳細的解釋後面再補上:
<pre name="code" class="cpp">#include<stdio.h>
#include<time.h>
#define H 3 // 代表對下一步的排序只取出最小的2個,而不是對8個都排序,這樣可以節省很多時間
int fx[8] = {-2,-1,1,2,2,1,-1,-2}, fy[8] = {1,2,2,1,-1,-2,-2,-1}, f[8] = {-15,-6,10,17,15,6,-10,-17};
// fx[] 和 fy[] 表示馬在二維的八個方向,給二維座標x和y用;f[]表示一維的八個方向,給陣列a用。
int dep = 1; // dep 為遞迴的深度,代表在當前位置馬已經走了多少步
int count, z = 0, zz = 0; // count 表示目標要多少種解法,而 z 記錄當前算出了多少種解法,zz 記錄在運算中回溯的次數
int out[100001][8][8], F[8], a[64]; // out[][][] 記錄所有的遍歷路徑,a[] 用一維陣列記錄 8*8 棋盤中馬的遍歷路徑
// 輸入起始座標,對存放遍歷路徑的陣列a進行初始化
int Prepare()
{
int i, j, n;
printf("請輸入起始點的座標:\n");
printf("x=");
scanf("%d", &i);
printf("\by=");
scanf("%d", &j);
printf("你要的解的數目count=");
scanf("%d", &count);
n = i * 8 + j - 9; // 將起始點的二維座標 x、y 轉化成一維座標 n ,從而方便陣列 a[64] 的路徑記錄
for(i = 0; i<64; i++) // a[64] 存放在 8*8 方格中馬的遍歷路徑,搜尋之前先進行清零初始化
a[i] = 0;
a[n] = 1;
return n;
}
// Sortint() 函式對點 n 的下一步進行“後續下一步可選擇數”的排序,結果儲存在 b[][] 裡面
// c 表示前驅結點在結點 n 的哪個位置。
void Sorting(int b[64][H], int n, int c)
{
int i, j, x, y, m1, m2, k, k1, l=1, xx, yy;
if(c != -1)
c = (c + 8 - 4) % 8;
for(i=0; i<8; i++) //對於當前節點的八個方向
{
F[i] = -1; //F記錄八個方向的下一步的再下一步有多少個
m1 = n + f[i];
x = n / 8 + fx[i];
y = n % 8 + fy[i]; //這是下一步的座標
if(c!=i && x>=0 && x<8 && y>=0 && y<8 && a[m1]==0) //如果下一步存在
{
F[i]++;
for(j=0; j<8; j++) //對於下一步的八個方向
{
m2 = m1 + f[j];
xx = x + fx[j];
yy = y + fy[j]; //這是再下一步的座標
if(xx>=0 && xx<8 && yy>=0 && yy<8 && a[m2]==0) //如果再下一步存在
F[i]++;
}
}
}
b[n][0] = -1;
for(i=1; i<H; i++)
{
k = 9;
for(j=0; j<8; j++)
{
if(F[j]>-1 && F[j]<k)
{
k = F[j];
k1 = j;
}
}
if(k < 9)
{
b[n][l++] = k1;
F[k1] = -1;
b[n][0] = 1;
}
else
{
b[n][l++] = -1;
break;
}
}
}
// 搜尋遍歷路徑
void Running(int n)
{
int i, j, k;
int b[64][H], s[64]; // b[][] 用來存放下一步的所有後續結點排序
s[0] = n;
Sorting(b, n, -1);
while(dep >= 0)
{
if(b[n][0]!=-1 && b[n][0]<H && b[n][b[n][0]]!=-1)
{
k = b[n][b[n][0]];
b[n][0]++;
n += f[k];
Sorting(b, n, k);
a[n] = ++dep;
s[dep-1] = n;
if(dep == 64)
{
for(i=0; i<8; i++)
for(j=0; j<8; j++)
out[z][i][j] = a[i*8+j];
z++;
if(z == count)
{
printf("\n完成!!\n");
printf("回溯的次數:%d\n", zz);
break;
}
}
}
else
{
dep--;
zz++;
a[n] = 0;
n = s[dep-1];
}
}
}
// 輸出所有的遍歷路徑
void Output()
{
int i, j, k;
printf("\n\n輸入'1'展示詳細遍歷,輸入'0'退出程式:");
scanf("%d", &count);
if(count)
{
for(i=0; i<z; i++)
{
printf("第%d個解:\n", i+1);
for(j=0; j<8; j++)
{
for(k=0; k<8; k++)
printf("%3d", out[i][j][k]);
printf("\n");
}
}
}
}
void main()
{
int n;
double start,finish;
n = Prepare();
start = clock();
Running(n);
finish = clock();
printf("執行時間:%.3f秒\n",(finish-start)/1000);
Output();
}
執行結果如下(VC6.0、32位win8系統 + i7-3612QM處理器,如果使用VS時間會長一點):
測試了一下:十萬條路徑,耗時1秒多!!!效率比普通的深度優先搜尋不知道高了多少倍!
備註:
以上程式碼是四個月前寫的,現在才整理出來。打擊很大,當初不注意程式碼的格式和備註,現在看自己寫的程式碼看到頭暈腦炸,自食其果!以後必須注重寫程式碼的風格,注意寫上必須的備註,連自己寫的程式碼的看不懂,真真可笑。一直看了好久終於看明白各個函式 各個變數的作用了,也改了一些格式,寫了一些備註,到現在正的看不下去了。先把文章放出來,緩一陣再回來完善。
轉載請註明出處,謝謝!(原文連結:http://blog.csdn.net/bone_ace/article/details/41579957)
相關文章
- 馬踏棋盤演算法(騎士周遊問題)----遞迴與貪心優化演算法演算法遞迴優化
- 馬踏棋盤之遞迴實現遞迴
- 馬踏棋盤(棧實現)
- 常用演算法之貪心演算法演算法
- 圖的遍歷演算法-馬遍歷棋盤演算法
- 「演算法」貪心與隨機化演算法隨機
- 貪心演算法演算法
- 貪心演算法(貪婪演算法,greedy algorithm)演算法Go
- 貪心演算法之無重疊區間演算法
- 學一下貪心演算法-學一下貪心演算法演算法
- 貪心演算法Dijkstra演算法
- 9-貪心演算法演算法
- 演算法基礎–貪心策略演算法
- Moving Tables(貪心演算法)演算法
- Python演算法:貪心策略Python演算法
- 貪心
- 貪心演算法——換酒問題演算法
- 靈茶之貪心模擬01
- 加油站問題(貪心演算法)演算法
- 貪心演算法篇——區間問題演算法
- 求最大子陣列(貪心演算法)陣列演算法
- 反悔貪心
- Supermarket(貪心)
- 演算法---貪心演算法和動態規劃演算法動態規劃
- 資料結構與演算法——貪心演算法資料結構演算法
- 探索貪心演算法:解決最佳化問題的高效策略演算法
- 活動選擇問題理解貪心演算法演算法
- 貪心演算法有時也很有用 - hashnode演算法
- 汽車加油問題 SDUT OJ 貪心演算法演算法
- leedcode-分發餅乾(貪心演算法)演算法
- Day27 貪心演算法part1演算法
- Day28 貪心演算法part2演算法
- Day31 貪心演算法part5演算法
- dfs與貪心演算法——洛谷5194演算法
- 《演算法筆記》9. 培養貪心思維、貪心演算法深度實踐演算法筆記
- 五大常用演算法之三:貪心演算法演算法
- 8.13(優先佇列貪心維護+打表找規律+對頂堆優先佇列+DFS減枝+貪心dp)佇列
- 分治演算法-求解棋盤覆蓋問題演算法