问题描述:
这道题目类似于RPG游戏,player的目标是在被monster吃掉之前,拿到treasure。
在游戏中,monster分为aggressive和non-aggressive两种,有不同的吃人能力。
player可以run或walk,时间消耗是一样的。
要求:求出最短拿到treasure的时间。
主要的思路是这样的:使用A*搜索算法,比普通的广度搜索速度上要快一点,但算法上复杂了一些。
在程序设计中使用面向对象的思想。
源代码如下:
Player类:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#ifndef PLAYER_H
#define PLAYER_H
#include<iostream>
using namespace std;
class Player{
public:
Player(){
}
//overload operator< function for heap comparision
friend bool operator< (const Player& a, const Player& b){
return a.fCost >= b.fCost ;
}
friend ostream& operator<<(ostream& out , const Player& p){
return out<<"("<<p.x<<","<<p.y<<")"<<endl;
}
public:
int x;
int y;
int fCost; //fCost = gCost + Hcost(evaluated)
int gCost;
int second; //record the time to reach this position
int myindex;
int parent;
const static int direction[8][2]; //move direction
};
#endif
const int Player::direction[8][2]={{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
这里需要注意的是,对于每个类对象共享的数据可以声明为static或const static,但是对于static数据需要在类外初始化。
Monster类:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#ifndef MONSTER_H
#define MONSTER_H
class Monster{
public :
Monster(){
pos =NULL; //初始化指针
}
Monster(bool isAggressive,int posNum):aggressiveOrNot(isAggressive),positionNum(posNum){
pos =NULL;
}
void clean(){
if( pos != NULL){
delete [] pos;
pos=NULL;
}
}
bool isSafe( int x,int y,int second){
int turn = (second % positionNum)*2 ;
second ++ ;
int nextTurn = (second % positionNum)*2 ;
bool flag1 = false,flag2 = false ;
if( true == aggressiveOrNot ){
flag1 = ( x <= pos[turn]+1 && x>=pos[turn]-1 && y<=pos[turn+1] + 1 && y>=pos[turn +1] -1) ? false : true;
flag2 = ( x <= pos[nextTurn]+1 && x>=pos[nextTurn]-1 && y<=pos[nextTurn+1] + 1 && y>=pos[nextTurn +1] -1) ? false : true;
}
else{
flag1 = ( pos[turn] == x && pos[turn+1] == y) ? false: true;
flag2 = ( pos[nextTurn] == x && pos[nextTurn+1] == y) ? false: true;
}
return (flag1 && flag2) ;
}
public:
bool aggressiveOrNot;
int positionNum;
int* pos;
static int MosterNum;
};
#endif
int Monster::MosterNum=0;
TreasureMap类:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#ifndef TREASURE_MAP_H
#define TREASURE_MAP_H
#define MAX_MAP_SIZE 10
class TreasureMap{
public:
TreasureMap(){
}
bool isInBoard(int x,int y){
return ( x<1 || x>height || y<1 || y>width )? false : true;
}
public:
char board[MAX_MAP_SIZE][MAX_MAP_SIZE];
int width;
int height;
};
#endif
下面是Main函数部分:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*
* POJ1924 The Treasure
* 根据player的起始位置,同时需要考虑monster的攻击,在最短的时间内找到treasure的位置
* 搜索策略:改进的深度优先搜索(A*搜索算法)
*/
#include<iostream>
#include<fstream>
#include<queue>
#include<ctime>
#include<cmath>
#include<stack>
#include "TreasureMap.h"
#include "Player.h"
#include "Monster.h"
#define MAX 100 //预定义Monster的最大数量
#define TABLE_SIZE 1000
using namespace std;
TreasureMap map;
Player player;
Monster* monster; //Monster数组
int treasureX,treasureY;
priority_queue<Player> PlayerHeap;
bool visitedArray[MAX_MAP_SIZE*MAX_MAP_SIZE+1]; //标记当前的Player位置是否已处于关闭列表中
Player table[TABLE_SIZE];
static int index=0;
Player resultPlayer;
void print(){
for( int i=1 ; i<= map.height ; i++){
for( int j=1 ; j<= map.width ; j++){
cout<<map.board[i][j]<<" ";
}
cout<<endl;
}
}
/*
* 计算gCost的值
* 评估函数
*/
inline int getHCost(int x,int y){
return abs( x-treasureX )+abs( y- treasureY );
}
void outputPath( Player dest){
stack<Player> PlayerStack;
Player tmp = dest ;
while( tmp.parent != -1){
PlayerStack.push(tmp);
tmp = table[ tmp.parent ];
}
while( ! PlayerStack.empty()){
cout<<PlayerStack.top()<<endl;
PlayerStack.pop();
}
}
void AstarSearch(){
while( ! PlayerHeap.empty()) //每次调用前重新清空堆
PlayerHeap.pop();
Player tmp,tmp1,curr; //tmp: walk / tmp1 : run
bool tmpSafe , tmp1Safe ,tmpTmp ,tmpTmp1 ; //safe state flag
int pos,second=0;
//step 1: put start state into the heap
player.fCost=0;
player.gCost=0;
player.second=0;
player.myindex=index++;
player.parent=-1;
table[player.myindex] = player;
PlayerHeap.push(player);
while( ! PlayerHeap.empty()){
curr = PlayerHeap.top();
PlayerHeap.pop();
pos = (curr.x-1) * map.width + curr.y ;
if( true == visitedArray[pos] ){ //排除重复检测
continue ;
}
//put state into closed list
visitedArray[ pos ] =true; //把检测完的节点放入关闭列表
map.board[curr.x][curr.y] = 'x'; //标记已经走过(处理过)的位置
if( treasureX == curr.x && treasureY == curr.y ){
cout<<"Time used :"<< curr.second << endl;
resultPlayer = curr ;
return ;
}
//step 2 :generate new state
for(int i=0 ; i<8 ;i++){
tmp.x = curr.x + Player::direction[i][0]; //walk
tmp.y = curr.y + Player::direction[i][1];
tmp.second = curr.second +1 ;
tmpSafe = true; //reset
tmp1Safe = true ;
tmpTmp = true ;
tmpTmp1 = true ;
if( ! map.isInBoard(tmp.x, tmp.y) ){
continue;
}
if( '#' == map.board[tmp.x][tmp.y] ){
continue;
}
tmp1.x = curr.x + Player::direction[i][0]*2; //run
tmp1.y = curr.y + Player::direction[i][1]*2;
tmp1.second= curr.second +1 ;
int j;
//检查walk是否安全
for(j=0 ; j< Monster::MosterNum ; j++){
//如果walk的位置已经被访问过
//如果该状态已经处于关闭列表
if( 'x' == map.board[tmp.x][tmp.y] ||
true == visitedArray[ (tmp.x-1) * map.width + tmp.y ] ){
tmpSafe=false;
break;
}
if(! monster[j].isSafe( tmp.x , tmp.y , tmp.second )){
tmpSafe=false;
break;
}
}
//检查run是否安全
for(j=0 ; j< Monster::MosterNum ; j++){
//如果run的位置已经被访问过
//如果该状态已经处于关闭列表
if( 'x' == map.board[tmp1.x][tmp1.y] ||
true == visitedArray[ (tmp1.x-1) * map.width + tmp1.y ] ||
! map.isInBoard( tmp1.x, tmp1.y ) ||
'#' == map.board[tmp1.x][tmp1.y]
){
tmp1Safe=false;
break;
}
if(! monster[j].isSafe( tmp1.x , tmp1.y, tmp1.second )){
tmp1Safe = false;
break ;
}
}
if( true == tmpSafe ){
tmp.gCost = curr.gCost +1 ;
tmp.fCost = tmp.gCost + getHCost( tmp.x , tmp.y );
tmp.myindex = index++ ;
tmp.parent = curr.myindex ;
table[tmp.myindex ] = tmp;
PlayerHeap.push(tmp);
}
if( true == tmp1Safe ){
tmp1.gCost= curr.gCost +1 ;
tmp1.fCost = tmp1.gCost + getHCost( tmp1.x , tmp1.y );
tmp1.myindex = index++ ;
tmp1.parent = curr.myindex ;
table[ tmp1.myindex ] = tmp1;
PlayerHeap.push( tmp1 );
}
}
}
cout<<"impossible"<<endl<<endl;
}
int main(){
ifstream in("1924.in");
char monsterTypeArray[MAX];
int arrayCount=0,i,j,tmp;
while(1){
in>>map.height>>map.width ;
if( 0 == map.height && 0 == map.width)
break;
for(i=1; i<= map.height ; i++){
for(j=1 ; j<= map.width ; j++){
in>>map.board[i][j];
switch( map.board[i][j] ){
case 'p': player.x = i;
player.y = j;
break;
case 't': treasureX = i; //switch-case-break!!
treasureY = j;
break;
case 'a':
case 'n': monsterTypeArray[arrayCount++]=map.board[i][j];
break;
default: break;
}
}
}
in>>Monster::MosterNum; //Monster的数量
if( 0 != Monster::MosterNum ){
monster=new Monster[Monster::MosterNum];
for(i=0 ; i<arrayCount ; i++){
in>>monster[i].positionNum;
monster[i].aggressiveOrNot = ( 'a' == monsterTypeArray[i] ) ? true : false;
monster[i].pos=new int[ monster[i].positionNum *2 ];
for(j=0 ;j< monster[i].positionNum *2 ; j++)
in>>monster[i].pos[j];
}
}
//in>>tmp; //cin、in直接跳过空行和空格
clock_t time=clock();
AstarSearch();
cout<<"计算用时:"<<clock()-time<<"MS"<<endl;
outputPath(resultPlayer);
}
return 0;
}
这里存在的可改进的地方:
1、由于题目需要进行大量的输入,所以避免使用C++中的流操作cin,而是使用C中的scanf函数,这样可以节省很多时间。
2、由于题目中run和walk的两种情况存在一定的逻辑关系,所以在排除不合理情况中可以进一步提前判断排除。
3、题目中可能存在player需要stand still的情况,但是我这一点没有考虑好,感觉太复杂了。
可以参考下面的这篇文章:http://www.chhaya.me/?p=204
这里我总结一下对A*算法的思想的体会:
A*算法的主要步骤是这样的:
1、首先将开始节点放入二叉堆中。
2、将二叉堆中的首元素弹出,放入关闭列表,判断它是否为目标节点,如果是,搜索算法结束,输出相关结果信息。如果不是,跳到第3步。
3、产生当前节点的所有可能的子节点。判断这些子节点是否可以放入堆中(如果已经处于关闭列表中,忽略 ;如果不处于关闭列表中,就放入堆中,这里由于数据结构使用的STL中的优先队列,所以没有考虑判断是否可以更新堆中已有的子节点,将所有符合筛选条件的节点都放入堆中),然后根据评估函数计算出这些子节点的fCost(和父节点有关的,fCost = gCost + Hcost)。
4、不断循环直到找到目标节点。
这里需要注意的地方:
1、由于没有对已经在堆中的节点进行判和更新,所以在堆中可能存在重复的节点,因此一旦这个节点弹出了,进入关闭列表了,那么后面的所有这个节点的副本都应该直接忽略掉。另外如果是类似于地图的搜索,那么对于已经进入关闭列表的元素,就需要做一下已访问过的标记,防止“死循环”访问。
2、对于一些复杂的应用,可能在向堆中插入子节点前,要进行重复状态检测,而这个就需要使用Hash来完成。
3、有时候为了还原搜索目标节点的路径,需要在节点中记录myindex和parent信息,而这时就需要利用数组来存储各个状态节点了,因为节点一旦被检测完毕,就会被从堆中弹出。