从皇后问题到图搜索

一、  问题介绍

输入:皇后的数目,例4

输出:第1行至第N行皇后对应的列号,例(2,4,1,3)

1.         采用递归策略实现N皇后问题,测试能够在短时间内找到解的最大N;

2.         采用宽度优先算法实现N皇后问题,测试能够在短时间内找到解的最大N;

3.         采用深度优先算法实现N皇后问题,测试能够在短时间内找到解的最大N。

4.         利用位运算算法实现N皇后问题,测试能够在短时间内找到解的最大N。

5.         采用爬山法实现N皇后问题,测试能够在短时间内找到解的最大N。


二、  程序设计与算法分析

(一) 采用递归算法的实现思路为:

Backtrack(Data)

Data:当前状态

回溯搜索算法 返回值:从当前状态到目标状态的路径(以规则表的形式表示)

或Fail。

1.   If Term(Data) ReturnNil; 


2.   If Deadend(Data)Return Fail; 


3.   Rules:=Apprules(Data);


4.   Loop: If Null(Rules)Return Fail; 


5.   R:=First(Rules); 


6.   Rules:=Tail(Rules); 


7.   RData:=Gen(R, Data); 


8.   Path:=Backtrack(RData);


9.   If Path=Fail Go Loop;


10.  Return Cons(R, Path);

 

(二) 一般图的搜索方法的实现思路:

1.   G=G0 (G0=s),Open:=(s);


2.   Closed:=( );


3.   Loop: If Open=( )Then Exit(Fail);

4.   n:=First(Open),Remove(n, Open), Add(n, Closed);

5.   If Goal(n),ThenExit(Success);


6.   Expand(n)→{mi}, G:=Add(mi, G);


7.   标记和修改指针: Add(mj,Open), 并标记mj到n的指针; 计算是否要修改mk、ml到n的指针; 计算是否要修改ml到其后继节点的指针;

8.对Open中的节点按某种原则重新排序;

9.Go Loop;

这是对于一般图搜索的普适算法。不同的图搜,例如宽度优先算法和深度优先算法的区别主要体现在该算法中的第8条原则,依据不同的原则对open表中的数据进行排序。其中深度优先的排序方式为每次将待扩展节点插入open表的头部,而宽度优先搜索每次将待扩展的节点放到open表的尾部。

也就是深度优先搜索方法:

1.   G := G0(G0=s), Open:= (s), Closed := ( ); 


2.   Loop: If Open = ( )Then Exit (Fail); 


3.   n := First(Open); 


4.   If Goal(n) Then Exit(Success); 


5.   Remove(n, Open),Add(n, Closed); 


6.   If Depth(n) ≥ Dm Go Loop; 


7.   Expand(n) →{mi}, G := Add(mi,G); 


8.   If 目标在{mi}中 ThenExit(Success); 


9.   Add(mj, Open), 并标记mj到n的指针;


10.  Go Loop;

深度优先搜索特点:

l  若存在多个目标状态,一般不能保证找到最优解


l  当深度限制不合理时,可能找不到解,可以将算法改为可变深度限制


l  最坏情况时,搜索空间等同于穷举 


l  与回溯法的差别:图搜索 


l  是一个通用的与问题无关的方法 


 


宽度优先搜索方法的实现思路:

1.   G := G0 (G0=s), Open:= (s), Closed := ( );

2.   Loop: If Open = ( )Then Exit (Fail);


3.   n := First(Open);

4.   If Goal(n) Then Exit(Success);

5.   Remove(n, Open),Add(n, Closed);

6.   Expand(n) →{mi}, G:=Add(mi, G);

7.   If 目标在{mi}中 ThenExit(Success);

8.   Add(Open, mj), 并标记mj到n的指针;

9.   Go Loop;

宽度优先搜索特点:

l  当问题有解时,一定能找到解

l  当问题为单位耗散值,且问题有多解时,一定能找到最优解

l  方法与问题无关,具有通用性

l  效率较低

l  属于图搜索方法

 

 

(三) 爬山法:

爬山法是A算法的一个特例。定义一个评价函数f,对当前的搜索状态进行评估,找出一个最有希望的节点来扩展。一般A算法中定义

l  评价函数的格式: f(n) = g(n)+ h(n)

l  f(n):评价函数

l  h(n):启发函数

而在爬山法中总是考虑与目标之间的差距

l  g(n) = 0,仅考虑h(n)

爬山法具体算法:

1.   n := s;

2.   Loop: If Goal(n) ThenExit(Success);

3.   Expand(n) →{mi}, 计算h(mi), nextn:= m (min h(mi) 的结点)

4.   If h(n) < h(nextn)Then Exit(Fail);

5.   n := nextn;

6.   Go Loop;

 

(四) 位运算法:

严格来说这并不应该单独作为一个算法来讲,因为他同样是用的深度优先的算法思想,其运行效率因为位运算速度的提高虽然比起不同搜索算法略有提高,不过也不是什么质的提高。不过考虑到其实现的巧妙性还是在这里讲一下。

算法思路:

l  二进制的每一位就代表一个皇后。

l  从第一层开始往下一层迭代,最后为全1表示一种可行解。

l  当前层皇后摆放情况取反后1为表示可放皇后的地方。

l  利用位运算x&(-x)便可以很快的找到从右往左第一个1的位置,在这个位置摆放皇后,往下一层搜索。

 

三、  结果及其分析

1.   递归和深度优先在较短时间内能解决33规模的皇后问题。

2.   利用为运算能在较短时间内解决34规模的皇后问题。

3.   利用宽度优先算法能在较短时间内解决14规模的皇后问题。宽度优先算法效率较低的原因是他无法直接回溯,这导致它不能在不同节点共用同一个棋盘。为保存每个节点的状态信息不仅需要较大的内存开销,在每一个节点间传递棋盘信息时,也需要较大的数据拷贝的时间开销。

4.   爬山法效率较高,经测试10w规模的皇后问题耗时一般在5s左右,100w。这主要是因为爬山法不是漫无目的的搜索,而是利用启发函数,尽量朝着目标方向靠近,这减少了大量无意义的枚举。


各种版本实现代码:

递归法:

// 递归法解n皇后问题,n在30以内效率可以接受
import java.util.*;
public class Queen1{ 
	private static int n;
	private static boolean ok;
	static int[] pos=new int[100];//第i行所放的棋子的列号
	public static void main(String[] args){
		Scanner in=new Scanner(System.in);
		while(in.hasNextInt()){
			n=in.nextInt();
			ok=false;
			dfs(0);
			if(!ok)
				System.out.println("no answer");
		}
	}
	public static void dfs(int deep){
		if(ok) return;
		if(deep==n){
			System.out.print("("+(pos[0]+1));
			for(int i=1;i





非递归的深度优先搜索:

// 迭代回溯,n皇后
import java.util.*;
public class Queen2{
	public static void main(String[] args){
		Scanner in=new Scanner(System.in);
		int n;
		while(in.hasNext()){
			n=in.nextInt();
			if(!queen(n))
				System.out.println("no answer");
		}
	}
	public static boolean queen(int n){
		int deep=0;
		int[] pos=new int[100];		
	label:
		while(deep>=0){
			while(pos[deep]




宽度优先搜索:

// bfs求解皇后问题
import java.util.*;
public class Queen4{
	static int n;
	static boolean ok=false;
	public static void main(String[] args){
		Scanner in=new Scanner(System.in);
		while(in.hasNext()){
			n=in.nextInt();
			while(n>39){
				System.out.println("Please be sure that n must less than 40. Try to input n again!");
				n=in.nextInt();
			}
			ok=false;
			bfs();
			if(!ok)
				System.out.println("No answer");
		}
	}
	public static void bfs(){
		Queue q=new LinkedList();
		q.clear();
		for(int i=0;i





位运算法:

#include "iostream"  
using namespace std;  
#include "time.h"  
  
// sum用来记录皇后放置成功的不同布局数;upperlim用来标记所有列都已经放置好了皇后。  
long sum = 0, upperlim = 1;       
  
// 试探算法从最右边的列开始。  
bool ok=false;
void test(long row, long ld, long rd)  
{  
	if(ok) return;
    if (row != upperlim)  
    {  
        // row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0,  
        // 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1  
        // 也就是求取当前哪些列可以放置皇后  
        long pos = upperlim & ~(row | ld | rd);   
        while (pos)    // 0 -- 皇后没有地方可放,回溯  
        {  
            // 拷贝pos最右边为1的bit,其余bit置0  
            // 也就是取得可以放皇后的最右边的列  
            long p = pos & -pos;                                                
  
            // 将pos最右边为1的bit清零  
            // 也就是为获取下一次的最右可用列使用做准备,  
            // 程序将来会回溯到这个位置继续试探  
            pos -= p;                             
  
            // row + p,将当前列置1,表示记录这次皇后放置的列。  
            // (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。  
            // (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。  
            // 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归  
            // 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位  
            // 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线  
            // 上产生的限制都被记录下来了  
            test(row + p, (ld + p) << 1, (rd + p) >> 1);                                
        }  
    }  
    else     
    {  
        // row的所有位都为1,即找到了一个成功的布局,回溯  
        ok=true;
        return;  
    }  
}  
  
int main(int argc, char *argv[])  
{  
    time_t tm;  
    int n = 16;  
  
    if (argc != 1)  
        n = atoi(argv[1]);  
    tm = time(0);  
  
    // 因为整型数的限制,最大只能32位,  
    // 如果想处理N大于32的皇后问题,需要  
    // 用bitset数据结构进行存储  
    if ((n < 1) || (n > 32))                   
    {  
        printf(" 只能计算1-32之间\n");  
        exit(-1);  
    }  
    printf("%d 皇后\n", n);  
  
    // N个皇后只需N位存储,N列中某列有皇后则对应bit置1。  
    upperlim = (upperlim << n) - 1;           
  
    test(0, 0, 0);  
    printf("共有%ld种排列, 计算时间%d秒 \n", sum, (int) (time(0) - tm));  
    system("pause");  
    return 0;  
}  






爬山法:

//爬山发解决皇后问题
#include 
#include 
#include 
#include 
using namespace std;
const int size=1000000; 
int board[size],ru[size*2],rd[size*2],n;

int f(){//统计对角线需要调整的皇后数
	int i,r=0; 
	memset(ru,0,sizeof(ru)); 
	memset(rd,0,sizeof(rd)); 
	for(i=0;i1) r+=ru[i]-1; 
		if(rd[i]>1) r+=rd[i]-1; 
	} 
	return r;
} 

void randgen(){ // 随即生成全排列
	for(int i=0;i>n){
		if(n==2 || n==3){
			cout<<"No answer"<1)
					++fnow;
				++rd[board[i]+j];
				if(rd[board[i]+j]>1)
					++fnow;
				--ru[board[j]-j+n-1];
				if(ru[board[j]-j+n-1])
					--fnow;
				--rd[board[j]+j];
				if(rd[board[j]+j])
					--fnow;
				++ru[board[j]-i+n-1];
				if(ru[board[j]-i+n-1]>1)
					++fnow;
				++rd[board[j]+i];
				if(rd[board[j]+i]>1)
					++fnow;
				swap(board[i],board[j]);
				if(fnow

博客主页

你可能感兴趣的:(搜索)