续动态规划—0-1背包问题小故事,话说在Lucy和亲友团从沙漠回去的路上,不小心进入一个叫做“迷宫”的地方,可是捏一把汗呀,差那么一点点就困在里面了。他们向一个方向前进,一步一步向前试探前进,如果碰到死胡同,说明前进方向已无路可走,这是,折回上一个路口(聪明的Lucy在已经走过的路口做好标记O(∩_∩)O哈哈~~再也不怕走迷宫了)看其它方向是否还有路可走,如果有路,则沿着该方向再向前试探。按此原则不断搜素回溯再搜索,直到找到新的出路。就这样他们走出了“迷宫”……
故事讲完,喜欢的小朋友请赞一下我的博文哦~~~~
回溯是搜索算法中一种控制策略,基本思想是:为了求得问题的解,先选择某一种可能情况向前探索,在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索,如此反复进行,直至得到解或证明无解。
问题描述
这是来源于国际象棋的一个问题。n后问题要求在一个n*n格的棋盘上放置n个皇后,使得它们彼此不受攻击。按照国际象棋的规则,一个皇后可以攻击与之处在同一行或同一列或同一条斜线上的其他任何棋子。
问题分析
我以最简单的4*4格棋盘4皇后为例讲解。求解过程从空棋盘开始,设从第1行开始,第一个皇后默认位置在第1列,以后改变时,顺次选择第2列、第3列、第4列。当下一个皇后找不到合适的列位置时(如下图),就要回溯,去改变前一个皇后的位置。
问题解决
小编大致描述了问题,那么就带领大家共同看看放置皇后的过程:
代码解析:
/*
* 回溯法解N皇后问题
* 使用一个一维数组表示皇后的位置
* 其中数组的下标表示皇后所在的行
* 数组元素的值表示皇后所在的列
* 这样设计的棋盘,所有皇后必定不在同一行
*
* 假设前n-1行的皇后已经按照规则排列好
* 那么可以使用回溯法逐个试出第n行皇后的合法位置
* 所有皇后的初始位置都是第0列
* 那么逐个尝试就是从0试到N-1
* 如果达到N,仍未找到合法位置
* 那么就置当前行的皇后的位置为初始位置0
* 然后回退一行,且该行的皇后的位置加1,继续尝试
* 如果目前处于第0行,还要再回退,说明此问题已再无解
*
* 如果当前行的皇后的位置还是在0到N-1的合法范围内
* 那么首先要判断该行的皇后是否与前几行的皇后互相冲突
* 如果冲突,该行的皇后的位置加1,继续尝试
* 如果不冲突,判断下一行的皇后
* 如果已经是最后一行,说明已经找到一个解,输出这个解
* 然后最后一行的皇后的位置加1,继续尝试下一个解
*/
#define MAX_LENGTH 1024
/*
* 检查第n行的皇后与前n-1行的皇后是否有冲突
* 发生冲突的充分必要条件是:
* a) 两皇后处于同一列,即a[i] == a[n]
* b) 两皇后处于同一斜线,即|a[i] - a[n]| == |i - n| == n - i
*/
int is_conflict(int *a, int n) {
int flag = 0;
int i;
for ( i = 0; i < n; i++ ) {
if ( a[i] == a[n] || a[i] - a[n] == n - i || a[n] - a[i] == n - i ) {
flag = 1;
break;
}
}
return flag;
}
/*
* 输出皇后的排列
*/
void print_board(int *a, int n) {
int i, j;
for ( i = 0; i < n; i++ ) {
for ( j = 0; j < a[i]; j++ ) {
printf(" ");
}
printf("Q");
for ( j = a[i] + 1; j < n; j++ ) {
printf(" ");
}
printf("/n");
}
printf("--------/n");
}
/*
* 初始化棋盘,所有皇后都在第0列
*/
void init_board(int *a, int n) {
int i;
for ( i = 0; i < n; i++ ) {
a[i] = 0;
}
}
/*
* 解决N皇后问题
*/
int queen(int n) {
int count = 0;
int a[MAX_LENGTH];
init_board(a, n);
int i = 0;
while ( 1 ) {
if ( a[i] < n ) {
// 如果皇后的位置尚未超出棋盘范围
// 需要检查第i行的皇后是否与前i-1行的皇后冲突
if ( is_conflict(a, i) ) {
// 如果冲突,尝试下一个位置
a[i]++;
continue;
}
if ( i >= n - 1 ) {
// 如果已经到最后一行,也即找到一个解,首先输出它
count++;
print_board(a, n);
// 然后尝试当前行的皇后的下一个位置
a[n-1]++;
continue;
}
// 没有冲突,尝试下一行
i++;
continue;
}
else {
// 皇后的位置已经超出棋盘范围
// 那么该行的皇后复位
a[i] = 0;
// 回退到上一行
i--;
if ( i < 0 ) {
// 已经不能再退了,函数结束
return count;
}
// 尝试上一行的皇后的下个位置
a[i]++;
continue;
}
}
}
int main(void) {
int n = 8;
int count = queen(n);
printf("%d solutions in %d queens problem\n", count, n);
return 0;
}
总结
无论是动态规划、贪心、回溯、分治算法,每个算法都有她独特的思想和中心点。当你掌握其中的要害,以后的代码设计和理解很有帮助哦。