用貪心算法解決馬踏棋盤問題時,主要的思想與用遞歸的方法解決該問題相同,都是用深度優先搜索,只是在選下一個結點的時候做了貪心算法優化,其思路如下:
從起始點開始,根據“馬”的走法,它的下一步的可選擇數是有0—8個的。
已知,當馬下一步的可選擇數為0的時候(即馬沒有下一個節點可跳),進行回溯。當下一步的可選擇數有1個的時候,我們直接取那一步就可以。但是如果下一步的可選擇數有多個的時候呢?
在之前用的遞歸+回溯算法中,我們是任意取一個的,只要它在棋盤內,且未遍歷就可以了。但其實我們怎么選下一步,對搜索的效率影響是非常大的!
貪心算法:又稱貪婪算法,是指在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的僅是在某種意義上的局部最優解。
用貪心算法的思想來解決在棋盤如何選擇下一個節點的問題,就是計算馬兒的哪個下一節點對應的下一個節點(即下下個節點)的總數最少,就將哪個下一節點視為最優。
例如:假設a、b、c、d為馬兒可以選擇的下一個節點,選哪個比較好,就看哪個點的后續下一步比較少。如果馬走到a點后的下一步有3個選擇;而b的下一步有2個;c有1個,d有2個,那么最優選擇是:c點。
因為c點的后續下一步很少,如果不先遍歷它的話以后可能會很難遍歷到它。甚至極端一點的情況是,如果現在不遍歷它,以后都遍歷不到了。遍歷不成功的時候只能回溯,一直回溯到此刻的點,然后選了c點以后才能完成,這就浪費了大量的時間。
選擇下一個節點的函數如下圖所示,其中,num是馬在當前位置時,下一個節點的數目。數組a[]的長度為num,a[]中存放馬的每個下一節點所對應的下一個節點的數目。
用貪心算法求解馬踏棋盤問題的一個解的代碼如下。當然,一般來講棋盤的大小應該為8*8,此處,定義的是6*6,主要是為了方便和我之前的用遞歸+回溯的方法寫的代碼進行對比,用遞歸的話,用8*8的棋盤,執行的時間有點長,於是就用了6*6的.
1 # include <stdio.h> 2 # include <stdlib.h> 3 # include <math.h> 4 # include <time.h> 5 # define R 6 6 # define C 6 7 int cheesboard [R] [C]; 8 const int moveX [8] = {-2,-1,1,2,2,1,-1,-2}; 9 const int moveY [8] = {1,2,2,1,-1,-2,-2,-1}; 10 //初始化棋盤,將棋盤所有的位置賦值為0 11 void initBoard (int board[][C]){ 12 int i ,j; 13 for(i = 0; i < R; i ++){ 14 for( j = 0; j < C; j ++){ 15 board[i][j] = 0; 16 } 17 } 18 } 19 //從待選的下一個點的集合中,選取它們其中所對應的下一個節點數目最少的一個 20 int getMinPath (int a[],int num){ //num:下一個節點的數目, 21 //a[]:每一個 下一個節點 對應的下一節點的數目 22 //定義下標為 23 int i = 0,index=0; 24 //定義最小的值為a(0),找到最小的值,而且大於0的值 25 int min= a[0]; 26 //找出數組中第一個大於0的值為min 27 for(i = 0 ; i< num; i++) 28 { 29 if(a[i] > 0) 30 { 31 min = a[i]; 32 index = i; 33 break; 34 } 35 } 36 for(i = index + 1; i < num ; i++) 37 { 38 if(a[i] > 0 && min > a[i]) 39 { 40 min = a[i]; 41 index = i; 42 } 43 } 44 if(a[index] > 0) 45 return index; 46 return -1; 47 } 48 // 打印路徑 49 void printPath (int board[][C]){ 50 int i,j; 51 for (i = 0; i < R; i++){ 52 for ( j = 0; j < C; j++){ 53 printf("%d\t",board[i][j]); 54 } 55 printf("\n\n"); 56 } 57 } 58 // 獲得馬行走的路徑 59 void getPath (int board [][C], int startX, int startY){ 60 //下一個可行位置的數目 61 int next = 0; 62 //路徑最短的可行位置在數組中的位置 63 int min; 64 //下一個可行位置的可行位置數目 65 int nextNext; 66 //將棋盤初始化 67 initBoard (board); 68 // 存放下一個位置對應的下一個位置的數目 69 int nextNum[8] = {0,0,0,0,0,0,0,0}; 70 //下一個位置的在二維數組中對應位置,初始為0 71 int nextX[C] = {0}; 72 int nextY[R] = {0}; 73 //第一個位置賦值為1 74 board [startX] [startY] = 1; 75 int m,i,j; 76 //走完所有的點要循環63次 77 for ( m = 1; m < R*C; m++){ 78 //當前點其后面可行的位置設為0 79 next = 0; 80 //通過循環來判斷該位置是否還可以向下面位置移動 81 for ( i = 0; i < 8; i++){ 82 if(startX + moveX[i] < 0 || startX + moveX[i] >= R 83 || startY + moveY[i] < 0 || startY + moveY[i] >= C 84 || board[startX+moveX[i]][startY+moveY[i]] != 0){ 85 continue; 86 } 87 //如果可以向下一個位置移動的話,通過next數組保存下來,通過next記錄下有多少個 88 nextX [next] = startX + moveX[i]; 89 nextY [next] = startY + moveY[i]; 90 next ++; 91 } 92 //循環結束之后,對next的值進行判斷,當為1的時候 93 if (next == 1){ 94 //讓min=0,表示現在所需要的位置是在我們的保存next數組中的第一位 95 min = 0; 96 //設置初始點 97 goto set_nextpoint; 98 } 99 //無法向下一個位置移動了 100 else if (next == 0){ 101 printf("沒有路徑可走了\n"); 102 goto print_path; 103 } 104 else { 105 /*當有多個路徑可以走的時候,檢測每一個點還可不可以繼續向下走然后 106 記錄下來該點有幾個點可以向下走,找到最少的一個但是不為0的哪一個 107 */ 108 for (i = 0; i<next; i++){ 109 nextNext = 0; 110 for(j = 0; j < 8; j++){ 111 if(nextX[i] + moveX[j] >=0 && nextX[i] + moveX[j] < R 112 && nextY[i] + moveY[j] >= 0 && nextY[i] + moveY[j] <C 113 && board [nextX[i]+moveX[j]][nextY[i]+moveY[j]] == 0){ 114 nextNext ++; 115 } 116 } 117 nextNum[i] = nextNext; 118 } 119 if ((min = getMinPath(nextNum,next))>=0 ) 120 { 121 goto set_nextpoint; 122 } 123 else{ 124 printf("沒有路徑可走了\n"); 125 goto print_path; 126 } 127 } 128 set_nextpoint: 129 startX = nextX[min]; 130 startY = nextY[min]; 131 board[startX][startY] = m+1; 132 } 133 print_path: 134 printPath(board); 135 136 } 137 void judgeExistence(){ 138 if(R<=4 || C<=4){//通過已有的理論給出判斷條件 139 printf("棋盤矩陣為%d * %d 時,馬從其中一些節點出發,不能夠找到" 140 "\n不重復遍歷完棋盤中每一格的路徑.請重新設置矩陣的大小,矩" 141 "\n陣的大小應滿足行數和列數均大於4\n",R,C); 142 exit(0); 143 } 144 } 145 int main (){ 146 int i,j; 147 //main函數后首先執行一個判斷存在性的函數 148 judgeExistence(); 149 clock_t start, finish; //計算核心方法一共花費了多少時間 150 long duration; 151 152 //獲得最開始的位置 153 label_1:printf("請輸入馬初始橫坐標(X<=%d):X=\n",R); 154 scanf("%d",&i); 155 if(i>R){ 156 printf("請輸入小於等於%d的數\n",R); 157 goto label_1; 158 } 159 label_2:printf("請輸入馬初始縱坐標(Y<=%d):Y=\n",C); 160 scanf("%d",&j); 161 if(j>C){ 162 printf("請輸入小於等於%d的數\n",C); 163 goto label_2; 164 } 165 start=clock();//開始時間 166 i=i-1; 167 j=j-1; 168 169 //調用該函數獲取路徑 170 getPath(cheesboard, i, j); 171 finish=clock(); 172 duration=finish-start; 173 printf("棋盤的大小為%d*%d\n",R,C); 174 printf("采用貪心算法所需的時間為%ld\t ms \n",duration); 175 return 0; 176 }