广度优先搜索.

这是一道poj1184的题目,由于求解的是最优解,所以首先想到的就是使用广度优先搜索。对于这道题目我同时使用set容器,来作为状态判重。

/*
 *	POJ 1184 聪明的打字员 
 *	版本1 : 普通的广度搜索 ,使用set进行状态判重 
*/
#include<iostream>
#include<queue>
#include<set>
#include<ctime>
//#include<fstream>
#define CODE_LENGTH 6 
using namespace std;

typedef struct str{
	
	int step;
	string code;
	int pos;					//光标的位置 
	str(){}
	//光标的初始位置为0 
	str(int s , string c):step(s),code(c),pos(0){}
	//重载operator< 
	friend bool operator<(const str& a, const str& b) {
		
		if(a.code < b.code )
			return true;
		if(a.code > b.code )
			return false;
			
		if( a.pos < b.pos )
			return true;
		if( a.pos > b.pos)
			return false ;
		
		return false ;
	}
}Str;

typedef pair<set<Str>::iterator,bool> Pair; 

queue<Str> Queue;			//bfs的队列的数据结构
set<Str> HashSet;			//状态判重的set 
//ofstream out("result.txt");
static int pushNUM=0;

//index :标记使用的操作
//state:表示当前节点 
Str changeCode(const Str& state,const int& index){			
	
	Str tmp = state ;
	char c;
	
	//1 : swap0 2: swap1 3:up 4:down 5:left 6:right
	switch( index ){
		
		case 1:	if( 0 == tmp.pos ) {}
				else {
					
					c= tmp.code[0];
					tmp.code[0]= tmp.code[tmp.pos];
					tmp.code[tmp.pos] = c;
				}
				break;
				
		case 2: if( 5 == tmp.pos ) {}
				else{
					
					c = tmp.code[5];
					tmp.code[5]= tmp.code[tmp.pos];
					tmp.code[ tmp.pos] = c ;
				}
				break;
				//注意这里是字符不是数字 
		case 3: if( '9' == tmp.code[ tmp.pos ] ){}
				else{
					
					tmp.code[tmp.pos] += 1 ; 
				}
				break;
				
		case 4: if( '0' == tmp.code[ tmp.pos ] ){}
				else{
					
					tmp.code[tmp.pos] -= 1 ; 
				}
				break;
		
		case 5: if( 0 == tmp.pos ) {}
				else{
					
					tmp.pos -=1 ;
				}
				break;
		case 6: if( 5 == tmp.pos ){}
				else{
					
					tmp.pos += 1;
				}
				break;
		default: cout<<"ERROR!"<<endl; break;
	}
	
	tmp.step +=1 ;

	return tmp;
} 

void bfsSearch(const Str& org,Str& dest){
	
	//step1: push the original node into the queue 
	Queue.push(org);
	HashSet.insert(org);
	Str curr,tmp;
	Pair check;
	
	while(! Queue.empty()){
		
		curr = Queue.front();
		Queue.pop();
		
		//step2 : check wether find the target node
		if( dest.code == curr.code){
			
			dest.step = curr.step ;
			return ;
		}
		
		for(int i=1; i<=6 ;i++){
			
			tmp = changeCode(curr,i);
			check = HashSet.insert(tmp);
			//检查是否为重复状态 
			if( false == check.second )
				continue ;
			else{
				Queue.push(tmp ) ;
				pushNUM++;
			}
				
		}
	}
	
}

int main(){
	
	string org,dest;
	cin>>org>>dest;
	
	Str strOrg(0,org),strDest(0,dest);
	clock_t time=clock();
	bfsSearch( strOrg, strDest );
	cout<<"Time consumed: "<<clock()-time <<" MS"<<endl;
	cout<<strDest.step<<endl;
	cout<<"pushNum= "<<pushNUM<<endl;
//	out.close();
	return 0;
}

在这里值得注意的地方如下:

 

1、由于重复状态的条件必须满足有相同的code和光标位置,所以在str结构体中重载operator<操作时,需要同时考虑code和pos两个域,保证这两个状态的都相同时,是不会被插入到set中的。因为对于set来说,他判断是否insert时发生冲突是通过两次使用operator<对两个状态进行检测,发现得出相同的结果。如果比较对象域是string这样的标准类型的话,那么在operator<函数中不能出现等于号,即写成这样是错误的:return a.code <= b.code,因为这样的话,就不会发生冲突了

 

2、由于没有进行其他方面的优化,所以这个程序的效率是比较差的。

 

下面是我改用hash处理状态判重的一个程序:

 

/*
 *	POJ 1184 聪明的打字员 
 *	版本2 : 使用hash进行状态判重 
*/
#include<iostream>
#include<queue>
#include<set>
#include<ctime>
//#include<fstream>
#define HashTableSize 262147
using namespace std;

typedef struct str{
	
	int step;
	string code;
	int pos;					//光标的位置 
	str(){}
	//光标的初始位置为0 
	str(int s , string c):step(s),code(c),pos(0){}

}Str,*PStr;

//typedef pair<set<Str>::iterator,bool> Pair; 

queue<Str> Queue;			//A*的堆数据结构 
set<Str> HashSet;			//状态判重的set 
PStr hashTable[HashTableSize]={NULL}; //状态判重的hash表 
//static int conflict=0,hashNum=0;
//ofstream out("result.txt");




int getHashValue(const Str& state){
	
	string str(state.code);
	str = str.append(1,state.pos+'0');
	int hashValue=0 ,len= str.length()+1;
	
	for(int i=0; i<len ;i++)
		hashValue = 37*hashValue+ str[i];
		
	hashValue %= HashTableSize ;
	if( hashValue <0 )
		hashValue += HashTableSize ;
		
	return hashValue ;
}

inline bool isEqual(const Str& a,const Str& b){
	
	return ((a.code==b.code) && (a.pos == b.pos ))? true : false ;
}

bool insertHashTable(const Str& state){
	
	int hashValue = getHashValue(state);

	if( hashTable[hashValue] && !isEqual(state , *hashTable[hashValue])){
		
		while(hashTable[hashValue])
			hashValue ++;

	}
	if( !hashTable[hashValue]){
		
		hashTable[hashValue ] = new Str();
		*hashTable[hashValue ]=state ;
		return true ;
	}
	
	return false ;
	
}

//index :标记使用的操作
//state:表示当前节点 
Str changeCode(const Str& state,const int& index){			
	
	Str tmp = state ;
	char c;
	
	//1 : swap0 2: swap1 3:up 4:down 5:left 6:right
	switch( index ){
		
		case 1:	if( 0 == tmp.pos ) {}
				else {
					
					c= tmp.code[0];
					tmp.code[0]= tmp.code[tmp.pos];
					tmp.code[tmp.pos] = c;
				}
				break;
				
		case 2: if( 5 == tmp.pos ) {}
				else{
					
					c = tmp.code[5];
					tmp.code[5]= tmp.code[tmp.pos];
					tmp.code[ tmp.pos] = c ;
				}
				break;
				//注意这里是字符不是数字 
		case 3: if( '9' == tmp.code[ tmp.pos ] ){}
				else{
					
					tmp.code[tmp.pos] += 1 ; 
				}
				break;
				
		case 4: if( '0' == tmp.code[ tmp.pos ] ){}
				else{
					
					tmp.code[tmp.pos] -= 1 ; 
				}
				break;
		
		case 5: if( 0 == tmp.pos ) {}
				else{
					
					tmp.pos -=1 ;
				}
				break;
		case 6: if( 5 == tmp.pos ){}
				else{
					
					tmp.pos += 1;
				}
				break;
		default: cout<<"ERROR!"<<endl; break;
	}
	
	tmp.step +=1 ;

	return tmp;
} 

void bfsSearch(const Str& org,Str& dest){
	
	//step1: push the original node into the queue 
	Queue.push(org);
	insertHashTable(org);
	Str curr,tmp;
	bool check;
	
	
	
	while(! Queue.empty()){
		
		curr = Queue.front();
		Queue.pop();
		
		//step2 : check wether find the target node
		if( dest.code == curr.code){
			
			dest.step = curr.step ;
			return ;
		}
		
		for(int i=1; i<=6 ;i++){
			
			tmp = changeCode(curr,i);
			check = insertHashTable(tmp);
			//检查是否为重复状态 
			if( false == check )
				continue ;
			else{
				Queue.push(tmp ) ;
			
			}
				
		}
	}
	
}

int main(){
	
	string org,dest;
	cin>>org>>dest;
	
	Str strOrg(0,org),strDest(0,dest);
	clock_t time=clock();
	bfsSearch( strOrg, strDest );
	cout<<"Time consumed: "<<clock()-time <<" MS"<<endl;
	cout<<strDest.step<<endl;
//	out.close();
	return 0;
}

这里我把光标的位置直接放到code的最后一位,这样直接作为字符串来处理,方便很多。但是测试下来,性能没有太大的提高,因为需要搜索的状态实在太多,到后面hash冲突现象很严重。

 

后来我用A*算法来优化BFS搜索,代码如下:

/*
 *	POJ 1184 聪明的打字员 
 *	版本3 : A* ,使用set进行状态判重 
*/
#include<iostream>
#include<queue>
#include<set>
#include<ctime>
//#include<fstream>
#define CODE_LENGTH 6 
#define TABLE_SIZE 1<<16
using namespace std;

typedef struct str{
	
	int step;
	string code;
	int pos;					//光标的位置 
	int fCost;
	int hCost;
	int myindex;
	int parent ;
	
	str(){}
	//光标的初始位置为0 
	str(int s , string c):step(s),code(c),pos(0){}
	//重载operator< 
	friend bool operator<(const str& a, const str& b) {
		
		//注意一定要写成< ,这样才能排除code相同的Str 
		return a.code < b.code ;
	}
}Str;

//堆的比较函数 
struct compareFunction{
	
	bool operator()(const Str& a,const Str& b){
		
		return a.fCost>=b.fCost ;
	}
	
};

typedef pair<set<string>::iterator,bool> Pair; 

priority_queue<Str,vector<Str>,compareFunction> Heap;			
set<string> ClosedSet;					//A*的关闭列表
set<string> OpenSet;

//ofstream out("result.txt");
static int storageTableIndex=0;
//目标串 
string cc="654321";
static int pushNum=0;

//index :标记使用的操作
//state:表示当前节点 
Str changeCode(const Str& state,const int& index){			
	
	Str tmp = state ;
	char c;
	
	//1 : swap0 2: swap1 3:up 4:down 5:left 6:right
	switch( index ){
		
		case 1:	if( 0 == tmp.pos ) {}
				else {
					
					c= tmp.code[0];
					tmp.code[0]= tmp.code[tmp.pos];
					tmp.code[tmp.pos] = c;
				}
				break;
				
		case 2: if( 5 == tmp.pos ) {}
				else{
					
					c = tmp.code[5];
					tmp.code[5]= tmp.code[tmp.pos];
					tmp.code[ tmp.pos] = c ;
				}
				break;
				//注意这里是字符不是数字 
		case 3: if( '9' == tmp.code[ tmp.pos ] ){}
				else{
					
					tmp.code[tmp.pos] += 1 ; 
				}
				break;
				
		case 4: if( '0' == tmp.code[ tmp.pos ] ){}
				else{
					
					tmp.code[tmp.pos] -= 1 ; 
				}
				break;
		
		case 5: if( 0 == tmp.pos ) {}
				else{
					
					tmp.pos -=1 ;
				}
				break;
		case 6: if( 5 == tmp.pos ){}
				else{
					
					tmp.pos += 1;
				}
				break;
		default: cout<<"ERROR!"<<endl; break;
	}
	
	tmp.step +=1 ;

	return tmp;
} 

//局面评估函数 
int calCost(const string& str){
	
	int len= str.length(),cost=0 ;
	for( int i=0 ; i< len ; i++){
		
		if( str[i]!=cc[i])
			cost++;
	}
	
	return cost;
} 

void aStarSearch(const Str& org,Str& dest){
	
	//step1: push the original node into the Heap
	Heap.push(org);
	Str curr,tmp;
	Pair check ,check1;
	string tmpString;
	
	tmpString = org.code;
	tmpString = tmpString.append(1,org.pos+'0');
	OpenSet.insert(tmpString);
	
	while(! Heap.empty()){
		
		curr = Heap.top();		
		Heap.pop();
		tmpString = curr.code;
		tmpString = tmpString.append(1,curr.pos+'0');
		//step2: 进入关闭列表 
		check = ClosedSet.insert(tmpString);
		
		if( false == check.second )
			continue ;
		
		//step3 : check wether find the target node
		if( dest.code == curr.code){
			
			dest.step = curr.step ;
			dest.parent = curr.parent;
			return ;
		}
		
		for(int i=1; i<=6 ;i++){
			
			tmp = changeCode(curr,i);
			
			tmpString = tmp.code;
			tmpString = tmpString.append(1,tmp.pos+'0');
			check = ClosedSet.insert(tmpString);
			check1 = OpenSet.insert(tmpString);
			
			//检查是否为关闭状态,是否为已有状态 
			if( false == check.second || false == check1.second )
				continue ;
				
			else{
				//step4: push new state into heap
				ClosedSet.erase(check.first) ;
				
				tmp.hCost = curr.hCost+1 ;
				tmp.fCost = tmp.hCost + calCost( tmp.code );
				tmp.myindex = storageTableIndex++;
				tmp.parent = curr.myindex ;
//				storageTable[tmp.myindex] = tmp;
				
				Heap.push(tmp);
				pushNum++;
			}
				
		}
	}
	
}

int main(){
	
	string org,dest;
	cin>>org>>dest;
	
	Str strOrg(0,org),strDest(0,dest);
	strOrg.fCost=0;
	strOrg.hCost=0;
	strOrg.parent = -1;
	strOrg.myindex=storageTableIndex++;
//	storageTable[strOrg.myindex]=strOrg;
	
	clock_t time=clock();
	aStarSearch( strOrg, strDest );
	cout<<"Time consumed: "<<clock()-time <<" MS"<<endl;
	cout<<strDest.step<<endl;
	cout<<"pushNum= "<<pushNum<<endl;
//	cout<<"pushNum= "<<pushNUM<<endl;
//	out.close();
	return 0;
}

 

这里有一些值得注意的地方:

 

1、这里关闭列表使用的是set容器,另外和传统的A*算法不同,我这里没有将产生的已有状态,但还没有进入关闭列表的状态,加入到堆数据结构中,因为可以分析出来,在这道题目中,这些状态是不可能产生更好的搜索路径的。这样大大提高了搜索效率。

但是在有些例子下,还是搜索时间太长了,需要进一步剪枝优化。

 

在这些程序中使用到的一些技巧吧:

1、使用pair这个utility。声明定义pair,如:typedef pair<set<string>::iterator , bool > Pair ;

pair的两个域可以分别用first和second来引用。

2、set容器中的insert有一个版本为 pair insert( constTYPE &val ); 注意返回的是pair。

3、string中的一个append版本为:basic_string &append( size_type num, char ch );  在字符串的末尾添加num个字符ch 。

文章转载出处:http://blog.csdn.net/urecvbnkuhbh_54245df/article/details/5853291

posted @ 2012-10-16 12:10  梦你所梦  阅读(154)  评论(0编辑  收藏  举报