深度優先搜索和廣度優先搜索的比較與分析


深度優先搜索和廣度優先搜索的深入討論

 

(一)深度優先搜索的特點是:

(1)無論問題的內容和性質以及求解要求如何不同,它們的程序結構都是相同的,即都是深度優先算法(一)和深度優先算法(二)中描述的算法結構,不相同的僅僅是存儲結點數據結構和產生規則以及輸出要求。

(2)深度優先搜索法有遞歸以及非遞歸兩種設計方法。一般的,當搜索深度較小、問題遞歸方式比較明顯時,用遞歸方法設計好,它可以使得程序結構更簡捷易懂。當搜索深度較大時,當數據量較大時,由於系統堆棧容量的限制,遞歸容易產生溢出,用非遞歸方法設計比較好。

(3)深度優先搜索方法有廣義和狹義兩種理解。廣義的理解是,只要最新產生的結點(即深度最大的結點)先進行擴展的方法,就稱為深度優先搜索方法。在這種理解情況下,深度優先搜索算法有全部保留和不全部保留產生的結點的兩種情況。而狹義的理解是,僅僅只保留全部產生結點的算法。本書取前一種廣義的理解。不保留全部結點的算法屬於一般的回溯算法范疇。保留全部結點的算法,實際上是在數據庫中產生一個結點之間的搜索樹,因此也屬於圖搜索算法的范疇。

(4)不保留全部結點的深度優先搜索法,由於把擴展望的結點從數據庫中彈出刪除,這樣,一般在數據庫中存儲的結點數就是深度值,因此它占用的空間較少,所以,當搜索樹的結點較多,用其他方法易產生內存溢出時,深度優先搜索不失為一種有效的算法。

(5)從輸出結果可看出,深度優先搜索找到的第一個解並不一定是最優解。

如果要求出最優解的話,一種方法將是后面要介紹的動態規划法,另一種方法是修改原算法:把原輸出過程的地方改為記錄過程,即記錄達到當前目標的路徑和相應的路程值,並與前面已記錄的值進行比較,保留其中最優的,等全部搜索完成后,才把保留的最優解輸出。

 

二、廣度優先搜索法的顯著特點是:

(1)在產生新的子結點時,深度越小的結點越先得到擴展,即先產生它的子結點。為使算法便於實現,存放結點的數據庫一般用隊列的結構。

(2)無論問題性質如何不同,利用廣度優先搜索法解題的基本算法是相同的,但數據庫中每一結點內容,產生式規則,根據不同的問題,有不同的內容和結構,就是同一問題也可以有不同的表示方法。

(3)當結點到跟結點的費用(有的書稱為耗散值)和結點的深度成正比時,特別是當每一結點到根結點的費用等於深度時,用廣度優先法得到的解是最優解,但如果不成正比,則得到的解不一定是最優解。這一類問題要求出最優解,一種方法是使用后面要介紹的其他方法求解,另外一種方法是改進前面深度(或廣度)優先搜索算法:找到一個目標后,不是立即退出,而是記錄下目標結點的路徑和費用,如果有多個目標結點,就加以比較,留下較優的結點。把所有可能的路徑都搜索完后,才輸出記錄的最優路徑。

(4)廣度優先搜索算法,一般需要存儲產生的所有結點,占的存儲空間要比深度優先大得多,因此程序設計中,必須考慮溢出和節省內存空間得問題。

(5)比較深度優先和廣度優先兩種搜索法,廣度優先搜索法一般無回溯操作,即入棧和出棧的操作,所以運行速度比深度優先搜索算法法要快些。

 總之,一般情況下,深度優先搜索法占內存少但速度較慢,廣度優先搜索算法占內存多但速度較快,在距離和深度成正比的情況下能較快地求出最優解。因此在選擇用哪種算法時,要綜合考慮。決定取舍。

  

三、基本搜索算法比較和搜索算法的優化

 

搜索算法是利用計算機的高性能來有目的的窮舉一個問題的部分或所有的可能情況,從而求出問題的解的一種方法。搜索過程實際上是根據初始條件和擴展規則構造一棵解答樹並尋找符合目標狀態的節點的過程。所有的搜索算法從其最終的算法實現上來看,都可以划分成兩個部分──控制結構和產生系統,而所有的算法的優化和改進主要都是通過修改其控制結構來完成的。現在主要對其控制結構進行討論,因此對其產生系統作如下約定:

Function ExpendNode(Situation:Tsituation;ExpendWayNo:Integer):TSituation;

表示對給出的節點狀態Sitution采用第ExpendWayNo種擴展規則進行擴展,並且返回擴展后的狀態。

(本文所采用的算法描述語言為類Pascal。)

第一部分 基本搜索算法

一、回溯算法

回溯算法是所有搜索算法中最為基本的一種算法,其采用了一種“走不通就掉頭”思想作為其控制結構,其相當於采用了先根遍歷的方法來構造解答樹,可用於找解或所有解以及最優解。具體的算法描述如下:(略)

范例:一個M*M的棋盤上某一點上有一個馬,要求尋找一條從這一點出發不重復的跳完棋盤上所有的點的路線。

評價:回溯算法對空間的消耗較少,當其與分枝定界法一起使用時,對於所求解在解答樹中層次較深的問題有較好的效果。但應避免在后繼節點可能與前繼節點相同的問題中使用,以免產生循環。

二、深度搜索與廣度搜索

深度搜索與廣度搜索的控制結構和產生系統很相似,唯一的區別在於對擴展節點選取上。由於其保留了所有的前繼節點,所以在產生后繼節點時可以去掉一部分重復的節點,從而提高了搜索效率。這兩種算法每次都擴展一個節點的所有子節點,而不同的是,深度搜索下一次擴展的是本次擴展出來的子節點中的一個,而廣度搜索擴展的則是本次擴展的節點的兄弟節點。在具體實現上為了提高效率,所以采用了不同的數據結構.

三、初探隊與廣度優先搜索:

1、隊的定義:

隊是特殊的線性表之一,它只允許在隊的一端插入,在隊的另一端刪除。插入一端叫隊尾(T),刪除一端叫隊首(H),沒有任何元素的隊叫做空隊。隊列遵循"先進先出"原則,排隊購物、買票等,就是最常見的隊。

2、隊的基本操作:

(1)隊的描述:

  type queue=array[1..100] of integer;

var a:queue; {定義數組}

h,d:integer; {隊首、隊尾指針}

(2) 初始化(圖1):

procedure start;

begin

h:=1; d:=1;

end;

(3) 入隊操作(圖2):

procedure enter;

begin

read(s); {讀入數據}

inc(d); {隊尾加一}

a[d]:=s;

end;

(4) 出隊操作(圖3):

procedure out;

begin

inc(h); {隊首加一}

a[h]:=0;

end;

廣度優先搜索類似於樹的按層次遍歷的過程。它和隊有很多相似之處,運用了隊的許多思想,其實就是對隊的深入一步研究,它的基本操作和隊列幾乎一樣。

第二部分 搜索算法的優化

一、雙向廣度搜索

廣度搜索雖然可以得到最優解,但是其空間消耗增長太快。但如果從正反兩個方向進行廣度搜索,理想情況下可以減少二分之一的搜索量,從而提高搜索速度。

范例:移動一個只含字母A和B的字符串中的字母,給定初始狀態為(a)表,目標狀態為(b)表,給定移動規則為:只能互相對換相鄰字母。請找出一條移動最少步數的辦法。

AABBAA          BAAAAB

   (a)           (b)

問題分析:從初始狀態和目標狀態均按照廣度優先搜索擴展接點,當達到以下狀態時,出現相交點,如圖(c),接點序號表示生成順序。

雙向擴展結點:

利用雙向搜索對廣度搜索算法的改進:
1、添加一張節點表,作為反向擴展表。

2、在while循環體中在正向擴展代碼后加入反向擴展代碼,其擴展過程不能與正向過程共享一個for循環。

3、在正向擴展出一個節點后,需在反向表中查找是否有重合節點。反向擴展時與之相同。

對雙向廣度搜索算法的改進: 略微修改一下控制結構,每次while循環時只擴展正反兩個方向中節點數目較少的一個,可以使兩邊的發展速度保持一定的平衡,從而減少總擴展節點的個數,加快搜索速度。

二、分支定界

分支定界實際上是A*算法的一種雛形,其對於每個擴展出來的節點給出一個預期值,如果這個預期值不如當前已經搜索出來的結果好的話,則將這個節點(包括其子節點)從解答樹中刪去,從而達到加快搜索速度的目的。

范例:在一個商店中購物,設第I種商品的價格為Ci。但商店提供一種折扣,即給出一組商品的組合,如果一次性購買了這一組商品,則可以享受較優惠的價格。現在給出一張購買清單和商店所提供的折扣清單,要求利用這些折扣,使所付款最少。

問題分析:顯然,折扣使用的順序與最終結果無關,所以可以先將所有的折扣按折扣率從大到小排序,然后采用回溯法的控制結構,對每個折扣從其最大可能使用次數向零遞減搜索,設A為已打完折扣后優惠的價格,C為當前未打折扣的商品零售價之和,則其預期值為A+a*C,其中a為下一個折扣的折扣率。如當前已是最后一個折扣,則a=1。

對回溯算法的改進:

1、添加一個全局變量BestAnswer,記錄當前最優解。

2、在每次生成一個節點時,計算其預期值,並與BestAnswer比較。如果不好,則調用回溯過程。

三、A*算法

A*算法中更一般的引入了一個估價函數f,其定義為f=g+h。其中g為到達當前節點的耗費,而h表示對從當前節點到達目標節點的耗費的估計。其必須滿足兩個條件:

1、h必須小於等於實際的從當前節點到達目標節點的最小耗費h*。

2、f必須保持單調遞增。

A*算法的控制結構與廣度搜索的十分類似,只是每次擴展的都是當前待擴展節點中f值最小的一個,如果擴展出來的節點與已擴展的節點重復,則刪去這個節點。如果與待擴展節點重復,如果這個節點的估價函數值較小,則用其代替原待擴展節點,具體算法描述如下:

范例:一個3*3的棋盤中有1-8八個數字和一個空格,現給出一個初始態和一個目標態,要求利用這個空格,用最少的步數,使其到達目標態。

問題分析:預期值定義為h=|x-dx|+|y-dy|。

估價函數定義為f=g+h。

<Type>

Node(節點類型)=Record

Situtation:TSituation(當前節點狀態);

g:Integer;(到達當前狀態的耗費)

h:Integer;(預計的耗費)

f:Real;(估價函數值)

Last:Integer;(父節點)

End

<Var>

List(節點表):Array[1..Max(最多節點數)] of Node(節點類型);

open(總節點數):Integer;

close(待擴展節點編號):Integer;

New-S:Tsituation;(新節點)

<Init>

List<-0;

open<-1;

close<-0;

List[1].Situation<- 初始狀態;

<Main Program>

While (close<open(還有未擴展節點)) and

(open<Max(空間未用完)) and

(未找到目標節點) do

Begin

Begin
close:=close+1;

For I:=close+1 to open do (尋找估價函數值最小的節點)

Begin

if List[i].f<List[close].f then
Begin

交換List[i]和List[close];

End-If;

End-For;

End;

For I:=1 to TotalExpendMethod do(擴展一層子節點)

Begin

New-S:=ExpendNode(List[close].Situation,I)

If Not (New-S in List[1..close]) then

(擴展出的節點未與已擴展的節點重復)

Begin

If Not (New-S in List[close+1..open]) then

(擴展出的節點未與待擴展的節點重復)

Begin

open:=open+1;

List[open].Situation:=New-S;

List[open].Last:=close;

List[open].g:=List[close].g+cost;

List[open].h:=GetH(List[open].Situation);

List[open].f:=List[open].h+List[open].g;

End-If

Else Begin

If List[close].g+cost+GetH(New-S)<List[same].f then

(擴展出來的節點的估價函數值小於與其相同的節點)

Begin

List[same].Situation:= New-S;

List[same].Last:=close;

List[same].g:=List[close].g+cost;

List[same].h:=GetH(List[open].Situation);

List[same].f:=List[open].h+List[open].g;

End-If;

End-Else;

End-If

End-For;

End-While;

對A*算法的改進--分階段A*:

當A*算法出現數據溢出時,從待擴展節點中取出若干個估價函數值較小的節點,然后放棄其余的待擴展節點,從而可以使搜索進一步的進行下去。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM