A 尋路演算法的探尋與改良(一)

2022-08-27 04:03:09 字數 4667 閱讀 7172

by:田宇軒

1.1 前言

1.2 a*演算法要解決的問題

我們可以通過先了解a*演算法解決的問題來了解a*演算法的概念。a*是一種用於尋路的演算法,現在我們給計算機一張地圖,標記了哪些地方能走,哪些地方不能走,然後告訴計算機起點a和終點b,計算機自動計算出一條從a點到b點的最短路徑,我們的問題就解決了。a*演算法就是用於快速解決這類問題的方法。

讓我們先用抽象的方法看這個問題,首先,解決這類問題的前提條件有以下兩個:

第乙個條件是,地圖必須是事先設計好的(這裡重點內容會用黑體mark一下,後同)。也就是說地形、各點的位置等地圖的屬性是固定不變的,這種地圖不能前一秒裡面還有一條河,後一秒同樣位置就沒有這條河了。本著抽象的原則,我們把這種有固定屬性的地圖叫做靜態路網。因此,a*是一種基於靜態路網的尋徑演算法。

使用a*的第二個條件是,已知起點和終點。在一些rpg遊戲中,經常會有遊戲迷宮這種設定,在遊戲迷宮中,玩家只能確定自己的位置(仙劍3的一些迷宮裡,因為有傳送的概念,玩家甚至不能確定自己的位置),但是玩家並不知道迷宮的終點在**,這種情況下用a*演算法是不合適的。因此,a*演算法需要知道起點和終點才能求出最優路徑。

符合這兩個條件的問題,就是我們可以嘗試用a*演算法解決的問題。和你預料的一樣,a*演算法被廣泛應用在遊戲程式設計之中。

1.3 a*演算法的尋路思路

1.3.1 設定和定義地圖、地貌、起點和終點

了解了a*演算法的概念之後,我們來看看a*演算法是怎麼解決尋路這個問題的。

首先,我們給計算機一張地圖的同時,我們也要給計算機乙個處理地圖的方式。在a*中,處理地圖的思想就是讓計算機把一張大地圖看成是由大小相等的格仔拼出來的結構,這些格仔都不可再分割成更小的格仔,它們是地圖上的最小完整單位。我們事先把每個格仔的狀態在電腦中標記起來,比如這個格仔的地形不可通過,我們就打上一種標籤;如果這個格仔能通過,但是由於現實地形的原因(比如,這條路比較坎坷),這個格仔就被打上另一種標籤(比如:」難以通過的格仔」);如果這個格仔對應的地形一馬平川,這個格仔的標籤可以換成"可以通過的格仔"。

好的,讀到這裡,我們大概能知道地圖是怎麼在a*演算法中被處理的了,還是基於抽象的思想,我們可以得出重要的兩種結構:

1.地圖

地圖由有各種屬性的格仔組成。

2.格仔

格仔的基本屬性包括以下兩點:

(1).格仔在地圖中的位置,即座標。要保證現有的格仔足以描述一片完整的區域,因此這個座標應該是連續的而非離散的。

(2).格仔的「路況」,就是上面我們說的那些標籤,不同的標籤代表不同的格仔內路況,每次尋路如果格仔內相對路況更差,我們就會盡量繞開它,其實路況有很多種,這麼多種類用標籤分類,其實是不太靠譜的,這裡我們直接量化這些不同的格仔內路況,比如地形越難通過,相應地就設定乙個更大的值(比如,999),地形越容易通過,就設乙個更小的值。在a*演算法裡,我們把這種值叫做「權」,或者權值,它量化地直觀體現了每個格仔的路況。

在設定好地圖組成的基礎上,只要再設定好哪個格仔是起點,哪個格仔是終點,我們就可以考慮怎麼找一條最優路徑了。

1.3.2 bfs(breadth first search,廣度優先搜尋)和dfs(depth-first-search,深度優先搜尋演算法

在思考怎麼尋找最優路徑之前,應該先定義什麼叫做最優路徑,最優路徑不只是a與b的距離最短,還要求我們權衡經過的路徑權值盡量的低,權值越低的路徑就意味著越適合的路線。這裡,我們把起點到終點經過格仔用的長度和一路格仔的權值之和一起考慮,我們設這種衡量路線是否最優的標準叫「代價」,計算最優路徑,其實就是計算代價最小的路徑。因此,a*演算法的重點就是如何評估代價。

,不是好的演算法。這有點像網路安全中的暴力破解,乙個密碼鎖有三位數,000~999,挨個數嘗試解鎖最多用999次不斷嘗試,就能知道正確密碼,但這種方法顯然不如社工密碼鎖主人或者用生日什麼的做線索數字去試高效。

圖1.3.2.1—bfs搜尋終點

圖1.3.2.2—dfs搜尋過程

可以清楚地對比出來,比起bfs,dfs因為盡量靠近終點的原則,其實是用終點相對與當前點的方向為導向,所以有乙個大致的方向,就不用盲目地去找了,這樣,就能比bfs能快地找出來最短路徑,但是這種快速尋找預設起點a終點b之間沒有任何障礙物,地形的權值也都差不多。如果起點終點之間有障礙物,那麼dfs就會出現繞彎的情況。見圖1.3.2.4:

圖1.3.2.4—dfs一直向右下角搜尋,但由於被擋住去路,因只能繞了乙個大圈

圖中dfs演算法使電腦一路往更右下方的區域探索,可以看出,在dfs遇到障礙物時,其實沒有辦法找到一條最優的路徑,只能保證dfs會提供其中的一條路徑()如果有的話。

大概了解了bfs和dfs,對比這兩者可以看出來,bfs保證的是從起點到達路線上的任意點花費的代價最小(但是不考慮這個過程是否要搜尋很多格仔)dfs保證的是通過不斷矯正行走方向和終點的方向的關係,使發現終點要搜尋的格仔更少(但是不考慮這個過程是否繞遠)。

a*演算法的設計同時融合了bfs和dfs的優勢,既考慮到了從起點通過當前路線的代價(保證了不會繞路),又不斷的計算當前路線方向是否更趨近終點的方向(保證了不會搜尋很多圖塊),是一種靜態路網中最有效的直接搜尋演算法(會在稍後的文章中提到不直接搜尋地圖的尋路方式)。

1.3.3 a*演算法的估價思想

計算機上的事物,一定是先要量化,然後再計算的,這是一種抽象的過程。在bfs中,越接近起點的格仔的代價越低,越遠離起點的格仔的代價越高。每次搜尋,都是在尚未搜尋的範圍中尋找他們之間更接近起點的格仔來進行搜尋;在dfs中,越接近終點的格仔代價越低,離終點越遠的格仔的代價越高。如果要融合這兩種方法,我們應該怎麼對每個格仔估值呢?

在a*中,每個格仔的估值(估值了這個格仔代價)是由兩部分組成的,一部分是這個格仔距離起點的距離,我們記作g,另一部分是這個格仔距離終點的距離,這種距離,我們記作h。注意,這裡的距離不是指直線距離,g指的是從這個格仔的起點到達這個格仔一共花費的距離,h指的是估算一下從這個格仔到終點將會用多少距離,我們可以看出來,我們事先並不知道這個格仔到終點的花費,因此,我們必須找一種方法估算當前點與終點的距離,這種估算的方法,我們稱為啟發式演算法,a*的估價思想就是用量化g值和f值(其實就是求出每個要搜尋格仔的這兩個值),然後加在一起,它們的和就是這個格仔的估值(或者叫做代價),這裡我們把他們的和記作f。很好,我們現在就可以得出a*演算法中計算格仔代價的公式:f=g+h.f,g,h也都是格仔的屬性,讓我們把這三個引數新增到格仔的性質中去。

通過這個公式,求出搜尋的每個格仔的權值,然後每次都選擇代價更小的格仔,這樣,我們就能得出一條最佳路徑。這就是a*演算法的估價思想。

1.4 a*演算法的描述

以下是對a*演算法的完整概括:

1,找到起點對應的格仔,並把起始格新增到開啟列表裡。開啟列表是乙個用於記載我們待處理格仔的列表。(藍色用於寫注釋,下同)

2,重複如下的工作:

a) 尋找開啟列表中f值最低的格仔。我們稱它為當前格

b) 把當前格從開啟列表中刪除,放到關閉列表裡面。

關閉列表是乙個用於記載我們已經處理過的處理格仔的列表。

c) 對當前格的每乙個相鄰格仔,分三種情況進行操作:

* 如果它不可通過或者已經在關閉列表中,略過它。反之如下。

* 如果它不在開啟列表中,把它新增進去。把當前格作為它的父節點。記錄這一格的f,g,和h值。父節點用於記載當前格仔是哪個格仔的相鄰格。

* 如果它已經在開啟列表中,就說明我們之前已經訪問過了這個格仔,現在我們又遇到了它,說明可以通過這個格仔的父節點直接到達當前格,這種情況下進一步說明至少有兩種方式到達這個格仔,因此我們必須知道哪種方式到達這一格更好,才能進行下一步操作。

為了知道哪種方式更好,我們假設當前點是這個格仔的父節點,然後計算這個節點的g值,然後比較我們剛計算出來的g值是否比原來的g值(因為這個格仔如果在開啟列表,就說明已經有了父節點和求好的g值),更低的g值意味著更好的路徑。如果是這樣,就把這一格的父節點改成當前格,並且重新計算這一格的g和f值。

d) 停止,當你

* 把目標格新增進了關閉列表,這時候路徑被找到,或者

* 沒有找到目標格,開啟列表已經空了。這時候,路徑不存在。

你可以點選這裡

檢視第二章的內容。

尋路演算法 A (一)

說到 a 演算法相信都不陌生。做遊戲的人都多多少少的接觸過,而且網上教程也是一大堆。比如非常詳細的 我開始學習 a 也是看的這個 以下是我個人對 a 的理解。首先要明白 a 是基於地圖格仔 可以是方形,三角形,六邊形等 每乙個格仔也是乙個節點 尋路的。a 中最重要的一點是要明白 a 是啟發式的。a ...

關於尋路演算法的一些思考(9) 尋路者的移動成本

當使用尋路演算法的時候,你可能想把地圖空間當做一種不單是用通暢或阻礙來表達的東西。通常地圖空間會有更多的已知資訊,比如通過那片區域的移動難度。比如,沼澤和高山可能比草地和沙漠更難通過。使用演算法,比如a 你可以把給資訊編碼代入成本函式中。下面列出了一些計算移動成本的想法,也許會派上用場。高海拔 比如...

學習筆記 迷宮生成與尋路演算法

2016年5月12日 更新 本來一直想寫乙個遊戲,但是自己的功力又不夠,正好在 資料結構 一書中看到了棧應用之迷宮尋路演算法,所以打算寫乙個自動生成隨機迷宮並可以自動解迷宮的程式。我本來打算用c寫的,但是寫起來卻不太順手,一方面是我對c的語法不太了解,另一方面是c在好多地方反而不夠靈活。比如,當函式...