【八皇后问题】
在棋盘上放置8个皇后,使得她们互不攻击,此时每个皇后的攻击范围为同行同列和同对角线,要求找出所有解。
【回溯】:当把问题分成若干步骤并递归求解时,如果当前步骤没有合法选择,则函数将返回上一级递归调用,这种现象称为回溯。递归枚举算法通常被称为回溯法。
1 #include <stdio.h> 2 3 #define QUEEN_N (8) 4 5 int queen[QUEEN_N][QUEEN_N] = { 6 {0, 0, 0, 0, 0, 0, 0, 0}, 7 {0, 0, 0, 0, 0, 0, 0, 0}, 8 {0, 0, 0, 0, 0, 0, 0, 0}, 9 {0, 0, 0, 0, 0, 0, 0, 0}, 10 {0, 0, 0, 0, 0, 0, 0, 0}, 11 {0, 0, 0, 0, 0, 0, 0, 0}, 12 {0, 0, 0, 0, 0, 0, 0, 0}, 13 {0, 0, 0, 0, 0, 0, 0, 0}, 14 }; 15 16 int queen_check(int x, int y) 17 { 18 int i; 19 20 for(i = 0; i < QUEEN_N; i++) 21 { 22 if(queen[i][y] == 1 && i != x) // 检测列 23 return 0; 24 if(queen[x][i] == 1 && i != y) // 检测行 25 return 0; 26 } 27 28 // 检测对角线,x正向方向 29 for(i = x+1; i < QUEEN_N; i++) 30 { 31 if(y + i - x < QUEEN_N) 32 if(queen[i][y+i-x] == 1) 33 return 0; 34 if(y - (i - x) >= 0) 35 if(queen[i][y-i+x] == 1) 36 return 0; 37 } 38 // 检测对角线,x反向方向 39 for(i = x-1; i >=0; i--) 40 { 41 if(y + (x-i) < QUEEN_N) 42 if(queen[i][y+x-i] == 1) 43 return 0; 44 if(y - (x-i) >= 0) 45 if(queen[i][y-x+i] == 1) 46 return 0; 47 } 48 return 1; 49 } 50 51 void queen_print(int queen[QUEEN_N][QUEEN_N]) // 输出棋盘布局 52 { 53 int i, j; 54 55 for(i = 0; i < QUEEN_N; i++) 56 { 57 for(j = 0; j < QUEEN_N; j++) 58 { 59 if(queen[i][j] == 1) 60 { 61 printf("O "); 62 } 63 else 64 { 65 printf("x "); 66 } 67 } 68 printf(" "); 69 } 70 } 71 72 // 输出所有棋盘 73 void queen_fill(int n) 74 { 75 int i; 76 static int iCount = 0; 77 if(n >= QUEEN_N) // 递归边界。只要走到了这里,所有皇后必然不冲突。 78 { 79 printf("queen_print <%d> ", iCount++); 80 queen_print(queen); 81 } 82 else 83 { 84 for(i = 0; i < QUEEN_N; i++) // 尝试把第n行的皇后放在第i列 85 { 86 queen[n][i] = 1; 87 if(queen_check(n, i) == 1) 88 { 89 queen_fill(n+1); // 如果合法,则继续递归,放置下一行 90 } 91 queen[n][i] = 0; 92 } 93 } 94 } 95 // 输出第1个棋盘 96 int queen_fill_1(int n) 97 { 98 int i; 99 if(n >= QUEEN_N) 100 { 101 queen_print(queen); 102 return 1; 103 } 104 else 105 { 106 for(i = 0; i < QUEEN_N; i++) 107 { 108 queen[n][i] = 1; 109 if(queen_check(n, i) == 1) 110 { 111 if(queen_fill_1(n+1) == 1) 112 return 1; 113 } 114 queen[n][i] = 0; 115 } 116 } 117 return 0; 118 } 119 120 // 对于原始棋盘,判断是否能输出满足的棋盘 121 int queen_fill_x(int n) 122 { 123 int i; 124 if(n >= QUEEN_N) 125 { 126 queen_print(queen); 127 return 1; 128 } 129 else 130 { 131 for(i = 0; i < QUEEN_N; i++) 132 { 133 if(queen[n][i] == 0) 134 { 135 queen[n][i] = 1; 136 if(queen_check(n, i) == 1) 137 { 138 if(queen_fill_x(n+1) == 1) 139 return 1; 140 } 141 queen[n][i] = 0; 142 } 143 else 144 { 145 if(queen_check(n, i) == 1) 146 { 147 if(queen_fill_x(n+1) == 1) 148 return 1; 149 } 150 } 151 } 152 } 153 return 0; 154 } 155 156 int main(void) 157 { 158 int iRet; 159 printf("init queen: "); 160 queen_print(queen); 161 162 printf("queen_fill_x: "); 163 iRet = queen_fill_x(0); 164 if(iRet != 1) 165 { 166 printf("xxx err "); 167 } 168 169 printf("queen_fill_1: "); 170 queen_fill_1(0); 171 172 printf("queen_fill_all: "); 173 queen_fill(0); 174 175 return 0; 176 }
在编写递归枚举程序之前,需要深入分析问题,对模型精雕细琢。一般还应对解答树的结点数有一个粗略的估计,作为对评价模型的重要依据。
递归解决问题的思想:
if 问题足够简单:
直接解决问题
返回解
else:
将问题分解为与原问题同构的一个或多个更小的问题
逐个解决这些更小的问题
将结果组合为,获得最终的解
返回解