POJ1184Clerver_Writer:广度优先搜索

 这是一道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( const TYPE &val ); 注意返回的是pair。

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

 

 

你可能感兴趣的:(POJ1184Clerver_Writer:广度优先搜索)