唉!!! 上次交作业写错题目了,做了简单的马周游 sicily 1152。补上新的,应该是sicily 1153.为了解决规模太大的问题。加上了优化算法。
中大ACM实验题。
(1)原题中文大意
中国象棋的马按照象棋的规则在8 x 8 的棋盘上跑64步跑完所有的格子。
(2)算法思想及解题用到的主要数据结构
从每一个点往下走第二步,会有几点有效的行走位置。 把当前位置记在栈里面,以深度优先的方式进入下一个有效位置。如果下一个有效位置在之前已经走过,则从栈顶弹出上一位置,恢复到调出的位置后尝试未走过的有效位置。利用函数调用时会压栈的特别,用函数递归调用即可解决问题。
软之简单的马周游,8 x 8 棋盘的规模非常大。需要在可选下一步中找到最接近正确路线的点。求该点的办法是把所以的可选点先找出,再给这些可选点按权重排序,从最优的解依次向次优,次次优…..的点试探。重点在权重的算法。这里的权重的计算法则是指一个点的下一次可走的点的个数。
主要的数据结构有:
// elocution数组在初始化后会记下每个有效位置的下一步有哪些可走位 // 置.elocution[x][0]用下标0记个数。最多8个有效可走位置。 char elocution[ELEM_COUNT][9]; // stamps数组记每个点是否已经走过了。0表示没走过,1表示走过了。 char stamps[ELEM_COUNT]; // track数组记路径的顺序。 char track[ELEM_COUNT]; 通过结合stamps数组和elocution数组可以算出下一步的每个点的权重。 (3)详细解题思路 1. 先初始化一张表。这张表记下了棋盘上所有的30个位置的下一步可走的有效位置. 2. 写一个一般的递归调用自己的函数,表示马在走第几步时到了哪个位置,然后求出余下的所有可走位置。 3. 余下的所有位置按照上文提到的权重排序。 4. 对于排序后的可选点数组,按顺序依次用递归函数尝试。 5. 递归函数有三个退出条件。1.下一个要尝试的点已经走过了,2.试完了所有的可选下一步无解。3.走到了最后一步,即得解! (4)算法描述 初如化上文提到的elocution数组,清空stamps和tracks.从起点开始调用递归函数。 http://ykyi.net zausiu's blog. (5)程序注释清单 #include <iostream> #include <cstring> #include <cstdlib> #include <vector> #include <csetjmp> //#include <cassert> using namespace std; #define ROW_NUM 8 #define COLUMN_NUM 8 #define ELEM_COUNT ROW_NUM * COLUMN_NUM jmp_buf jmpbuffer; // 很深的函数递归调用时用longjmp快速退出. // elocution数组在初始化后会记下每个有效位置的下一步有哪些可走位 // 置.elocution[x][0]用下标0记个数。最多8个有效可走位置。 char elocution[ELEM_COUNT][9]; // stamps数组记每个点是否已经走过了。0表示没走过,1表示走过了。 char stamps[ELEM_COUNT]; struct coordinate // 二维坐标 { char _x; // start from zero. char _y; }; // 作标转序号 char coordinate2serial_num(coordinate co) { char num = co._x * COLUMN_NUM + co._y + 1; return num; } // 序号转坐标 coordinate serial_num2coordinate(char sn) { coordinate co; co._x = (sn - 1) / COLUMN_NUM; co._y = sn - co._x * COLUMN_NUM - 1; return co; } // ((x:1;y:-2),(x:2;y:-1),(x:2;y:1),(x:1;y:2) (x:-1;y:2),(x:-2;y:1),(x:-2;y:-1),(x:-1;y:-2)); char increments[8][2] = { 1, -2, 2, -1, 2, 1, 1, 2, -1, 2, -2, 1, -2, -1, -1, -2 }; void next_step(char pos, char steps[]) // 把位置在pos序号的点的每一个下一个可选位置记在数组中。 { char valid_count = 0; coordinate co = serial_num2coordinate(pos); coordinate tmp; char serial_num; for ( int i = 0; i < 8; i++ ) { tmp._x = co._x + increments[i][0]; tmp._y = co._y + increments[i][1]; if ( tmp._x < 0 || tmp._x >= ROW_NUM || tmp._y < 0 || tmp._y >= COLUMN_NUM ) // 保证位置有效 { continue; } serial_num = coordinate2serial_num(tmp); if ( serial_num >= 1 && serial_num <= ELEM_COUNT ) { valid_count++; steps[valid_count] = serial_num; } else { cerr << "Not expected to reach here.\n"; } } steps[0] = valid_count; } // 下面的逻辑以每个点的下一步可跳点的数目作为权重排序。 struct pos_weight { char _pos; char _weight; }; // 又是用递归。这里为了实现快速排序 int partition(pos_weight poses[], int low, int high) { pos_weight pivot = poses[low]; while (low < high) { while (low < high && pivot._weight < poses[high]._weight) high--; poses[low] = poses[high]; while (low < high && pivot._weight >= poses[low]._weight) low++; poses[high] = poses[low]; } poses[low] = pivot; return low; } // 快速排序。 void quick_sort_steps(pos_weight poses[], int low, int high) { if ( low < high ) { int pivot_loc = partition(poses, low, high); quick_sort_steps(poses, low, pivot_loc-1); quick_sort_steps(poses, pivot_loc+1, high); } } void rearrage_steps(char poses[], int len) // poese里放置了下一个位置数组。Len是数组长度。 { char weight, pos, next_step_count; vector<pos_weight> vec(len); for ( int i = 0; i < len; i++ ) // 计算权重. { weight = 0; pos = poses[i]; next_step_count = elocution[pos-1][0]; for ( int j = 0; j < next_step_count; j++ ) { char next_step_pos = elocution[pos-1][j+1]; if ( 0 == stamps[next_step_pos-1] ) { weight++; // 如果有一下一跳点没走过则权重加1. } } vec[i]._pos = pos; vec[i]._weight = weight; } quick_sort_steps(&vec[0], 0, len-1); // 根据权重排序. for ( int i = 0; i < len; i++ ) // 把排序后的位置写回原始数组. { poses[i] = vec[i]._pos; } } void init_elocution() { memset(stamps, 0, sizeof(stamps)); for ( int i = 1; i <= ELEM_COUNT; i++ ) { next_step(i, elocution[i-1]); } } char track[ELEM_COUNT]; void run_horse(char start_pos, char step_count) // step_count [0 -- 64) { // 如果已经经过这点就立即退出函数。 if ( 1 == stamps[start_pos-1] ) { return; } track[step_count] = start_pos; if ( step_count == COLUMN_NUM * ROW_NUM - 1 ) // 是不是最后一步。 { for ( int i = 0; i < sizeof(track); i++ ) { cout << (int)track[i]; if ( i + 1 != sizeof(track) ) { cout << " "; } } cout << endl; longjmp(jmpbuffer, 0x1); return; } // 记下已经走了这一步。 stamps[start_pos-1] = 1; rearrage_steps(elocution[start_pos-1]+1, elocution[start_pos-1][0]); for ( int i = 0; i < elocution[start_pos-1][0]; i++ ) { run_horse(elocution[start_pos-1][i+1], step_count+1); } stamps[start_pos-1] = 0; // 试完了所有可走步.退出这个函数.重置这个位置为没有走过。 } int main(int argc, char* argv[]) { int pos; vector<int> vec; while(true) { cin >> pos; if (pos==-1) { break; } vec.push_back(pos); } for ( int i = 0; i < vec.size(); i++ ) { if(setjmp(jmpbuffer) == 0) // 为了很深的递归函数快速回退到这里。 { init_elocution(); memset(track, 0, sizeof(track)); memset(stamps, 0, sizeof(stamps)); run_horse(vec[i], 0); } } return 0; }
(6)该算法的时间复杂度是 O(8 ^ (m * n)) m,n分别为棋盘的长和宽。
/////////////////// 原题目如下:
1153. 马的周游问题
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个整数,从起点开始按顺序给出马每次经过的棋盘方格的编号。相邻的数字用一个空格分开。
Sample Input
4
-1
Sample Output
注意:如果起点和输入给定的不同,重复多次经过同一方格或者有的方格没有被经过,都会被认为是错误的。