本周做了两道题,Fast Robot和Picking up Jewels
先说Fast Robot,要求找出从起点到终点最少拐弯次数。
这道题其实有一种很简单的算法:
1. 从起点开始,将拐1次弯的点全部入队,map[i][j].path全部标记成1(并且标记为已访问),然后起点出队。
2. 拐一次弯的第一个点出队,将第一个点作为起点,所有拐一次弯并且未入队的点入队,即总计拐两次弯的点入队。以此类推,直到将所有的拐一次弯的点全部出队。
3. 同理所有的可以走到的点都会入队出队,直到找到终点。
在此原理的基础上,只要找到了终点,那就是最短的拐弯次数。
可是做的时候并没有去思考过多,上来就用了特别朴素的方法,也特别复杂。换了好几次尝试,从不用指针实现的队到用指针实现的队,最后又换回了不用指针实现的队。
首先
int DIR[4][2] ={{-1,0},{0,1},{1,0},{0,-1}}; //正点方向顺时针 上右下左
void BFS(Node start, Node end) {
Node t1;
enqueue(start); // 将起始点进队
int newValue = 0;
int i;
int dx = 0, dy = 0;
while(!isEmpty()) { // 如果队不为空
t1 = dequeue(); // 出队
for(i=0; i<4; i++) //扫描当前节点的4个方向
{
dx = t1.x+DIR[i][0];
dy = t1.y+DIR[i][1];
if(dx == end.x && dy == end.y)
{
if(t1.dir != i) //如果当前节点方向与即将要前进的方向(到终点)不一致,则拐弯数+1
{
Answer = Answer<t1.value+1?Answer:t1.value+1; //比较新的路径的拐弯数和当前所记录的最小值,取小的数
} else {
Answer = Answer<t1.value?Answer:t1.value;
}
#ifdef TEST
cout<<"find end!:"<<dx<<","<<dy<<", Answer = "<<Answer<<endl;
printOMap();
#endif
continue; // 终点不进队,所以用continue
}
if(dx>=0 && dy>=0 && dx<N && dy<M && map[dx][dy].d=='0') // '0'表示可走的路
{
if(t1.dir != i) //如果当前节点方向与即将要前进的方向不一致,则拐弯数+1
{
newValue = t1.value+1;
}else {
newValue = t1.value;
}
if(map[dx][dy].dir == -1 || newValue<=map[dx][dy].value) { //如果 要判断的点是从没访问过的点 或者 已经访问过但是新的路径拐弯次数少于之前保存的拐弯次数
map[dx][dy].dir = i; // 将所判断的点的方向置为当前行进方向
map[dx][dy].value = newValue; // 将所判断的点的拐弯书置为新的路径拐弯次数
enqueue(map[dx][dy]); // 进队 这里本来是要用指针来实现的,但是因为本题设计到拐弯数和方向两个变量调控的原因,
// 只保存一个值的话会有问题。除非添加多个方向的变量,否则最好是使用拷贝的方式进队
}
}
}
}
}
我是那种建模想象能力不太好的人,一般都需要画出图来理解,所以我test的方式是把整个map当前的状态绘制了出来。
void printOMap()
{
cout<<"---------------------N="<<N<<",M="<<M<<endl;
int i,j;
for(i=0; i<N; i++)
{
for(j=0; j<M; j++)
{
if(map[i][j].value == BMAX)
{ cout<<"*"<<" ";
}else{
cout<<map[i][j].value<<" ";
}
}
cout<<endl;
}
}
7 7 // 7列7行 1 2 7 5 // start end 1111111 // 1是墙,0是路 0000011 1011001 1011100 1011110 1000000 1111111 | find end!:4,6, Answer = 5 ---------------------N=7,M=7 * * * * * * * 1 0 0 0 0 * * * 1 * * 1 2 * * 1 * * * 3 4 * 1 * * * * -1 * 1 2 2 2 * * * * * * * * * | find end!:4,6, Answer = 3 ---------------------N=7,M=7 * * * * * * * 1 0 0 0 0 * * * 1 * * 1 2 * * 1 * * * 3 4 * 1 * * * * -1 * 1 2 2 2 2 2 * * * * * * * |
因此最小的拐弯数就是3了。
在做这道题的时候,我曾遇到一个百思难得其解的问题。就是小的数据运算结果都是OK的,但是一碰到大的数据的时候,DFS后的数组输出就有问题了。
100x120的数据为什么输出变成了7行1列呢。研究了半天没整明白。最后在老大的帮助下,发现原来我的循环队列写成了顺序队列,40000的长度都没打住,把N和M的空间给踩了,可见这复杂的。也由此暴露出来了一个问题,这基础多不牢固能犯这错误,哈哈。
现在来说说队列怎么写吧。
typedef struct {
int x;
int y;
int dir;
int value;
char d;
}Node; // 节点结构体
Node map[MAX][MAX]; // 节点数组用于存储map
typedef struct {
Node data[LENGTH];
int rear;
int front;
}Queue; // 队列结构体
Queue que; // 队列
Node map[MAX][MAX]; 输入是char型的字符,那么怎么存储成Node的数组呢
如果是char map[MAX][MAX],那么你可以这样
for(i=0; i<N; i++) {
cin>>map[i];
}
然而Node型的你则需要逐个地输入了。
for(i=0; i<N; i++)
{
for(j=0; j<M; j++)
{
cin>>map[i][j].d;
map[i][j].x = i;
map[i][j].y = j;
map[i][j].dir = -1;
map[i][j].value = 999999;
}
}
队列一般需要队空、队满、入队、出队、获取对头、初始化等方法。
bool isEmpty()
{
if(que.rear == que.front){
return true;
}else {
return false;
}
}
bool isFull()
{
if((que.rear+1)%LENGTH == que.front){
return true;
}else {
return false;
}
}
void enqueue(Node n)
{
#ifdef QLOG
cout<<"enqueue:"<<n.x<<","<<n.y<<",dir = "<<n.dir<<",value = "<<n.value<<endl;
#endif
if(!isFull())
{
que.data[que.rear] = n;
que.rear = (que.rear+1)%LENGTH;
// que.rear++; // 顺序队列
} else {
#ifdef QLOG
cout<<"enqueue fail because queue is full"<<endl;
#endif
}
}
Node dequeue()
{
Node n = que.data[que.front];
if(!isEmpty())
{
que.front = (que.front+1)%LENGTH;
//que.front++; // 顺序队列
#ifdef QLOG
cout<<"dequeue:"<<n.x<<","<<n.y<<",dir = "<<n.dir<<",n.value = "<<n.value<<endl;
#endif
} else {
#ifdef QLOG
cout<<"Queue is empty"<<endl;
#endif
}
return n;
}
Node getTop()
{
Node n = que.data[que.front];
#ifdef QLOG
cout<<"getTop:"<<n.x<<","<<n.y<<",dir = "<<n.dir<<endl;
#endif
return n;
}
void initQueue()
{
que.front = que.rear = 0;
}
到此基本上这个问题就说完了。