本週做了兩道題,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;
}
到此基本上這個問題就說完了。