Sicily1153-马的周游问题:启发式搜索
代码地址: https://github.com/laiy/Datastructure-Algorithm/blob/master/sicily/1153.c
题目如下:
1153. 马的周游问题
Constraints
Time Limit: 1 secs, Memory Limit: 32 MB , Special Judge
Description
和题目C同样的任务,这里只是把棋盘扩大到标准的国际象棋。对这样一个8 * 8的棋盘用同样的方法编号如下:
1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64
Input
输入有若干行。每行一个整数N(1<=N<=64),表示马的起点。最后一行用-1表示结束,不用处理。
Output
对输入的每一个起点,求一条周游线路。对应地输出一行,有64个整数,从起点开始按顺序给出马每次经过的棋盘方格的编号。相邻的数字用一个空格分开。
典型的搜索问题,1152数据规模较小,直接DFS就可以过,然而1153不行,以下为超时代码(>0.99s):
1 // Problem#: 1153 2 // Submission#: 3914599 3 // The source code is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License 4 // URI: http://creativecommons.org/licenses/by-nc-sa/3.0/ 5 // All Copyright reserved by Informatic Lab of Sun Yat-sen University 6 #include <cstdio> 7 #include <cstring> 8 9 bool visited[8][8]; 10 int y_direction[8] = {-2, -2, -1, -1, 1, 1, 2, 2}; 11 int x_direction[8] = {-1, 1, 2, -2, 2, -2, -1, 1}; 12 int record[64]; 13 bool found; 14 15 inline bool is_valid(int x, int y) { 16 return x >= 0 && x < 8 && y >= 0 && y < 8; 17 } 18 19 inline int get_number(int x, int y) { 20 return x * 8 + y + 1; 21 } 22 23 void dfs(int x, int y, int step) { 24 if (visited[x][y] || found) 25 return; 26 visited[x][y] = true; 27 record[step] = get_number(x, y); 28 if (step == 63) { 29 found = true; 30 return; 31 } 32 int temp_x, temp_y; 33 for (int i = 0; i < 8; i++) { 34 temp_x = x + x_direction[i], temp_y = y + y_direction[i]; 35 if (is_valid(temp_x, temp_y) && !visited[temp_x][temp_y]) 36 dfs(temp_x, temp_y, step + 1); 37 } 38 visited[x][y] = false; 39 } 40 41 int main() { 42 int n; 43 while (scanf("%d", &n) && n != -1) { 44 found = false; 45 memset(visited, 0, sizeof(visited)); 46 dfs((n - 1) / 8, (n - 1) % 8, 0); 47 for (int i = 0; i < 63; i++) 48 printf("%d ", record[i]); 49 printf("%d\n", record[63]); 50 } 51 return 0; 52 }
怎么优化?
答案是: 启发式搜索。
启发式搜索其实很简单,就是在DFS的时候正常情况下是按照固定的顺序对树的节点进行访问的(例如从左到右)。
而启发式搜索则是在DFS搜索的时候对树节点的访问加入一个贪心策略,让每次往下搜索的顺序是有策略性的,有启发性的。(这个贪心策略是为了指向最终的终点)。
那么在马周游问题里面怎么在DFS里面加入这个贪心策略使这个搜索更聪明呢?(更快找到终点)
之前Wansdorff在1823年给出了这个启发策略。
Warnsdorff's rule
Warnsdorf's rule is a heuristic for finding a knight's tour. We move the knight so that we always proceed to the square from which the knight will have the fewest onward moves. When calculating the number of onward moves for each candidate square, we do not count moves that revisit any square already visited. It is, of course, possible to have two or more choices for which the number of onward moves is equal; there are various methods for breaking such ties, including one devised by Pohl [14] and another by Squirrel and Cull.[15]
好,尝试一下,在延伸树的支点的时候对每个支点加权排序之后再遍历。
代码如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <vector> 4 #include <algorithm> 5 6 bool visited[8][8]; 7 int y_direction[8] = {-2, -2, -1, -1, 1, 1, 2, 2}; 8 int x_direction[8] = {-1, 1, 2, -2, 2, -2, -1, 1}; 9 int record[64]; 10 bool found; 11 12 struct Node { 13 int x, y, weight; 14 Node(int x, int y, int weight) { 15 this->x = x; 16 this->y = y; 17 this->weight = weight; 18 } 19 bool operator<(const Node &node) const { 20 return weight < node.weight; 21 } 22 }; 23 24 inline bool is_valid(int x, int y) { 25 return x >= 0 && x < 8 && y >= 0 && y < 8; 26 } 27 28 inline int get_number(int x, int y) { 29 return x * 8 + y + 1; 30 } 31 32 inline int get_weight(int x, int y) { 33 int temp_x, temp_y, weight = 0; 34 for (int i = 0; i < 8; i++) { 35 temp_x = x + x_direction[i], temp_y = y + y_direction[i]; 36 if (is_valid(temp_x, temp_y) && !visited[temp_x][temp_y]) 37 weight++; 38 } 39 return weight; 40 } 41 42 void dfs(int x, int y, int step) { 43 if (visited[x][y] || found) 44 return; 45 visited[x][y] = true; 46 record[step] = get_number(x, y); 47 if (step == 63) { 48 found = true; 49 return; 50 } 51 int temp_x, temp_y; 52 std::vector<Node> v; 53 for (int i = 0; i < 8; i++) { 54 temp_x = x + x_direction[i], temp_y = y + y_direction[i]; 55 if (is_valid(temp_x, temp_y) && !visited[temp_x][temp_y]) 56 v.push_back(Node(temp_x, temp_y, get_weight(temp_x, temp_y))); 57 } 58 std::sort(v.begin(), v.end()); 59 for (int i = 0; (size_t)i < v.size(); i++) 60 dfs(v[i].x, v[i].y, step + 1); 61 visited[x][y] = false; 62 } 63 64 int main() { 65 int n; 66 while (scanf("%d", &n) && n != -1) { 67 found = false; 68 memset(visited, 0, sizeof(visited)); 69 dfs((n - 1) / 8, (n - 1) % 8, 0); 70 for (int i = 0; i < 63; i++) 71 printf("%d ", record[i]); 72 printf("%d\n", record[63]); 73 } 74 return 0; 75 }
运用此策略惊人的达到了0.00s,在性能的提升上我确实没想到能提升这么多。
以下为两个版本代码提交截图: