程序设计方法-回溯、概率、启发式

关于八皇后的思考:

基本方法:

回溯解法:

也就是深度优先遍历;

 

时间复杂度分析:

 

在很多的实现和教材中,对于八皇后的回溯解法都有递归和迭代两种情况,个人觉得在计算中迭代和递归并不在同一级别上。在计算中,递归、顺序、选择从某种意义上来说,跟顺序、选择、循环一样属于计算范式,处在最底层;上面是算法设计方法,也就是回溯、分治、贪心等的设计方法;再往上是一些比较通用的数据结构和算法,比如图,图的搜索等,这些虽然很通用,但是也是在解决某个具体问题,所以放在第三层,而第二层并不是在解决某个具体问题,比如排序或者搜索等。

那么迭代是什么呢?迭代应该属于数学上的解决某些问题的方法,当然递归也是,因为这两者天然与计算范式很接近(递归就是),所以很容易用计算范式来表示迭代或者递归的求解过程。

也即是说迭代属于上图的领域求解过程。


解法进阶:
1.我们用数组a[0,…,7]来存放符合条件的解的列号,很明显,那么八皇后的问题解就是8个   元素全排列的某个子集,于是,我们可以先生成排列,然后按约束函数寻找解,复杂度为n!;这个算法在复杂度上相对于基本的回溯算法并没有数量级上的降低,不过大大减少了代码量。

这是使用间接方法解决问题的一个例子,也就是说,很多问题都是可以转化为其他问题的;

2. 概率算法

在使用计算范式表示一个问题的解的过程中(可计算情况),有这么三种方法:(1)无穷遍历,一定能求得解;(2)通过对问题的深入理解,使用某些方法减少遍历过程,如梯度下降法解方程,就是沿着最优过程求解;或者将问题进行进一步的数学求解和转化;(3)如果问题没有系统性和规律性,也就是问题的解以人的认知无法在数学上精确描述,可以使用概率方法,随机选择下一步怎么走。(1)和(3)好像是两个极端,(2)是在对问题的深入了解上,人工先进行一步求解,然后再借助计算机求解。

对于n皇后问题,使用LasVegas求解,目前只写了求一个解的,时间复杂度有待分析;

3.启发式

使用启发式函数选择当前最好的位置放置皇后;启发式函数为当前位置的冲突数;

头文件:

#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;
}





你可能感兴趣的:(数据结构,算法,优化,测试,permutation,recursion)