关于八皇后的思考:
基本方法:
回溯解法:
也就是深度优先遍历;
时间复杂度分析:
在很多的实现和教材中,对于八皇后的回溯解法都有递归和迭代两种情况,个人觉得在计算中迭代和递归并不在同一级别上。在计算中,递归、顺序、选择从某种意义上来说,跟顺序、选择、循环一样属于计算范式,处在最底层;上面是算法设计方法,也就是回溯、分治、贪心等的设计方法;再往上是一些比较通用的数据结构和算法,比如图,图的搜索等,这些虽然很通用,但是也是在解决某个具体问题,所以放在第三层,而第二层并不是在解决某个具体问题,比如排序或者搜索等。
那么迭代是什么呢?迭代应该属于数学上的解决某些问题的方法,当然递归也是,因为这两者天然与计算范式很接近(递归就是),所以很容易用计算范式来表示迭代或者递归的求解过程。
也即是说迭代属于上图的领域求解过程。
解法进阶:
1.我们用数组a[0,…,7]来存放符合条件的解的列号,很明显,那么八皇后的问题解就是8个 元素全排列的某个子集,于是,我们可以先生成排列,然后按约束函数寻找解,复杂度为n!;这个算法在复杂度上相对于基本的回溯算法并没有数量级上的降低,不过大大减少了代码量。
这是使用间接方法解决问题的一个例子,也就是说,很多问题都是可以转化为其他问题的;
2. 概率算法
在使用计算范式表示一个问题的解的过程中(可计算情况),有这么三种方法:(1)无穷遍历,一定能求得解;(2)通过对问题的深入理解,使用某些方法减少遍历过程,如梯度下降法解方程,就是沿着最优过程求解;或者将问题进行进一步的数学求解和转化;(3)如果问题没有系统性和规律性,也就是问题的解以人的认知无法在数学上精确描述,可以使用概率方法,随机选择下一步怎么走。(1)和(3)好像是两个极端,(2)是在对问题的深入了解上,人工先进行一步求解,然后再借助计算机求解。
使用启发式函数选择当前最好的位置放置皇后;启发式函数为当前位置的冲突数;
头文件:
#ifndef QUEEN_H #define QUEEN_H #include <iostream> #include <bitset> #include <cmath> #include <ctime> using namespace std; //backtrack void queen(int, int, int (*p)[8]); void queen_no_recursion(int n, int (*p)[8]) ; //permutation extern int l_count; void permutation(int a[], int i, int n); //probability bool Place(int x[], int k); bool Backtrack(int x[], int t, int n); bool QueensLV(int x[], int stopVegas, int n); //heuristic void shuffle(int Queen[], const int n); int collision(int Queen[], const int row, const int column, const int n); void show(int Queen[],const int n); int heuristic(int Queen[], const int n); #endif
backtrack
#include "queen.h" void queen(int row, int max, int (*p)[8]) { for(int i = 0; i < max; ++i) { for(int j = 0; j < max; ++j) *(*(p + row) + j) = 0; int irow = 0; for(irow = 0; irow < row; ++irow) { if(*(*(p + irow) + i) == 1) break; if((i - (row - irow)) >= 0 && *(*(p + irow) + (i - (row - irow))) == 1 ||\ (i + (row - irow)) < max && *(*(p + irow) + (i + (row -irow))) == 1) break; } if(irow == row) { *(*(p + row) + i) = 1; if(row < max - 1) queen(row + 1, max, p); else if(row = max - 1) { for(int i = 0; i < max; ++i) { for(int j = 0; j < max; ++j) cout << *(*(p + i) + j) << ' '; cout << endl; } cout << "---------------------------------------------------------" << endl; } } } } void queen_no_recursion(int n, int (*p)[8]) { int x[8]; int k = 0; x[k] = -1; int i = 0; int sum = 0; while(k >= 0) { x[k] += 1; while(x[k] < n) { for(i = 0; i < k; ++i) if(x[i] == x[k] || abs(k - i) == abs(x[k] - x[i])) break; if(i == k) break; x[k]++; } if(x[k] < n) { if(k == n-1) { for(int i = 0; i < n; ++i) cout << x[i] << ' '; cout << endl; ++sum; } else x[++k] = -1; } else --k; } cout << "-------------------------------" << endl; cout << sum << endl; }
permutation
#include "queen.h" int l_count = 0; void permutation(int a[], int i, int n) { if(i == n - 1) { int flag = 1; //decide whether the current permutaion is 8-queen for(int k = 1; k < n; ++k) { for(int t = 0; t < k; ++t) { if((k - t) == abs(a[k] - a[t])) { flag = 0; break; } } if(flag == 0) break; } if(flag) ++l_count; } else { for(int j = i; j < n; ++j) { int tmp = a[j]; a[j] = a[i]; a[i] = tmp; permutation(a, i + 1, n); tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } }
probability
#include "queen.h" bool Place(int x[], int k) { // 测试皇后k置于第x[k]列的合法性 for(int j = 0; j < k; ++j) if((abs(k-j) == abs(x[j]-x[k])) || (x[j]==x[k])) return false; return true; } //only get one solution bool Backtrack(int x[], int t, int n) { // 解n后问题的回溯法 if(t > n - 1) { for(int i = 0; i < n; ++i) cout << x[i] << ' '; cout << endl; return true; } else for(int i = 0; i < n; ++i) { x[t] = i; if(Place(x, t) && Backtrack(x, t + 1, n)) return true; } return false; } bool QueensLV(int x[], int stopVegas, int n) { // 随机放置stopVegas个皇后的拉斯维加斯算法 srand((unsigned)time(0)); // 生成随机种子,保证每次调用函数产生 //不同的随机数列 int k = 0; // 下一个放置的皇后编号 int y[8] = {0}; int count = 1; // 1 <= stopVegas <= n 表示允许随机放置的皇后数 while((k < stopVegas) && (count > 0)) { count = 0; for(int i = 0; i < n; ++i) { x[k] = i; //计算每一行能放置的位置数及位置,count对应位置数,y[count]对应第几 //个能放在哪个位置 if(Place(x, k)) y[count++] = i; } if(count > 0) { x[k++] = y[rand() % count]; // 随机位置 } } return (count > 0); // count > 0表示放置位置成功 }
heuristic
#include "queen.h" void shuffle(int Queen[], const int n) { //随机取得各行的初始皇后位置,以Queen[i]表示第i行的皇后位置 for(int i = 0; i < n; ++i) Queen[i]=abs(rand())%n; } int collision(int Queen[], const int row, const int column, const int n) { //计算每个位置的冲突值 int bug = 0; for(int i = 0; i < n; ++i) { if((i != row) && (Queen[i] == column || (Queen[i] - column) == (i - row) || (Queen[i] - column) == (row - i)))//同列,同对角线的情况 ++bug; } return bug; } void show(int Queen[],const int n) { for(int i = 0; i < n; ++i) cout << Queen[i] << ' '; cout << endl; } int heuristic(int Queen[], const int n) { //启发式修补 int max = -1;//标志行行之间冲突数 int minbug = n; int count = 1; while(max != 0 && count <= 100) { max=0; for(int i = 0; i < n; ++i) { minbug=collision(Queen,i,Queen[i],n);//取得当前的冲突数,不断优化 int temp=Queen[i]; for(int j = 0; j < n; ++j) { int bug = collision(Queen,i,j,n); if(bug <= minbug&&j != temp) { //保持皇后在等冲突的情况下不断变更位置,有利于后面行的优化 minbug = bug; Queen[i] = j; } } if(minbug > max) max=minbug; } if(max == 0) return count; else ++count; } return count; }
测试:
#include "queen.h" int main() { //permutation int a[] = {0, 1, 2, 3 ,4, 5, 6, 7}; permutation(a, 0, 8); cout << l_count << endl; //probability int x[8] = {0}; //first use LV, then Backtrack while(!QueensLV(x, 4, 8)); Backtrack(x, 4, 8); cout << endl; //heuristic int q[8] = {0}; srand((unsigned)time(0)); shuffle(q, 8); show(q, 8); int step = heuristic(q, 8); if(step > 100) cout << "can't find one solution in 100 steps" << endl; else { cout << "one solution is : "; show(q, 8); } system("pause"); return 0; }