用贪心算法解决马踏棋盘问题时,主要的思想与用递归的方法解决该问题相同,都是用深度优先搜索,只是在选下一个结点的时候做了贪心算法优化,其思路如下:
从起始点开始,根据“马”的走法,它的下一步的可选择数是有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 ",board[i][j]); 54 } 55 printf(" "); 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("没有路径可走了 "); 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("没有路径可走了 "); 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 " 不重复遍历完棋盘中每一格的路径.请重新设置矩阵的大小,矩" 141 " 阵的大小应满足行数和列数均大于4 ",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= ",R); 154 scanf("%d",&i); 155 if(i>R){ 156 printf("请输入小于等于%d的数 ",R); 157 goto label_1; 158 } 159 label_2:printf("请输入马初始纵坐标(Y<=%d):Y= ",C); 160 scanf("%d",&j); 161 if(j>C){ 162 printf("请输入小于等于%d的数 ",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 ",R,C); 174 printf("采用贪心算法所需的时间为%ld ms ",duration); 175 return 0; 176 }