從皇后問題到圖搜索

一、  問題介紹

輸入:皇后的數目,例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;++i)
				System.out.print(","+(pos[i]+1));
			System.out.println(")");
			ok=true;
			return;
		}
		for(int i=0;i<n;++i){
			pos[deep]=i;
			if(judge(deep))
				dfs(deep+1);
		}
	}
	public static boolean judge(int deep){
		// boolean flag=true;
		for(int i=0;i<deep;++i){
			if(pos[i]==pos[deep] || Math.abs(deep-i)==Math.abs(pos[i]-pos[deep]))
				return false;		
		}
		return true;
	}
}





非遞歸的深度優先搜索:

// 迭代回溯,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]<n){
				if(judge(deep,pos)){
					++deep;
					pos[deep]=0;
					if(deep==n){
						for(int i=0;i<n;++i)
							System.out.printf("("+(i+1)+","+(pos[i]+1)+")\t");
						System.out.println();
						return true;
					}
					continue label;
				}
				++pos[deep];
			}
			if(pos[deep]==n){
				--deep;
				if(deep<0)
					break;
				++pos[deep];
				pos[deep+1]=0;
			}
		}
		return false;
	}
	public static boolean judge(int deep,int[] pos){
		for(int i=0;i<deep;++i){
			if(pos[i]==pos[deep] || Math.abs(i-deep)==Math.abs(pos[i]-pos[deep]))
				return false;
		}
		return true;
	}
}




寬度優先搜索:

// 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<ChessBoard> q=new LinkedList<ChessBoard>();
		q.clear();
		for(int i=0;i<n;++i){
			ChessBoard cb=new ChessBoard();
			cb.pos[0]=i;
			q.add(cb);
		}
		while(!q.isEmpty()){
			ChessBoard now=q.remove();
			for(int i=0;i<n;++i){
				ChessBoard next=new ChessBoard(now);
				++next.deep;
				next.pos[next.deep]=i;
				boolean flag=true;
				for(int j=0;j<next.deep;++j){
					if(next.pos[j]==next.pos[next.deep] || Math.abs(j-next.deep)==Math.abs(next.pos[j]-next.pos[next.deep])){
						flag=false;
						break;
					}
				}
				if(flag){
					if(next.deep==n-1){
						for(int j=0;j<n;++j)
							System.out.print("("+(j+1)+","+(next.pos[j]+1)+")\t");
						System.out.println();
						ok=true;
						return;
					}
					q.add(next);
				}
			}
		}
	}
}
class ChessBoard{
	public int[] pos=new int[40];// 第i行所放棋子的列數
	public int deep;
	public ChessBoard(){
		Arrays.fill(pos,0);
		deep=0;
	}
	public ChessBoard(ChessBoard r){
		pos=Arrays.copyOf(r.pos,40);
		deep=r.deep;
	}

}





位運算法:

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






爬山法:

</pre><pre name="code" class="cpp">//爬山發解決皇后問題
#include<string.h> 
#include<stdlib.h> 
#include<time.h> 
#include<iostream> 
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;i<n;i++){ 
		ru[board[i]-i+n-1]++; //負對角線
		rd[board[i]+i]++; //正對角線
	} 
	for(i=0;i<2*n-1;i++){ 
		if(ru[i]>1) r+=ru[i]-1; 
		if(rd[i]>1) r+=rd[i]-1; 
	} 
	return r;
} 

void randgen(){ // 隨即生成全排列
	for(int i=0;i<n;++i)
		board[i]=i;
	for(int i=0;i<n;++i){
		int pos=rand()%n;
		swap(board[i],board[pos]);
	}
} 

int main(){ 
	srand((unsigned)time(NULL)); 
	int i,j,temp,now,t1,t2,fnow; 
	// n=200000;
	while(cin>>n){
		if(n==2 || n==3){
			cout<<"No answer"<<endl;
			continue;
		}
		while(1){ 
			randgen(); 
			now=f();
		label:
			if(now==0) 
				break;
			int times=n*n;
			if(times<0) times=100000000;
			while(times--){
				int fnow=now,i=rand()%n,j=rand()%n;
				if(i==j) continue;
				--ru[board[i]-i+n-1];
				if(ru[board[i]-i+n-1])
					--fnow;
				--rd[board[i]+i];
				if(rd[board[i]+i])
					--fnow;
				++ru[board[i]-j+n-1];
				if(ru[board[i]-j+n-1]>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<now){
					now=fnow;
					goto label;
				}
				swap(board[i],board[j]);
				--rd[board[j]+i];
				--ru[board[j]-i+n-1];
				++rd[board[j]+j];
				++ru[board[j]-j+n-1];
				--rd[board[i]+j];
				--ru[board[i]-j+n-1];
				++rd[board[i]+i];
				++ru[board[i]-i+n-1];
			} 
		} 
		cout<<"("<<board[0]+1;
		for(int i=1;i<n;++i){
			cout<<","<<board[i]+1;
		}
		cout<<")"<<endl;
	}
	return 0; 
} 

博客主頁

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章