搜索算法總結

搜索算法廣泛應用於人工智能領域,但是由於時間複雜度是指數級別,計算機科學家在樸素的搜索算法(廣度優先,深度優先搜索)上優化得到了一系列搜索算法

 

本文介紹的搜索算法主要指:廣度優先搜索,深度優先搜索,以及在此基礎上優化得來的

A*算法,分支限界算法。如有錯誤歡迎指正。

 

爲了便於描述,搜索算法適用解決在一張有權無向圖中,找到從原點到終點的最短路徑。

基礎搜索算法的通式是這樣的:

Wait_arr[]數組存放待擴展的節點。

初始化:把初始節點root加入到wait_arr[]數組中

While( wait_arr[]數組不爲空 ){

wait_arr[]數組中取出左邊第一個節點n

把取出節點擴展出鄰接子節點

For( 遍歷篩選每個鄰接子節點 ){

     If( 子節點沒有出現在path中 ),留下這個子節點,然後記錄這個子節點的父親是節點

}

If( 還存在子節點 )

     子節點加入到wait_arr[]中。

}

注:由於每個子節點都保留了它的父親節點,所以很容易逆推出到達該子節點的路徑path

深度優先與廣度優先搜索的區別與聯繫:

深度優先和廣度優先的區別,從上面的通式中可以解釋爲紅字部分的不同:

深度優先搜索:鄰接子節點加入wait_arr[]數組的最左邊,所以每次擴展的節點都是新的節點。

廣度優先搜索:鄰接子節點加入wait_arr[]數組的最右邊,所以每次擴展的節點都是老一輩的節點。

 

A*算法與樸素搜索算法的區別與聯繫:

A*算法實際上是模擬這麼一個過程,在廣場上,有一些遮擋物,人要從B點越過遮擋物到達出口E點,應該怎麼做呢?人每一次可以選擇向前,向後,向左,向右四種走法。但是人比較聰明,每一次選擇自己記憶中直線距離離E點最近的那個方向邁步,如果不行再回溯試其他的方向。這就是A*算法。

A*算法和之前的樸素的搜索算法有什麼區別和聯繫呢?

 

估價函數 

f(n) = h(n) + g(n);   其中f(n)是代表當前節點n的估計代價,g(n)是從源節點到當前節點的已知代價,h(n)是當前節點到目標節點的估計代價。

  • A*算法和樸素搜索算法的區別與聯繫之一依然在基礎搜索算法通式的紅字部分。每次把新的節點加入到wait_arr[]最左邊之後,a*算法會計算新的節點的估價函數,然後把wait_arr所有點根據估價函數進行排序,把最優的節點放在wait_arr的最左邊。這樣每次擴展的節點都是目前認爲最優的方向。
  • 另一個區別在於,當找到一個目標節點後,a*算法就退出。

 

A*算法的通式:

A*算法的通式 1

 

Wait_arr[]數組存放待擴展的節點。

初始化:把初始節點root加入到wait_arr[]數組中

While( wait_arr[]數組不爲空 ){

wait_arr[]數組中取出左邊第一個節點n

If( 是目標節點 )  break

把取出節點擴展出鄰接子節點

For( 遍歷篩選每個鄰接子節點 ){

    If( 子節點沒有出現在path中 ),留下這個子節點,然後記錄這個子節點的父親是節點

}

If( 還存在子節點 )

    子節點加入到wait_arr[]中。並計算估值函數f(n)

把所有的點按照估值函數排序。

}

 


A*算法通式 2

Open表 存放待擴展節點

Close表 存放訪問過的節點

初始化:把初始節點root加入open表中

While( open表不空 ){

open表中取出左邊第一個節點,加入到close表中

If( 是目標節點 輸出路徑,break

把取出節點擴展出鄰接子節點

for( 遍歷篩選每個鄰接子節點 ){

    子節點中記錄下父節點

    求出子節點估值函數f(n)

        If( 子節點不在open表中 也不在close表中 ){

            加入open

        }else if( 子節點在open表中 ){

            If( 新的估值函數比open表中原有子節點的更優 ){

               Open表中原來的子節點被替換

            }

        } else{ //close表中

            If( 新的估值函數比close表中原有子節點的更優 ){//可以保證不會出現迴路,不用判斷這個子節點是否曾經出現在path

                Close表中原來的子節點被刪除 

                Open表中加入新的子節點

             }

        }

}

open表按照估價函數從優到劣排序。

}

 

通式1和通式2都可以得到正確解

通式相較於 通式有什麼優點? 

1. 通式1如果只維護一條路徑(就是單純記錄擴展節點的父節點),在算法執行過程



紅色路徑是先前的較短路徑,擴展到a點後發現c點的估價函數更優,轉而擴展c得到c的子節點b (畢竟b不曾出現在藍色路徑上)。但實際上藍色路徑比最左邊紅色那一段路徑長,這就會使得起始點到b點的路徑被覆蓋爲藍色路徑(因爲b的父節點會被替換爲c)。  然而通式2只有在藍色路徑比最左邊一小段紅色路徑短的時候纔會將b的父節點替換爲c

 

進一步我們可以想見,對於廣度優先搜索,最優優先搜索都會面臨路徑被覆蓋的問題,而深度優先搜索則不會,所以如果要輸出所有路徑,優先使用深度優先搜索(如果想要優化,可以結合分支限界法剪枝)

 

 

A*算法的相關定理:

A*算法能否找出最優解,取決於估價函數f(n)中的h(n)部分。

假設:F(n) = H(n) + g(n)   H(n)是從當前節點到目標節點的實際路徑長(當然這是目前無法得知的)

如果總有H(n) >= h(n),那麼找到的路徑一定是最短路徑。

證明:假如最短路徑p的長度是s,那麼由於總有H(n) >= h(n),所以F(n) >=f(n)

假如,找到的路徑pp長度是ss > s,那麼還未搜索到的最短路徑的f(n) <= s < ss,不滿足A*算法每次擴展f(n)最小的節點的原則,矛盾。 

在滿足H(n) >= h(n)的情況下,h(n)越大,也就說明估值函數越精確,需要回溯的機會就越小,效率就越高。

 

相較於樸素的搜索算法,A*算法無法尋找出所有最優解。


A*算法例子,小人從左邊一點到右邊一點,要越過障礙物,估值函數中的h(n)是當前點到右邊那一點的歐幾里得距離,算法大概的路線是如下圖所示,紅色,藍色是走岔的路徑,黃色是最終修正得到的路徑。



分支限界法

 

分支限界法

A*算法有相通之處,都有估值函數,而且形式也是相同的:

f(n) = h(n) + g(n)

F_upper(n) = up_h(n) + g(n)

假如我們要找圖中最短的從原點到終點的路徑,那麼我們就需要維護一個最短路徑的上界(最短路徑最長可能是多少),估值函數是最優解的下界,一般這個上界初始化是由貪心算法得到的。

 

 

分支限界算法基本流程(要求輸出所有線路,從深度優先搜索中改編)

 

Wait_arr[]數組存放待擴展的節點。

用貪心算法算出最優解的上界upper

初始化:把初始節點root加入到wait_arr[]數組中

While( wait_arr[]數組不爲空 ){

    從wait_arr[]數組中取出左邊第一個節點n

    If( 是目標節點 ){ 

        輸出路徑

        Iff(n)<upper)修改upper

    } else{

        If( 當前節點f(n)>upper ){

            剪枝

        } else{

            If( F_upper(n) < upper ){ 更新upper }

            把取出節點擴展出可擴展的鄰接子節點

            For( 遍歷篩選每個鄰接子節點 ){

                If( 子節點沒有出現在path中 ){

                    留下這個子節點,然後記錄這個子節點的父親是節點n

                    計算該子節點的f(n)F_upper(n)

                    子節點加入到wait_arr[]左邊。

                }              

            }

        }

    }

}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章