博客轉載自:http://blog.csdn.net/u012907049/article/details/75004124
前言
本篇文章是機器人自動尋路算法實現的第二章。我們要討論的是一個在一個M×N的格子的房間中,有若干格子里有灰塵,有若干格子里有障礙物,而我們的掃地機器人則是要在不經過障礙物格子的前提下清理掉房間內的灰塵。具體的問題情景請查看人工智能: 自動尋路算法實現(一、廣度優先搜索)這篇文章,即我們這個系列的第一篇文章。在上一篇文章里,我們介紹了通過廣度優先搜索算法來實現掃地機器人自動尋路的功能。在這篇文章中,我們要介紹與之相對應的另一種算法:深度優先搜索算法。
正文
算法介紹
深度優先算法,與廣度優先搜索算法類似,唯一不同的是,它是沿着樹的深度遍歷數的節點,盡可能遍歷搜索數的分支。也就是說,從根節點開始,它會首先遍歷根節點的第一個子節點,接着遍歷子節點的第一個子節點,並沿着樹的深度一直遍歷下去。下面兩幅圖就是深度優先搜索和廣度優先搜索遍歷順序的對比,圖中節點上的數字就表示該節點在這個算法中被遍歷到的順序。

深度優先搜索

廣度優先搜索
深度優先搜索的算法偽代碼如下:
開始
將頂點入棧
循環
當棧為非空時,繼續執行,否則算法結束
取得隊棧頂點V;訪問並標記為已訪問
如果頂點V有未被訪問過的子節點
查找頂點V的第一個未被訪問過的子節點W1
標記W1為已訪問
將W1入棧
否則將頂點V出棧
可以看出相對於上一篇文章中的廣度優先搜索算法,深度優先搜索只是更改了一個數據結構:將隊列改為棧。這里也是用到了棧的后進先出的特性。
void Robot::caculate(State* state)
{
//獲取當前機器人的坐標
int x = state->getRobotPosition().getX();
int y = state->getRobotPosition().getY();
//如果當前的點是灰塵並且沒有被清理
if (map[x][y] == '*' && !isCleared(Point(x, y), state->getDirtList()))
{
State* newState = new State();
list<Point> newdirtList;
//在新的state中,將灰塵列表更新,即去掉當前點的坐標
for (Point point : state->getDirtList())
{
if (point.getX() == x && point.getY() == y)
continue;
else
newdirtList.push_back(Point(point.getX(), point.getY()));
}
newState->setDirtList(newdirtList);
newState->setRobotPosition(Point(x, y));
//C代表Clean操作
newState->setRobotOperation("C");
newState->setPreviousState(state);
//若新產生的狀態與任意一個遍歷過的狀態都不同,則進入隊列
if (!isDuplicated(newState))
{
//深度搜索
Robot::instance()->depthSearch.push(*newState);
//廣度搜索
//Robot::instance()->breadthSearch.push(*newState);
closeList.push_back(*newState);
cost++;
return;
}
}
//若當前機器人坐標下方有格子並且不是障礙物
if (x + 1 < rowNum)
{
if (map[x + 1][y] != '#')
{
State* newState = new State();
newState->setDirtList(state->getDirtList());
newState->setRobotPosition( Point(x + 1, y));
//S代表South,即向下方移動一個格子
newState->setRobotOperation("S");
newState->setPreviousState(state);
if (!isDuplicated(newState))
{
//深度搜索
Robot::instance()->depthSearch.push(*newState);
//廣度搜索
//Robot::instance()->breadthSearch.push(*newState);
//加入到closeList中
closeList.push_back(*newState);
cost++;
return;
}
}
}
//若當前機器人坐標上方有格子並且不是障礙物
if (x - 1 >= 0)
{
if (map[x - 1][y] != '#')
{
State* newState = new State();
newState->setDirtList(state->getDirtList());
newState->setRobotPosition(Point(x - 1, y));
//W代表向上方移動一個格子
newState->setRobotOperation("W");
newState->setPreviousState(state);
if (!isDuplicated(newState))
{
//深度搜索
Robot::instance()->depthSearch.push(*newState);
//廣度搜索
//Robot::instance()->breadthSearch.push(*newState);
//加入到closeList中
closeList.push_back(*newState);
cost++;
return;
}
}
}
//若當前機器人坐標左側有格子並且不是障礙物
if (y - 1 >= 0)
{
if (map[x][y - 1] !='#')
{
State* newState = new State();
newState->setDirtList(state->getDirtList());
newState->setRobotPosition(Point(x , y-1));
//A向左側移動一個格子
newState->setRobotOperation("A");
newState->setPreviousState(state);
if (!isDuplicated(newState))
{
//深度搜索
Robot::instance()->depthSearch.push(*newState);
//廣度搜索
//Robot::instance()->breadthSearch.push(*newState);
//加入到closeList中
closeList.push_back(*newState);
cost++;
return;
}
}
}
//若當前機器人坐標右側有格子並且不是障礙物
if (y + 1 < coloumnNum)
{
if (map[x][y + 1] != '#')
{
State* newState = new State();
newState->setDirtList(state->getDirtList());
newState->setRobotPosition(Point(x, y + 1));
newState->setRobotOperation("D");
newState->setPreviousState(state);
if (!isDuplicated(newState))
{
//深度搜索
Robot::instance()->depthSearch.push(*newState);
//廣度搜索
//Robot::instance()->breadthSearch.push(*newState);
//加入到closeList中
closeList.push_back(*newState);
cost++;
return;
}
}
}
Robot::instance()->depthSearch.pop();
}
代碼與廣度優先搜索大體相同,但也有一部分差異。除了運用棧來代替隊列的數據結構之外,主要的差異在於calculate方法。在每一個可能產生的節點代碼之后多了一個return語句。我們可以回顧文章開頭的算法偽代碼:
開始
將頂點入棧
循環
當棧為非空時,繼續執行,否則算法結束
取得隊棧頂點V;訪問並標記為已訪問
如果頂點V有未被訪問過的子節點
查找頂點V的第一個未被訪問過的子節點W1
標記W1為已訪問
將W1入棧
否則將頂點V出棧
查找第一個未被訪問過的子節點。所以每次只遍歷一個子節點。我們的return就體現出了這一點:當一個節點符合要求,我們將它入棧並標記為已訪問之后,就結束這次遍歷。我們還可以看到方法的最后多了一個
Robot::instance()->depthSearch.pop();
語句。這里就對應偽代碼中的“否則將頂點V出棧”:當方法執行到這一步的時候,說明這個節點已經沒有未遍歷過的子節點(如果有的話一定會在前面的某一步驟return而執行不到這一步),那么此時就要將這個節點(state)出棧。
運行結果與廣度優先搜索的對比
對於如下的一個地圖:
@#* *__ #*_
本算法的結果為:
Please Enter Row Number: 3 Please Enter Colomn Number: 3 Please Enter the Elements in row 1: @#* Please Enter the Elements in row 2: *__ Please Enter the Elements in row 3: #*_ S C E S C N E N C 14
其中14是遍歷的節點數量。
對比一下上一篇文章中廣度優先搜索的結果:
Please Enter Row Number: 3 Please Enter Colomn Number: 3 Please Enter the Elements in row 1: @#* Please Enter the Elements in row 2: *__ Please Enter the Elements in row 3: #*_ S C E S C N E N C 45
可以看出遍歷的節點明顯少了很多。這也是深度優先搜索的一個優點。不過,深度優先搜索也有自己的不足。比如我們來看一下上一篇文章中的里一個例子:
*#_* _*__ _#_@
本算法的輸出為
Please Enter Row Number: 3 Please Enter Colomn Number: 4 Please Enter the Elements in row 1: *#_* Please Enter the Elements in row 2: _*__ Please Enter the Elements in row 3: _#_@ N N C S S W N W C W N C 15
對比一下廣度優先搜索的結果:
Please Enter Row Number: 3 Please Enter Colomn Number: 4 Please Enter the Elements in row 1: *#_* Please Enter the Elements in row 2: _*__ Please Enter the Elements in row 3: _#_@ N N C S W W C W N C 56
可以看出雖然遍歷了節點仍然少了很多,但是機器人所用的步驟要多了一些。這就是深度優先搜索的另一個特性:找到的解並不是最優解。這是由深度搜索的“深度遍歷”所決定的。比如,當最優解存在於根節點的右側子節點,而我們的程序先遍歷的是左側子節點。如果在遍歷左側子節點的過程中找到了一個解,就算這個解不是最優解,程序依然結束。此時我們找到的顯然不是最優解。
我們可以再考慮另外一種極端的情況:最優解依然存在於右側子節點,而我們的程序遍歷的是左側子節點。如果此時左側的子節點有無限多個,並且其中沒有解的話,那么我們的程序將永遠遍歷左側的節點,並且永遠找不到解。所以我們可以分析出深度優先搜索的特點:當一個問題有解的時候,深度優先搜索並不一定能找到解。並且就算找到了解,也並不一定是最優解。
由此可見,深度優先搜索的應用場景應該是不需要找到最優解的問題,並且問題的可能性應該是有限的。在我們遍歷的樹有很多分支節點的情況下,深度優先搜索的效率顯然要比廣度優先搜索高。
深度優先搜索的空間復雜度為
結束語
這個系列的前兩篇文章比較了廣度優先搜索與深度優先搜索在自動尋路問題中的體現。下一篇文章中,我將介紹另一種算法: A*算法。
