日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

A*算法介绍

發布時間:2025/4/14 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 A*算法介绍 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1?導言

  移動一個簡單的物體(object)看起來是容易的。而路徑搜索是復雜的。為什么涉及到路徑搜索就產生麻煩了?考慮以下情況:

?

?

  物體(unit)最初位于地圖的底端并且嘗試向頂部移動。物體掃描的區域中(粉紅色部分)沒有任何東西顯示它不能向上移動,因此它持續向上移動。在靠近頂部時,它探測到一個障礙物然后改變移動方向。然后它沿著U形障礙物找到它的紅色的路徑。相反的,一個路徑搜索器(pathfinder)將會掃描一個更大的區域(淡藍色部分),但是它能做到不讓物體(unit)走向凹形障礙物而找到一條更短的路徑(藍色路徑)。

  然而你可以擴展一個運動算法,用于對付上圖所示的障礙物。或者避免制造凹形障礙,或者把凹形出口標識為危險的(只有當目的地在里面時才進去):

?

?

  比起一直等到最后一刻才發現問題,路徑搜索器讓你提前作出計劃。不帶路徑搜索的運動(movement)可以在很多種情形下工作,同時可以擴展到更多的情形,但是路徑搜索是一種更常用的解決更多問題的方法。

1.1?算法

  計算機科學教材中的路徑搜索算法在數學視角的圖上工作——由邊聯結起來的結點的集合。一個基于圖塊(tile)拼接的游戲地圖可以看成是一個圖,每個圖塊(tile)是一個結點,并在每個圖塊之間畫一條邊:

?

?

  目前,我會假設我們使用二維網格(grid)。稍后我將討論如何在你的游戲之外建立其他類型的圖。

  許多AI領域或算法研究領域中的路徑搜索算法是基于任意(arbitrary)的圖設計的,而不是基于網格(grid-based)的圖。我們可以找到一些能使用網格地圖的特性的東西。有一些我們認為是常識,而算法并不理解。例如,我們知道一些和方向有關的東西:一般而言,如果兩個物體距離越遠,那么把其中一個物體向另一個移動將花越多的時間;并且我們知道地圖中沒有任何秘密通道可以從一個地點通向另一個地點。(我假設沒有,如果有的話,將會很難找到一條好的路徑,因為你并不知道要從何處開始。)

1.2?Dijkstra算法與最佳優先搜索

  Dijkstra算法從物體所在的初始點開始,訪問圖中的結點。它迭代檢查待檢查結點集中的結點,并把和該結點最靠近的尚未檢查的結點加入待檢查結點集。該結點集從初始結點向外擴展,直到到達目標結點。Dijkstra算法保證能找到一條從初始點到目標點的最短路徑,只要所有的邊都有一個非負的代價值。(我說“最短路徑”是因為經常會出現許多差不多短的路徑。)在下圖中,粉紅色的結點是初始結點,藍色的是目標點,而類菱形的有色區域(注:原文是teal areas)則是Dijkstra算法掃描過的區域。顏色最淡的區域是那些離初始點最遠的,因而形成探測過程(exploration)的邊境(frontier):

?

?

  最佳優先搜索(BFS)算法按照類似的流程運行,不同的是它能夠評估(稱為啟發式的)任意結點到目標點的代價。與選擇離初始結點最近的結點不同的是,它選擇離目標最近的結點。BFS不能保證找到一條最短路徑。然而,它比Dijkstra算法快的多,因為它用了一個啟發式函數(heuristic function)快速地導向目標結點。例如,如果目標位于出發點的南方,BFS將趨向于導向南方的路徑。在下面的圖中,越黃的結點代表越高的啟發式值(移動到目標的代價高),而越黑的結點代表越低的啟發式值(移動到目標的代價低)。這表明了與Dijkstra?算法相比,BFS運行得更快。

?

?

  然而,這兩個例子都僅僅是最簡單的情況——地圖中沒有障礙物,最短路徑是直線的。現在我們來考慮前邊描述的凹型障礙物。Dijkstra算法運行得較慢,但確實能保證找到一條最短路徑:

?

?

  另一方面,BFS運行得較快,但是它找到的路徑明顯不是一條好的路徑:

?

?

  問題在于BFS是基于貪心策略的,它試圖向目標移動盡管這不是正確的路徑。由于它僅僅考慮到達目標的代價,而忽略了當前已花費的代價,于是盡管路徑變得很長,它仍然繼續走下去。

  結合兩者的優點不是更好嗎?1968年發明的A*算法就是把啟發式方法(heuristic approaches)如BFS,和常規方法如Dijsktra算法結合在一起的算法。有點不同的是,類似BFS的啟發式方法經常給出一個近似解而不是保證最佳解。然而,盡管A*基于無法保證最佳解的啟發式方法,A*卻能保證找到一條最短路徑。

1.3?A*算法

  我將集中討論A*算法。A*是路徑搜索中最受歡迎的選擇,因為它相當靈活,并且能用于多種多樣的情形之中。

  和其它的圖搜索算法一樣,A*潛在地搜索圖中一個很大的區域。和Dijkstra一樣,A*能用于搜索最短路徑。和BFS一樣,A*能用啟發式函數(注:原文為heuristic)引導它自己。在簡單的情況中,它和BFS一樣快。

?

?

  在凹型障礙物的例子中,A*找到一條和Dijkstra算法一樣好的路徑:

?

?

  成功的秘決在于,它把Dijkstra算法(靠近初始點的結點)和BFS算法(靠近目標點的結點)的信息塊結合起來。在討論A*的標準術語中,g(n)表示從初始結點到任意結點n的代價,h(n)表示從結點n到目標點的啟發式評估代價(heuristic estimated cost)。在上圖中,yellow(h)表示遠離目標的結點而teal(g)表示遠離初始點的結點。當從初始點向目標點移動時,A*權衡這兩者。每次進行主循環時,它檢查f(n)最小的結點n,其中f(n) = g(n) + h(n)。

2?啟發式算法

  啟發式函數h(n)告訴A*從任意結點n到目標結點的最小代價評估值。選擇一個好的啟發式函數是重要的。

2.1?A*對啟發式函數的使用

  啟發式函數可以控制A*的行為:

  • 一種極端情況,如果h(n)是0,則只有g(n)起作用,此時A*演變成Dijkstra算法,這保證能找到最短路徑。
  • 如果h(n)經常都比從n移動到目標的實際代價小(或者相等),則A*保證能找到一條最短路徑。h(n)越小,A*擴展的結點越多,運行就得越慢。
  • 如果h(n)精確地等于從n移動到目標的代價,則A*將會僅僅尋找最佳路徑而不擴展別的任何結點,這會運行得非常快。盡管這不可能在所有情況下發生,你仍可以在一些特殊情況下讓它們精確地相等(譯者:指讓h(n)精確地等于實際值)。只要提供完美的信息,A*會運行得很完美,認識這一點很好。
  • 如果h(n)有時比從n移動到目標的實際代價高,則A*不能保證找到一條最短路徑,但它運行得更快。
  • 另一種極端情況,如果h(n)比g(n)大很多,則只有h(n)起作用,A*演變成BFS算法。

  所以我們得到一個很有趣的情況,那就是我們可以決定我們想要從A*中獲得什么。理想情況下(注:原文為At exactly the right point),我們想最快地得到最短路徑。如果我們的目標太低,我們仍會得到最短路徑,不過速度變慢了;如果我們的目標太高,那我們就放棄了最短路徑,但A*運行得更快。

在游戲中,A*的這個特性非常有用。例如,你會發現在某些情況下,你希望得到一條好的路徑("good" path)而不是一條完美的路徑("perfect" path)。為了權衡g(n)和h(n),你可以修改任意一個。

:在學術上,如果啟發式函數值是對實際代價的低估,A*算法被稱為簡單的A算法(原文為simply A)。然而,我繼續稱之為A*,因為在實現上是一樣的,并且在游戲編程領域并不區別A和A*。

2.2?速度還是精確度?

  A*改變它自己行為的能力基于啟發式代價函數,啟發式函數在游戲中非常有用。在速度和精確度之間取得折衷將會讓你的游戲運行得更快。在很多游戲中,你并不真正需要得到最好的路徑,僅需要近似的就足夠了。而你需要什么則取決于游戲中發生著什么,或者運行游戲的機器有多快。

  假設你的游戲有兩種地形,平原和山地,在平原中的移動代價是1而在山地則是3。A* is going to search three times as far along flat land as it does along mountainous land.?這是因為有可能有一條沿著平原到山地的路徑。把兩個鄰接點之間的評估距離設為1.5可以加速A*的搜索過程。然后A*會將3和1.5比較,這并不比把3和1比較差。It is not as dissatisfied with mountainous terrain, so it won't spend as much time trying to find a way around it. Alternatively, you can speed up up A*'s search by decreasing the amount it searches for paths around mountains―just tell A* that the movement cost on mountains is 2 instead of 3. Now it will search only twice as far along the flat terrain as along mountainous terrain. Either approach gives up ideal paths to get something quicker.

速度和精確度之間的選擇前不是靜態的。你可以基于CPU的速度、用于路徑搜索的時間片數、地圖上物體(units)的數量、物體的重要性、組(group)的大小、難度或者其他任何因素來進行動態的選擇。取得動態的折衷的一個方法是,建立一個啟發式函數用于假定通過一個網格空間的最小代價是1,然后建立一個代價函數(cost function)用于測量(scales):

g’(n) = 1 + alpha * ( g(n) – 1?)

  如果alpha是0,則改進后的代價函數的值總是1。這種情況下,地形代價被完全忽略,A*工作變成簡單地判斷一個網格可否通過。如果alpha是1,則最初的代價函數將起作用,然后你得到了A*的所有優點。你可以設置alpha的值為0到1的任意值。

  你也可以考慮對啟發式函數的返回值做選擇:絕對最小代價或者期望最小代價。例如,如果你的地圖大部分地形是代價為2的草地,其它一些地方是代價為1的道路,那么你可以考慮讓啟發式函數不考慮道路,而只返回2*距離。

  速度和精確度之間的選擇并不是全局的。在地圖上的某些區域,精確度是重要的,你可以基于此進行動態選擇。例如,假設我們可能在某點停止重新計算路徑或者改變方向,則在接近當前位置的地方,選擇一條好的路徑則是更重要的,因此為何要對后續路徑的精確度感到厭煩?或者,對于在地圖上的一個安全區域,最短路徑也許并不十分重要,但是當從一個敵人的村莊逃跑時,安全和速度是最重要的。(譯者注:譯者認為這里指的是,在安全區域,可以考慮不尋找精確的最短路徑而取近似路徑,因此尋路快;但在危險區域,逃跑的安全性和逃跑速度是重要的,即路徑的精確度是重要的,因此可以多花點時間用于尋找精確路徑。)

2.3?衡量單位

 A*計算f(n) = g(n) + h(n)。為了對這兩個值進行相加,這兩個值必須使用相同的衡量單位。如果g(n)用小時來衡量而h(n)用米來衡量,那么A*將會認為g或者h太大或者太小,因而你將不能得到正確的路徑,同時你的A*算法將運行得更慢。

2.4?精確的啟發式函數

  如果你的啟發式函數精確地等于實際最佳路徑(optimal path),如下一部分的圖中所示,你會看到此時A*擴展的結點將非常少。A*算法內部發生的事情是:在每一結點它都計算f(n) = g(n) + h(n)。當h(n)精確地和g(n)匹配(譯者注:原文為match)時,f(n)的值在沿著該路徑時將不會改變。不在正確路徑(right path)上的所有結點的f值均大于正確路徑上的f值(譯者注:正確路徑在這里應該是指最短路徑)。如果已經有較低f值的結點,A*將不考慮f值較高的結點,因此它肯定不會偏離最短路徑。

2.4.1?預計算的精確啟發式函數

  構造精確啟發函數的一種方法是預先計算任意一對結點之間最短路徑的長度。在許多游戲的地圖中這并不可行。然后,有幾種方法可以近似模擬這種啟發函數:

  • Fit a coarse grid on top of the fine grid. Precompute the shortest path between any pair of coarse grid locations.
  • Precompute the shortest path between any pair of?waypoints. This is a generalization of the coarse grid approach.

  (譯者:此處不好翻譯,暫時保留原文)

然后添加一個啟發函數h’用于評估從任意位置到達鄰近導航點(waypoints)的代價。(如果愿意,后者也可以通過預計算得到。)最終的啟發式函數可以是:

h(n) = h'(n, w1) + distance(w1, w2), h'(w2, goal)

或者如果你希望一個更好但是更昂貴的啟發式函數,則分別用靠近結點和目標的所有的w1,w2對對上式進行求值。(譯者注:原文為or if you want a better but more expensive heuristic, evaluate the above with all pairs w1, w2 that are close to the node and the goal, respectively.)

2.4.2?線性精確啟發式算法

  在特殊情況下,你可以不通過預計算而讓啟發式函數很精確。如果你有一個不存在障礙物和slow地形,那么從初始點到目標的最短路徑應該是一條直線。

  如果你正使用簡單的啟發式函數(我們不知道地圖上的障礙物),則它應該和精確的啟發式函數相符合(譯者注:原文為match)。如果不是這樣,則你會遇到衡量單位的問題,或者你所選擇的啟發函數類型的問題。

2.5?網格地圖中的啟發式算法

  在網格地圖中,有一些眾所周知的啟發式函數。

2.5.1?曼哈頓距離

標準的啟發式函數是曼哈頓距離(Manhattan distance)。考慮你的代價函數并找到從一個位置移動到鄰近位置的最小代價D。因此,我的游戲中的啟發式函數應該是曼哈頓距離的D倍:

???????H(n) = D * (abs ( n.x – goal.x ) + abs ( n.y – goal.y ) )

你應該使用符合你的代價函數的衡量單位。

?

?

(Note: the above image has a?tie-breaker?added to the heuristic.}

(譯者注:曼哈頓距離——兩點在南北方向上的距離加上在東西方向上的距離,即D(I,J)=|XI-XJ|+|YI-YJ|。對于一個具有正南正北、正東正西方向規則布局的城鎮街道,從一點到達另一點的距離正是在南北方向上旅行的距離加上在東西方向上旅行的距離因此曼哈頓距離又稱為出租車距離,曼哈頓距離不是距離不變量,當坐標軸變動時,點間的距離就會不同——百度知道)

2.5.2?對角線距離

如果在你的地圖中你允許對角運動那么你需要一個不同的啟發函數。(4 east, 4 north)的曼哈頓距離將變成8*D。然而,你可以簡單地移動(4 northeast)代替,所以啟發函數應該是4*D。這個函數使用對角線,假設直線和對角線的代價都是D:

h(n) = D * max(abs(n.x - goal.x), abs(n.y - goal.y))

?

?

如果對角線運動的代價不是D,但類似于D2 = sqrt(2) * D,則上面的啟發函數不準確。你需要一些更準確(原文為sophisticated)的東西:

h_diagonal(n) = min(abs(n.x - goal.x), abs(n.y - goal.y))

h_straight(n) = (abs(n.x - goal.x) + abs(n.y - goal.y))

h(n) = D2 * h_diagonal(n) + D * (h_straight(n) - 2*h_diagonal(n)))

這里,我們計算h_diagonal(n):沿著斜線可以移動的步數;h_straight(n):曼哈頓距離;然后合并這兩項,讓所有的斜線步都乘以D2,剩下的所有直線步(注意這里是曼哈頓距離的步數減去2倍的斜線步數)都乘以D。

2.5.3?歐幾里得距離

如果你的單位可以沿著任意角度移動(而不是網格方向),那么你也許應該使用直線距離:

h(n) = D * sqrt((n.x-goal.x)^2 + (n.y-goal.y)^2)

然而,如果是這樣的話,直接使用A*時將會遇到麻煩,因為代價函數g不會match啟發函數h。因為歐幾里得距離比曼哈頓距離和對角線距離都短,你仍可以得到最短路徑,不過A*將運行得更久一些:

?

?

2.5.4?平方后的歐幾里得距離

我曾經看到一些A*的網頁,其中提到讓你通過使用距離的平方而避免歐幾里得距離中昂貴的平方根運算:

h(n) = D * ((n.x-goal.x)^2 + (n.y-goal.y)^2)

不要這樣做!這明顯地導致衡量單位的問題。當A*計算f(n) = g(n) + h(n),距離的平方將比g的代價大很多,并且你會因為啟發式函數評估值過高而停止。對于更長的距離,這樣做會靠近g(n)的極端情況而不再計算任何東西,A*退化成BFS:

?

?

2.5.5 Breaking ties?Breaking ties

導致低性能的一個原因來自于啟發函數的ties(注:這個詞實在不知道應該翻譯為什么)。當某些路徑具有相同的f值的時候,它們都會被搜索(explored),盡管我們只需要搜索其中的一條:


Ties in f values.

為了解決這個問題,我們可以為啟發函數添加一個附加值(譯者注:原文為small tie breaker)。附加值對于結點必須是確定性的(也就是說,不能是隨機的數),而且它必須讓f值體現區別。因為A*對f值排序,讓f值不同意味著只有一個"equivalent"的f值會被檢測。

一種添加附加值的方式是稍微改變(譯者注:原文為nudge)h的衡量單位。如果我們減少衡量單位(譯者注:原文為scale it downwards),那么當我們朝著目標移動的時候f將逐漸增加。很不幸,這意味著A*傾向于擴展到靠近初始點的結點,而不是靠近目標的結點。我們可以增加衡量單位(譯者注:原文為scale it downwards scale h upwards slightly)(甚至是0.1%),A*就會傾向于擴展到靠近目標的結點。

heuristic?*= (1.0 + p)

選擇因子p使得p <?移動一步(step)的最小代價?/?期望的最長路徑長度。假設你不希望你的路徑超過1000步(step),你可以使p = 1 / 1000。添加這個附加值的結果是,A*比以前搜索的結點更少了。


Tie-breaking scaling added to heuristic.

當存在障礙物時,當然仍要在它們周圍尋找路徑,但要意識到,當繞過障礙物以后,A*搜索的區域非常少:


Tie-breaking scaling added to heuristic, works nicely with obstacles.

Steven van Dijk建議,一個更直截了當的方法是把h傳遞到比較函數(comparison function)。當f值相等時,比較函數檢查h,然后添加附加值。

一個不同的添加附加值的方法是,傾向于從初始點到目標點的連線(直線):

dx1 = current.x - goal.x

dy1 = current.y - goal.y

dx2 = start.x - goal.x

dy2 = start.y - goal.y

cross?= abs(dx1*dy2 - dx2*dy1)

heuristic?+= cross*0.001

這段代碼計算初始-目標向量(start to goal vector)和當前-目標向量(current point to goal vector)的向量叉積(vector cross-product)。When these vectors don't line up, the cross product will be larger.結果是,這段代碼選擇的路徑稍微傾向于從初始點到目標點的直線。當沒有障礙物時,A*不僅搜索很少的區域,而且它找到的路徑看起來非常棒:


Tie-breaking cross-product added to heuristic, produces pretty paths.

然而,因為這種附加值傾向于從初始點到目標點的直線路徑,當出現障礙物時將會出現奇怪的結果(注意這條路徑仍是最佳的,只是看起來很奇怪):


Tie-breaking cross-product added to heuristic, less pretty with obstacles.

為了交互地研究這種附加值方法的改進,請參考James Macgill的A*確applet(http://www.ccg.leeds.ac.uk/james/aStar/?)[如果鏈接無效,請使用這個鏡像(http://www.vision.ee.ethz.ch/~buc/astar/AStar.html)](譯者注:兩個鏈接均無效)。使用“Clear”以清除地圖,選擇地圖對角的兩個點。當你使用“Classic A*”方法,你會看到附加值的效果。當你使用“Fudge”方法,你會看到上面給啟發函數添加叉積后的效果。

然而另一種添加附加值的方法是,小心地構造你的A*優先隊列,使新插入的具有特殊f值的結點總是比那些以前插入的具有相同f值的舊結點要好一些。

你也許也想看看能夠更靈活地(譯者注:原文為sophisticated)添加附加值的AlphA*算法(http://home1.stofanet.dk/breese/papers.html),不過用這種算法得到的路徑是否能達到最佳仍在研究中。AlphA*具有較好的適應性,而且可能比我在上面討論的附加值方法運行得都要好。然而,我所討論的附加值方法非常容易實現,所以從它們開始吧,如果你需要得到更好的效果,再去嘗試AlphA*。

2.5.6?區域搜索

  如果你想搜索鄰近目標的任意不確定結點,而不是某個特定的結點,你應該建立一個啟發函數h’(x),使得h’(x)為h1(x), h2(x), h3(x)。。。的最小值,而這些h1, h2, h3是鄰近結點的啟發函數。然而,一種更快的方法是讓A*僅搜索目標區域的中心。一旦你從OPEN集合中取得任意一個鄰近目標的結點,你就可以停止搜索并建立一條路徑了。

3 Implementation notes

3.1?概略

  如果不考慮具體實現代碼,A*算法是相當簡單的。有兩個集合,OPEN集和CLOSED集。其中OPEN集保存待考查的結點。開始時,OPEN集只包含一個元素:初始結點。CLOSED集保存已考查過的結點。開始時,CLOSED集是空的。如果繪成圖,OPEN集就是被訪問區域的邊境(frontier)而CLOSED集則是被訪問區域的內部(interior)。每個結點同時保存其父結點的指針因此我們可以知道它是如何被找到的。

  在主循環中重復地從OPEN集中取出最好的結點n(f值最小的結點)并檢查之。如果n是目標結點,則我們的任務完成了。否則,結點n被從OPEN集中刪除并加入CLOSED集。然后檢查它的鄰居n’。如果鄰居n’在CLOSED集中,那么它是已經被檢查過的,所以我們不需要考慮它*;如果n’在OPEN集中,那么它是以后肯定會被檢查的,所以我們現在不考慮它*。否則,把它加入OPEN集,把它的父結點設為n。到達n’的路徑的代價g(n’),設定為g(n) + movementcost(n, n’)。

(*)這里我忽略了一個小細節。你確實需要檢查結點的g值是否更小了,如果是的話,需要重新打開(re-open)它。

OPEN = priority queue containing START

CLOSED = empty set

while?lowest rank in OPEN is not the GOAL:

??current?= remove lowest rank item from OPEN

??add?current to CLOSED

??for?neighbors of current:

????cost?= g(current) + movementcost(current, neighbor)

????if?neighbor in OPEN and cost less than g(neighbor):

??????remove?neighbor from OPEN, because new path is better

????if?neighbor in CLOSED and cost less than g(neighbor): **

??????remove?neighbor from CLOSED

????if?neighbor not in OPEN and neighbor not in CLOSED:

??????set?g(neighbor) to cost

??????add?neighbor to OPEN

??????set?priority queue rank to g(neighbor) + h(neighbor)

??????set?neighbor's parent to current

?

reconstruct?reverse path from goal to start

by?following parent pointers

(**) This should never happen if you have an admissible heuristic. However in games we often have inadmissible heuristics.

3.2?源代碼

我自己的(舊的)C++A*代碼是可用的:path.cpp (http://theory.stanford.edu/~amitp/ GameProgramming/path.cpp)和path.h (http://theory.stanford.edu/~amitp/GameProgramming/ path.h),但是不容易閱讀。還有一份更老的代碼(更慢的,但是更容易理解),和很多其它的A*實現一樣,它在Steve Woodcock'的游戲AI頁面(http://www.gameai.com/ai.html)。

在網上,你能找到C,C++,Visual Basic?,Java(http://www.cuspy.com/software/pathfinder/ doc/),Flash/Director/Lingo,?C#(http://www.codeproject.com/csharp/CSharpPathfind.asp), Delphi, Lisp, Python, Perl,?和Prolog?實現的A*代碼。一定的閱讀Justin Heyes-Jones的C++實現(http://www.geocities.com/jheyesjones/astar.html)。

3.3?集合的表示

你首先想到的用于實現OPEN集和CLOSED集的數據結構是什么?如果你和我一樣,你可能想到“數組”。你也可能想到“鏈表”。我們可以使用很多種不同的數據結構,為了選擇一種,我們應該考慮我們需要什么樣的操作。

在OPEN集上我們主要有三種操作:主循環重復選擇最好的結點并刪除它;訪問鄰居結點時需要檢查它是否在集合里面;訪問鄰居結點時需要插入新結點。插入和刪除最佳是優先隊列(http://members.xoom.com/killough/heaps.html)的典型操作。

選擇哪種數據結構不僅取決于操作,還取決于每種操作執行的次數。檢查一個結點是否在集合中這一操作對每個被訪問的結點的每個鄰居結點都執行一次。刪除最佳操作對每個被訪問的結點都執行一次。被考慮到的絕大多數結點都會被訪問;不被訪問的是搜索空間邊緣(fringe)的結點。當評估數據結構上面的這些操作時,必須考慮fringe(F)的最大值。

另外,還有第四種操作,雖然執行的次數相對很少,但還是必須實現的。如果正被檢查的結點已經在OPEN集中(這經常發生),并且如果它的f值比已經在OPEN集中的結點要好(這很少見),那么OPEN集中的值必須被調整。調整操作包括刪除結點(f值不是最佳的結點)和重插入。這兩個步驟必須被最優化為一個步驟,這個步驟將移動結點。

3.3.1?未排序數組或鏈表

最簡單的數據結構是未排序數組或鏈表。集合關系檢查操作(Membership test)很慢,掃描整個結構花費O(F)。插入操作很快,添加到末尾花費O(1)。查找最佳元素(Finding the best element)很慢,掃描整個結構花費O(F)。對于數組,刪除最佳元素(Removing the best element)花費O(F),而鏈表則是O(1)。調整操作中,查找結點花費O(F),改變值花費O(1)。

3.3.2?排序數組

為了加快刪除最掛操作,可以對數組進行排序。集合關系檢查操作將變成O(log F),因為我們可以使用折半查找。插入操作會很慢,為了給新元素騰出空間,需要花費?O(F)以移動所有的元素。查找最佳元素操作會很快,因為它已經在末尾了所以花費是O(1)。如果我們保證最佳排序至數組的尾部(best sorts to the?end?of the array),刪除最佳元素操作花費將是O(1)。調整操作中,查找結點花費O(logF),改變值/位置花費O(F)。

3.3.3?排序鏈表

在排序數組中,插入操作很慢。如果使用鏈表則可以加速該操作。集合關系檢查操作很慢,需要花費O(F)用于掃描鏈表。插入操作是很快的,插入新元素只花費O(1)時間,但是查找正確位置需要花費O(F)。查找最佳元素很快,花費O(1)時間,因為最佳元素已經在表的尾部。刪除最佳元素也是O(1)。調整操作中,查找結點花費O(F),改變值/位置花費O(1)。

3.3.4?排序跳表

在未排序鏈表中查找元素是很慢的。如果用跳表(http://en.wikipedia.org/wiki/Skip_list)代替鏈表的話,可以加速這個操作。在跳表中,如果有排序鍵(sort key)的話,集合關系檢查操作會很快:O(log F)。如果你知道在何處插入的話,和鏈表一樣,插入操作也是O(1)。如果排序鍵是f,查找最佳元素很快,達到O(1),刪除一個元素也是O(1)。調整操作涉及到查找結點,刪除結點和重插入。

如果我們用地圖位置作為跳表的排序鍵,集合關系檢查操作將是O(log F)。在完成集合關系檢查后,插入操作是O(1)。查找最佳元素是O(F),刪除一個結點是O(1)。因為集合關系檢查更快,所以它比未排序鏈表要好一些。

如果我們用f值作為跳表的排序鍵,集合關系檢查操作將是O(F)。插入操作是O(1)。查找最佳元素是O(1),刪除一個結點是O(1)。這并不比排序鏈表好。

3.3.5?索引數組

如果結點的集合有限并且數目是適當的,我們可以使用直接索引結構,索引函數i(n)把結點n映射到一個數組的索引。未排序與排序數組的長度等于OPEN集的最大值,和它們不同,對所有的n,索引數組的長度總是等于max(i(n))。如果你的函數是密集的(沒有不被使用的索引),max(i(n))將是你地圖中結點的數目。只要你的地圖是網格的,讓索引函數密集就是容易的。

假設i(n)是O(1)的,集合關系檢查將花費O(1),因為我們幾乎不需要檢查Array[i(n)]是否包含任何數據。Insertion is O(1), as we just ste?Array[i(n)].查找和刪除最佳操作是O(numnodes),因為我們必須搜索整個結構。調整操作是O(1)。

3.3.6?哈希表

索引數組使用了很多內存用于保存不在OPEN集中的所有結點。一個選擇是使用哈希表。哈希表使用了一個哈希函數h(n)把地圖上每個結點映射到一個哈希碼。讓哈希表的大小等于N的兩倍,以使發生沖突的可能性降低。假設h(n)?是O(1)的,集體關系檢查操作花費O(1);插入操作花費O(1);刪除最佳元素操作花費O(numnodes),因為我們需要搜索整個結構。調整操作花費O(1)。

3.3.7?二元堆

一個二元堆(不要和內存堆混淆)是一種保存在數組中的樹結構。和許多普通的樹通過指針指向子結點所不同,二元堆使用索引來查找子結點。C++ STL包含了一個二元堆的高效實現,我在我自己的A*代碼中使用了它。

在二元堆中,集體關系檢查花費O(F),因為你必須掃描整個結構。插入操作花費O(log F)而刪除最佳操作花費也是O(log F)。調整操作很微妙(tricky),花費O(F)時間找到節點,并且很神奇,只用O(log F)來調整。

我的一個朋友(他研究用于最短路徑算法的數據結構)說,除非在你的fringe集里有多于10000個元素,否則二元堆是很不錯的。除非你的游戲地圖特別大,否則你不需要更復雜的數據結構(如multi-level buckets(http://www-cs-students.stanford.edu/~csilvers/))。你應該盡可能不用Fibonacci?堆(http://www.star-lab.com/goldberg/pub/neci-tr-96-062.ps),因為雖然它的漸近復雜度很好,但是執行起來很慢,除非F足夠大。

3.3.8?伸展樹

堆是一種基于樹的結構,它有一個期望的O(log F)代價的時間操作。然而,問題是在A*算法中,通常的情況是,一個代價小的節點被移除(花費O(log F)的代價,因為其他結點必須從樹的底部向上移動),而緊接著一些代價小的節點被添加(花費O(log F)的代價,因為這些結點被添加到底部并且被移動到最頂部)。在這里,堆的操作在預期的情況下和最壞情況下是一樣的。如果我們找到這樣一種數據結構,最壞情況還是一樣,而預期的情況好一些,那么就可以得到改進。

伸展樹(Splay tree)是一種自調整的樹結構。任何對樹結點的訪問都嘗試把該結點推到樹的頂部(top)。這就產生了一個緩存效果("caching" effect):很少被使用的結點跑到底部(bottom)去了并且不減慢操作(don't slow down operations)。你的splay樹有多大并不重要,因為你的操作僅僅和你的“cache size”一樣慢。在A*中,低代價的結點使用得很多,而高代價結點經常不被使用,所以高代價結點將會移動到樹的底部。

使用伸展樹后,集體關系檢查,插入,刪除最佳和調整操作都是期望的O(log F)(注:原文為expected O(log F)?),最壞情況是O(F)。然而有代表性的是,緩存過程(caching)避免了最壞情況的發生。Dijkstra算法和帶有低估的啟發函數(underestimating heuristic)的A*算法卻有一些特性讓伸展樹達不到最優。特別是對結點n和鄰居結點n’來說,f(n') >= f(n)。當這發生時,也許插入操作總是發生在樹的同一邊結果是使它失去了平衡。我沒有試驗過這個。

3.3.9 HOT隊列

還有一種比堆好的數據結構。通常你可以限制優先隊列中值的范圍。給定一個限定的范圍,經常會存在更好的算法。例如,對任意值的排序可以在O(N log N)時間內完成,但當固定范圍時,桶排序和基數排序可以在O(N)時間內完成。

我們可以使用HOT(Heap On Top)隊列(http://www.star-lab.com/goldberg/pub /neci-tr-97-104.ps)來利用f(n') >= f(n),其中n’是n的一個鄰居結點。我們刪除f(n)值最小的結點n,插入滿足f(n) <= f(n') <= f(n) + delta的鄰居n',其中delta <= C。常數C是從一結點到鄰近結點代價改變量的最大值。因為f(n)是OPEN集中的最小f值,并且正要被插入的所有結點都小于或等于f(n) + delta,我們知道OPEN集中的所有f值都不超過一個0..delta的范圍。在桶/基數排序中,我們可以用“桶”(buckets)對OPEN集中的結點進行排序。

使用K個桶,我們把O(N)的代價降低到平均O(N/K)。通過HOT隊列,頂端的桶使用二元堆而所有其他的桶都是未排序數組。因而,對頂部的桶,集合關系檢查代價是預期的O(F/K),插入和刪除最佳是O(log (F/K))。對其他桶,集合關系檢查是O(F/K),插入是O(1),而刪除最佳根本不發生!如果頂端的桶是空的,那么我們必須把下一個桶即未排序數組轉換為二元堆。這個操作(“heapify”)可以在O(F/K)時間內完成。在調整操作中,刪除是O(F/K),然后插入是O(log (F/K))或O(1)。

在A*中,我們加入OPEN集中的許多結點實際上根本是不需要的。在這方面HOT隊列很有優勢,因為不需要的元素的插入操作只花費O(1)時間。只有需要的元素被heapified(代價較低的那些)。唯一一個超過O(1)的操作是從堆中刪除結點,只花費O(log (F/K))。

另外,如果C比較小,我們可以只讓K = C,則對于最小的桶,我們甚至不需要一個堆,國為在一個桶中的所有結點都有相同的f值。插入和刪除最佳都是O(1)時間!有人研究過,HOT隊列在至多在OPEN集中有800個結點時和堆一樣快,并且如果OPEN集中至多有1500個結點,則比堆快20%。我期望隨著結點的增加,HOT隊列也更快。

HOT隊列的一個簡單的變化是一個二層隊列(two-level queue):把好的結點放進一個數據結構(堆或數組)而把壞的結點放進另一個數據結構(數組或鏈表)。因為大多數進入OPEN集中的結點都“壞的”,它們從不被檢查,因而把它們放進出一個大數組是沒有害處的。

3.3.10?比較

注意有一點很重要,我們并不是僅僅關心漸近的行為(大O符號)。我們也需要關心小常數(low constant)下的行為。為了說明原因,考慮一個O(log F)的算法,和另一個O(F)的算法,其中F是堆中元素的個數。也許在你的機器上,第一個算法的實現花費10000*log(F)秒,而另一個的實現花費2*F秒。當F=256時,第一個算法將花費80000秒而第二個算法花費512秒。在這種情況下,“更快”的算法花費更多的時間,而且只有在當F>200000時才能運行得更快。

你不能僅僅比較兩個算法。你還要比較算法的實現。同時你還需要知道你的數據的大小(size)。在上面的例子中,第一種實現在F>200000時更快,但如果在你的游戲中,F小于30000,那么第二種實現好一些。

基本數據結構沒有一種是完全合適的。未排序數組或者鏈表使插入操作很快而集體關系檢查和刪除操作非常慢。排序數組或者鏈表使集體關系檢查稍微快一些,刪除(最佳元素)操作非常快而插入操作非常慢。二元堆讓插入和刪除操作稍微快一些,而集體關系檢查則很慢。伸展樹讓所有操作都快一些。HOT隊列讓插入操作很快,刪除操作相當快,而集體關系檢查操作稍微快一些。索引數組讓集體關系檢查和插入操作非常快,但是刪除操作不可置信地慢,同時還需要花費很多內存空間。哈希表和索引數組類似,但在普通情況下,它花費的內存空間少得多,而刪除操作雖然還是很慢,但比索引數組要快。

關于更高級的優先隊列的資料和實現,請參考Lee Killough的優先隊列頁面(http://members.xoom.com/killough/heaps.html)。

3.3.11?混合實現

為了得到最佳性能,你將希望使用混合數據結構。在我的A*代碼中,我使用一個索引數組從而集合關系檢查是O(1)的,一個二元堆從而插入操作和刪除最佳都是O(log F)的。對于調整操作,我使用索引數組從而花費O(1)時間檢查我是否真的需要進行調整(通過在索引數組中保存g值),然后在少數確實需要進行調整的情況中,我使用二元堆從而調整操作花費O(F)時間。你也可以使用索引數組保存堆中每個結點的位置,這讓你的調整操作變成O(log F)。

3.4?與游戲循環的交互

交互式的(尤其是實時的)游戲對最佳路徑的計算要求很高。能夠得到一個解決方案比得到最佳方案可能更重要。然而在所有其他因素都相同的情況下,短路徑比長路徑好。

一般來說,計算靠近初始結點的路徑比靠近目標結點的路徑更重要一些。立即開始原理(The principle of?immediate start):讓游戲中的物體盡可能快地開始行動,哪怕是沿著一條不理想的路徑,然后再計算一條更好的路徑。在實時游戲中,應該更多地關注A*的延遲情況(latency)而不是吞吐量(throughput)。

可以對物體編程讓它們根據自己的本能(簡單行為)或者智力(一條預先計算好的路徑)來行動。除非它們的智力告訴它們怎么行動,否則它們就根據自己的本能來行動(這是實際上使用的方法,并且Rodney Brook在他的機器人體系結構中也用到)。和立即計算所有路徑所不同,讓游戲在每一個,兩個,或者三個循環中搜索一條路徑。讓物體在開始時依照本能行動(可能僅僅是簡單地朝著目標直線前進),然后才為它們尋找路徑。這種方法讓讓路徑搜索的代價趨于平緩,因此它不會集中發生在同一時刻。

3.4.1?提前退出

可以從A*算法的主循環中提前退出來同時得到一條局部路徑。通常,當找到目標結點時,主循環就退出了。然而,在此之前的任意結點,可以得到一條到達OPEN中當前最佳結點的路徑。這個結點是到達目標點的最佳選擇,所以它是一個理想的中間結點(原文為so it's a reasonable place to go)。

可以提前退出的情況包括檢查了一定數量的結點,A*算法已經運行了幾毫秒時間,或者掃描了一個離初始點有些距離的結點。當使用路徑拼接時,應該給被拼接的路徑一個比全路徑(full path)小的最大長度。

3.4.2?中斷算法

如果需要進行路徑搜索的物體較少,或者如果用于保存OPEN和CLOSED集的數據結構較小,那么保存算法的狀態是可行的,然后退出到游戲循環繼續運行游戲。

3.4.3?組運動

路徑請求并不是均勻分布的。即時策略游戲中有一個常見的情況,玩家會選擇多個物體并命令它們朝著同樣的目標移動。這給路徑搜索系統以沉重的負載。

在這種情況下,為某個物體尋找到的路徑對其它物體也是同樣有用的。一種方法是,尋找一條從物體的中心到目的地中心的路徑P。對所有物體使用該路徑的絕大部分,對每一個物體,前十步和后十步使用為它自己尋找的路徑。物體i得到一條從它的開始點到P[10]的路徑,緊接著是共享的路徑P[10..len(P)-10],最后是從P[len(P)-10]到目的地的路徑。

為每個物體尋找的路徑是較短的(平均步數大約是10),而較長的路徑被共享。大多數路徑只尋找一次并且為所有物體所共享。然而,當玩家們看到所有的物體都沿著相同的路徑移動時,將對游戲失去興趣。為了對系統做些改進,可以讓物體稍微沿著不同的路徑運動。一種方法是選擇鄰近結點以改變路徑。

另一種方法是讓每個物體都意識到其它物體的存在(或許是通過隨機選擇一個“領導”物體,或者是通過選擇一個能夠最好地意識到當前情況的物體),同時僅僅為領導尋路。然后用flocking算法讓它們以組的形式運動。

然而還有一種方法是利用A*算法的中間狀態。這個狀態可以被朝著相同目標移動的多個物體共享,只要物體共享相同的啟發式函數和代價函數。當主循環退出時,不要消除OPEN和CLOSED集;用A*上一次的OPEN和CLOSED集開始下一次的循環(下一個物體的開始位置)。(這可以被看成是中斷算法和提前退出部分的一般化)

3.4.4?細化

如果地圖中沒有障礙物,而有不同代價的地形,那么可以通過低估地形的代價來計算一條初始路徑。例如,如果草地的代價是1,山地代價是2,山脈的代價是3,那么A*會考慮通過3個草地以避免1個山脈。通過把草地看成1,山地看成1.1,而山脈看成1.2來計算初始路徑,A*將會用更少的時間去設法避免山脈,而且可以更快地找到一條路徑(這接近于精確啟發函數的效果)。一旦找到一條路徑,物體就可以開始移動,游戲循環就可以繼續了。當多余的CPU時間是可用的時候,可以用真實的移動代價去計算更好的路徑。

4 A*算法的變種

4.1 beam search?beam search

在A*的主循環中,OPEN集保存所有需要檢查的結點。Beam Search是A*算法的一個變種,這種算法限定了OPEN集的尺寸。如果OPEN集變得過大,那些沒有機會通向一條好的路徑的結點將被拋棄。缺點是你必須讓排序你的集合以實現這個,這限制了可供選擇的數據結構。

4.2?迭代深化

迭代深化是一種在許多AI算法中使用的方法,這種方法從一個近似解開始,逐漸得到更精確的解。該名稱來源于游戲樹搜索,需要查看前面幾步(比如在象棋里),通過查看前面更多步來提高樹的深度。一旦你的解不再有更多的改變或者改善,就可以認為你已經得到足夠好的解,當你想要進一步精確化時,它不會再有改善。在ID-A*中,深度是f值的一個cutoff。當f的值太大時,結點甚至將不被考慮(例如,它不會被加入OPEN集中)。第一次迭代只處理很少的結點。此后每一次迭代,訪問的結點都將增加。如果你發現路徑有所改善,那么就繼續增加cutoff,否則就可以停止了。更多的細節請參考這些關于ID-A*的資料:http://www.apl.jhu.edu/~hall/AI-Programming/IDA-Star.html。

我本人認為在游戲地圖中沒有太大的必要使用ID-A*尋路。ID算法趨向于增加計算時間而減少內存需求。然而在地圖路徑搜索中,“結點”是很小的——它們僅僅是坐標而已。我認為不保存這些結點以節省空間并不會帶來多大改進。

4.3?動態衡量

在動態衡量中,你假設在開始搜索時,最重要的是訊速移動到任意位置;而在搜索接近結束時,最重要的是移動到目標點。

f(p) = g(p) + w(p) * h(p)

啟發函數中帶有一個權值(weight)(w>=1)。當你接近目標時,你降低這個權值;這降低了啟發函數的重要性,同時增加了路徑真實代價的相對重要性。

4.4?帶寬搜索

帶寬搜索(Bandwidth Search)有兩個對有些人也許有用的特性。這個變種假設h是過高估計的值,但不高于某個數e。如果這就是你遇到的情況,那么你得到的路徑的代價將不會比最佳路徑的代價超過e。重申一次,你的啟發函數設計的越好,最終效果就越好。

另一個特性是,你可以丟棄OPEN集中的某些結點。當h+d比路徑的真實代價高的時候(對于某些d),你可以丟棄那些f值比OPEN集中的最好結點的f值高至少e+d的結點。這是一個奇怪的特性。對于好的f值你有一個“范圍”("band"),任何在這個范圍之外的結點都可以被丟棄掉,因為這個結點肯定不會在最佳路徑上。

好奇地(Curiously),你可以對這兩種特性使用不同的啟發函數,而問題仍然可以得到解決。使用一個啟發函數以保證你得到的路徑不會太差,另一個用于檢查從OPEN集中去掉哪些結點。

4.5?雙向搜索

與從開始點向目標點搜索不同的是,你也可以并行地進行兩個搜索——一個從開始點向目標點,另一個從目標點向開始點。當它們相遇時,你將得到一條好的路徑。

這聽起來是個好主意,但我不會給你講很多內容。雙向搜索的思想是,搜索過程生成了一棵在地圖上散開的樹。一棵大樹比兩棵小樹差得多,所以最好是使用兩棵較小的搜索樹。然而我的試驗表明,在A*中你得不到一棵樹,而只是在搜索地圖中當前位置附近的區域,但是又不像Dijkstra算法那樣散開。事實上,這就是讓A*算法運行得如此快的原因——無論你的路徑有多長,它并不進行瘋狂的搜索,除非路徑是瘋狂的。它只嘗試搜索地圖上小范圍的區域。如果你的地圖很復雜,雙向搜索會更有用。

面對面的方法(The?front-to-front?variation)把這兩種搜索結合在一起。這種算法選擇一對具有最好的g(start,x) + h(x,y) + g(y,goal)的結點,而不是選擇最好的前向搜索結點——g(start,x) + h(x,goal),或者最好的后向搜索結點——g(y,goal) + h(start,y)。

Retargeting方法不允許前向和后向搜索同時發生。它朝著某個最佳的中間結點運行前向搜索一段時間,然后再朝這個結點運行后向搜索。然后選擇一個后向最佳中間結點,從前向最佳中間結點向后向最佳中間結點搜索。一直進行這個過程,直到兩個中間結點碰到一塊。

4.6?動態A*與終身計劃A*

有一些A*的變種允許當初始路徑計算出來之后,世界發生改變。D*用于當你沒有全局所有信息的時候。如果你沒有所有的信息,A*可能會出錯;D*的貢獻在于,它能糾正那些錯誤而不用過多的時間。LPA*用于代價會改變的情況。在A*中,當地圖發生改變時,路徑將變得無效;LPA*可以重新使用之前A*的計算結果并產生新的路徑。然而,D*和LPA*都需要很多內存——用于運行A*并保存它的內部信息(OPEN和CLOSED集,路徑樹,g值),當地圖發生改變時,D*或者LPA*會告訴你,是否需要就地圖的改變對路徑作調整。在一個有許多運動著的物體的游戲中,你經常不希望保存所有這些信息,所以D*和LPA*在這里并不適用。它們是為機器人技術而設計的,這種情況下只有一個機器人——你不需要為別的機器人尋路而重用內存。如果你的游戲只有一個或者少數幾個物體,你可以研究一下D*或者LPA*。

  • Overview of D*(http://www.frc.ri.cmu.edu/~axs/dynamic_plan.html)
  • D* Paper 1(http:// http://www.frc.ri.cmu.edu/~axs/doc/icra94.ps)
  • D* Paper 2(http:// http://www.frc.ri.cmu.edu/~axs/doc/ijcai95.ps)
  • Lifelong planning overview(http://idm-lab.org/project-a.html)
  • Lifelong planning paper (PDF)(http://csci.mrs.umn.edu/UMMCsciwiki/pub/?Csci3903s03/KellysPaper/seminar.pdf)
  • Lifelong planning A* applet(http://idm-lab.org/applet.html)

5?處理運動障礙物

一個路徑搜索算法沿著固定障礙物計算路徑,但是當障礙物會運動時情況又怎樣?當一個物體到達一個特寫的位置,原來的障礙物也許不再在那兒了,或者一個新的障礙物也許到達那兒。處理該問題的一個方法是放棄路徑搜索而使用運動算法(movement algorithms)替代,這就不能look far ahead;這種方法會在后面的部分中討論。這一部分將對路徑搜索方法進行修改從而解決運動障礙物的問題。

5.1?重新計算路徑

當時間漸漸過去,我們希望游戲世界有所改變。以前搜索到的一條路徑到現在也許不再是最佳的了。對舊的路徑用新的信息進行更新是有價值的。以下規則可以用于決定什么時候需要重新計算路徑:

  • 每N步:這保證用于計算路徑的信息不會舊于N步。
  • 任何可以使用額外的CPU時間的時候:這允許動態調整路徑的性質;在物體數量多時,或者運行游戲的機器比較慢時,每個物體對CPU的使用可得到減少。
  • 當物體拐彎或者跨越一個導航點(waypoint)的時候。
  • 當物體附近的世界改變了的時候。

重計算路徑的主要缺點是許多路徑信息被丟棄了。例如,如果路徑是100步長,每10步重新計算一次,路徑的總步數將是100+90+80+70+60+50+40+30+20+10 = 550。對M步長的路徑,大約需要計算M^2步。因此如果你希望有許多很長的路徑,重計算不是個好主意。重新使用路徑信息比丟棄它更好。

5.2?路徑拼接

當一條路徑需要被重新計算時,意味著世界正在改變。對于一個正在改變的世界,對地圖中當前鄰近的區域總是比對遠處的區域了解得更多。因此,我們應該集中于在附近尋找好的路徑,同時假設遠處的路徑不需要重新計算,除非我們接近它。與重新計算整個路徑不同,我們可以重新計算路徑的前M步:

  • 令p[1]..p[N]為路徑(N步)的剩余部分
  • 為p[1]到p[M]計算一條新的路徑
  • 把這條新路徑拼接(Splice)到舊路徑:把p[1]..p[M]用新的路徑值代替
  • ?

    ?

    因為p[1]和p[M]比分開的M步小(原文:Since p[1] and p[M] are fewer than M steps apart),看起來新路徑不會很長。不幸的是,新的路徑也許很長而且不夠好。上面的圖顯示了這種情況。最初的紅色路徑是1-2-3-4,褐色的是障礙物。如果我們到達2并且發現從2到達3的路徑被封鎖了,路徑拼接技術會把2-3用2-5-3取代,結果是物體沿著路徑1-2-5-3-4運動。我們可以看到這不是一條好的路徑,藍色的路徑1-2-5-4是一條更好的路徑。

    通常可以通過查看新路徑的長度檢測到壞的路徑。如果這嚴格大于M,就可能是不好的。一個簡單的解決方法是,為搜索算法設置一個最大路徑長度。如果找不到一條短的路徑,算法返回錯誤代碼;這種情況下,用重計算路徑取代路徑拼接,從而得到路徑1-2-5-4.。

    對于其它情況,對于N步的路徑,路徑拼接會計算2N或者3N步,這取決于拼接新路徑的頻率。對于對世界的改變作反應的能力而言,這個代價是相當低的。令人吃驚的是這個代價和拼接的步數M無關。M不影響CPU時間,而控制了響應和路徑質量的折衷。如果M太大,物體的移動將不能快速對地圖的改變作出反應。如果M太小,拼接的路徑可能太短以致不能正確地繞過障礙物;許多不理想的路徑(如1-2-5-3-4)將被找到。嘗試不同的M值和不同的拼接標準(如每3/4?M步),看看哪一種情況對你的地圖最合適。

    路徑拼接確實比重計算路徑要快,但它不能對路徑的改變作出很好的反應。經常可以發現這種情況并用路徑重計算來取代。也可以調整一些變量,如M和尋找新路徑的時機,所以可以對該方法進行調整(甚至在運行時)以用于不同的情況。

    Note:Bryan Stout?有兩個算法,Patch-One和Patch-All,他從路徑拼接中得到靈感,并在實踐中運行得很好。他出席了GDC 2007(https://www.cmpevents.com/GD07/a.asp?option =C &V=11& SessID=4608);一旦他把資料放在網上,我將鏈接過去。

    Implementation Note

    反向保存路徑,從而刪除路徑的開始部分并用不同長度的新路徑拼接將更容易,因為這兩個操作都將在數組的末尾進行。本質上你可以把這個數組看成是堆棧因為頂部的元素總是下一個要使用的。

    5.3?監視地圖變化

    與間隔一段時間重計算全部或部分路徑不同的是,可以讓地圖的改變觸發一次重計算。地圖可以分成區域,每個物體都可以對某些區域感興趣(可以是包含部分路徑的所有區域,也可以只是包含部分路徑的鄰近區域)。當一個障礙物進入或者離開一個區域,該區域將被標識為已改變,所有對該區域感興趣的物體都被通知到,所以路徑將被重新計算以適應障礙物的改變。

    這種技術有許多變種。例如,可以每隔一定時間通知物體,而不是立即通知物體。多個改變可以成組地觸發一個通知,因此避免了額外的重計算。另一個例子是,讓物體檢查區域,而不是讓區域通知物體。

    監視地圖變化允許當障礙物不改變時物體避免重計算路徑,所以當你有許多區域并不經常改變時,考慮這種方法。

    5.4?預測障礙物的運動

    如果障礙物的運動可以預測,就能為路徑搜索考慮障礙物的未來位置。一個諸如A*的算法有一個代價函數用以檢查穿過地圖上一點的代價有多難。A*可以被改進從而知道到達一點的時間需求(通過當前路徑長度來檢查),而現在則輪到代價函數了。代價函數可以考慮時間,并用預測的障礙物位置檢查在某個時刻地圖某個位置是否可以通過。這個改進不是完美的,然而,因為它并不考慮在某個點等待障礙物自動離開的可能性,同時A*并不區分到達相同目的地的不同的路徑,而是針對不同的目的地,所以還是可以接受的。

    6?預計算路徑的空間代價

    有時,路徑計算的限制因素不是時間,而是用于數以百計的物體的存儲空間。路徑搜索器需要空間以運行算法和保存路徑。算法運行所需的臨時空間(在A*中是OPEN和CLOSED集)通常比保存結果路徑的空間大許多。通過限制在一定的時間計算一條路徑,可以把臨時空間數量最小化。另外,為OPEN和CLOSED集所選擇的數據結構的不同,最小化臨時空間的程度也有很大的不同。這一部分聚集于優化用于計算路徑的空間代價。

    6.1?位置VS方向

    一條路徑可以用位置或者方向來表示。位置需要更多的空間,但是有一個優點,易于查詢路徑中的任意位置或者方向而不用沿著路徑移動。當保存方向時,只有方向容易被查詢;只有沿著整個路徑移動才能查詢位置。在一個典形的網格地圖中,位置可以被保存為兩個16位整數,每走一步是32位。而方向是很少的,因此用極少的空間就夠了。如果物體只能沿著四個方向移動,每一步用兩位就夠了;如果物體能沿著6個或者8個方向移動,每一步也只需要三位。這些對于保存路徑中的位置都有明顯的空間節省。Hannu Kankaanpaa指出可以進一步減少空間需求,那就是保存相對方向(右旋60度)而不是絕對方向(朝北走)。有些相對方向對某些物體來說意義不大。比如,如果你的物體朝北移動,那么下一步朝南移動的可能性很小。在只有六種方向的游戲中,你只有五個有意義的方向。在某些地圖中,也許只有三個方向(直走,左旋60度,右旋60度)有意義,而其它地圖中,右旋120度是有效的(比如,沿著陡峭的山坡走之字形的路徑時)。

    6.2?路徑壓縮

    一旦找到一條路徑,可以對它進行壓縮。可以用一個普通的壓縮算法,但這里不進行討論。使用特定的壓縮算法可以縮小路徑的存儲,無論它是基于位置的還是基于方向的。在做決定之前,考察你的游戲中的路徑以確定哪種壓縮效果最好。另外還要考慮實現和調試,代碼量,and whether it really matters.如果你有300個物體并且在同一時刻只有50個在移動,同時路徑比較短(100步),內存總需求大概只有不到50k,總之,沒有必要擔心壓縮的效果。

    6.2.1?位置存儲

    在障礙物比地形對路徑搜索影響更大的地圖中,路徑中有大部分是直線的。如果是這種情況,那么路徑只需要包含直線部分的終止點(有時叫waypoints)。此時移動過程將包含檢查下一結點和沿著直線向前移動。

    6.2.2?方向存儲

    保存方向時,有一種情況是同一個方向保存了很多次。可以用簡單的方法節省空間。

    一種方法是保存方向以及朝著該方向移動的次數。和位置存儲的優化不同,當一個方向并不是移動很多次時,這種優化的效果反而不好。同樣的,對于那些可以進行位置壓縮的直線來說,方向壓縮是行不通的,因為這條直線可能沒有和正在移動的方向關聯。通過相對方向,你可以把“繼續前進”當作可能的方向排除掉。Hannu Kankaanpaa指出,在一個八方向地圖中,你可以去掉前,后,以及向左和向右135度(假設你的地圖允許這個),然后你可以僅用兩個比特保存每個方向。

    另一種保存路徑的方法是變長編碼。這種想法是使用一個簡單的比特(0)保存最一般的步驟:向前走。使用一個“1”表示拐彎,后邊再跟幾個比特表示拐彎的方向。在一個四方向地圖中,你只能左轉和右轉,因此可以用“10”表示左轉,“11”表示右轉。

    變長編碼比run length encoding更一般,并且可以壓縮得更好,但對于較長的直線路徑則不然。序列(向北直走6步,左轉,直走3步,右轉,直走5步,左轉,直走2步)用run length encoding表示是[(NORTH, 6), (WEST, 3), (NORTH, 5), (WEST, 2)]。如果每個方向用2比特,每個距離用8比特,保存這條路徑需要40比特。而對于變長編碼,你用1比特表示每一步,2比特表示拐彎——[NORTH 0 0 0 0 0 0 10 0 0 0 11 0 0 0 0 0 10 0 0]——一共24比特。如果初始方向和每次拐彎對應1步,則每次拐彎都節省了一個比特,結果只需要20比特保存這條路徑。然而,用變長編碼保存更長的路徑時需要更多的空間。序列(向北直走200步)用run length encoding表示是[(NORTH, 200)],總共需要10比特。用變長編碼表示同樣的序列則是[NORTH 0 0 ...],一共需要202比特。

    6.3?計算導航點

    一個導航點(waypoint)是路徑上的一個結點。與保存路徑上的每一步不同,在進行路徑搜索之后,一個后處理(post-processing)的步驟可能會把若干步collapse(譯者:不好翻譯,保留原單詞)為一個簡單的導航點,這經常發生在路徑上那些方向發生改變的地方,或者在一個重要的(major)位置如城市。然后運動算法將在兩個導航點之間運行。

    6.4?極限路徑長度

    當地圖中的條件或者秩序會發生改變時,保存一條長路徑是沒有意義的,因為在從某些點開始,后邊的路徑已經沒有用了。每個物體都可以保存路徑開始時的特定幾步,然后當路徑已經沒用時重新計算路徑。這種方法慮及了(allows for)對每個物體使用數據的總量的管理。

    6.5?總結

    在游戲中,路徑潛在地花費了許多存儲空間,特別是當路徑很長并且有很多物體需要尋路時。路徑壓縮,導航點和beacons通過把多個步驟保存為一個較小數據從而減少了空間需求。Waypoints rely on straight-line segments being common so that we have to store only the endpoints, while beacons rely on there being well-known paths calculated beforehand between specially marked places on the map.(譯者:此處不好翻譯,暫時保留原文)如果路徑仍然用了許多存儲空間,可以限制路徑長度,這就回到了經典的時間-空間折衷法:為了節省空間,信息可以被丟棄,稍后才重新計算它。

    轉載于:https://www.cnblogs.com/alexanderkun/p/4599287.html

    總結

    以上是生活随笔為你收集整理的A*算法介绍的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    97精品国产97久久久久久久久久久久 | 国产黄免费 | 国产精品密入口果冻 | 免费亚洲黄色 | 国产综合片 | 特级西西www44高清大胆图片 | 天堂av观看 | 午夜色婷婷 | 久久精品超碰 | 99色婷婷| 国产精品久久久久久久久久免费 | 国产理论一区二区三区 | 久久久国产一区二区三区四区小说 | 日韩三级.com | 亚洲伦理一区二区 | 天天弄天天操 | 日韩va欧美va亚洲va久久 | 最近高清中文在线字幕在线观看 | 狠狠色噜噜狠狠 | 成人精品一区二区三区电影免费 | 天天射天 | 粉嫩av一区二区三区入口 | 五月婷婷中文字幕 | 日韩电影在线观看中文字幕 | 国产精品免费不 | 成人免费影院 | 国产亚洲成av人片在线观看桃 | 日本久久成人 | 久久精品国产免费看久久精品 | 久久夜色精品国产欧美乱极品 | 91精品一区二区三区久久久久久 | 日韩精品中文字幕有码 | 日韩一区二区三区免费视频 | 亚洲va欧美va人人爽 | 久久视频99 | 97视频网址 | 欧美精品一区二区免费 | 国产精品综合av一区二区国产馆 | 91看片在线| 免费日韩一区二区 | 综合激情网| 久久综合九色综合久久久精品综合 | 日韩在线视频网站 | 国产精品影音先锋 | 福利一区二区三区四区 | 一级电影免费在线观看 | 91香蕉国产在线观看软件 | 日韩免费视频在线观看 | 丁香激情综合 | 精品v亚洲v欧美v高清v | 国产精品a成v人在线播放 | 五月婷婷爱 | 中文字幕av影院 | 福利一区在线视频 | 日韩成人精品一区二区 | 久久国产精品免费观看 | 日韩a在线看| av在线免费观看黄 | 国产视频资源 | 国产在线播放一区二区三区 | 中文字幕中文字幕在线中文字幕三区 | 国产日本亚洲高清 | 亚州av成人 | 中文字幕在线观看1 | 亚洲欧美一区二区三区孕妇写真 | 91精品国产成人www | 91爱看片 | 精品少妇一区二区三区在线 | 亚洲欧美乱综合图片区小说区 | 在线观看一二三区 | 9797在线看片亚洲精品 | 久久最新 | 久久精品视频在线看 | 久久成人资源 | 91影视成人 | 久久人人插 | 成人精品一区二区三区电影免费 | 久久一区二区三区国产精品 | 1024手机在线看 | 国产一区二区在线免费观看 | 日韩高清成人 | 色综合天天狠狠 | 特级毛片在线观看 | 伊人视频 | 国产品久精国精产拍 | 婷婷精品国产欧美精品亚洲人人爽 | 81国产精品久久久久久久久久 | 色香网| 亚州国产精品久久久 | 日韩精品免费 | 亚洲精品福利在线观看 | 最新国产在线 | 中文字幕一区2区3区 | 日韩视频www | 亚洲精品日韩在线观看 | 免费观看一级特黄欧美大片 | 色橹橹欧美在线观看视频高清 | 91久久国产露脸精品国产闺蜜 | 久久国产精品电影 | 综合久久一本 | 欧美日韩高清一区二区 | 欧美一区二区三区不卡 | 久草青青在线观看 | 99在线视频观看 | 亚洲精品一区二区18漫画 | 免费观看成人网 | 久久99亚洲精品久久 | 色资源网免费观看视频 | 99riav1国产精品视频 | 91九色在线观看视频 | 日韩美女高潮 | 欧美日韩国产伦理 | 亚洲精品456在线播放 | www.av在线播放 | 国产成人精品亚洲日本在线观看 | av三级av| 色午夜 | 欧美怡红院视频 | 欧洲精品视频一区二区 | 伊人色综合久久天天 | 国产一二区视频 | 国产精品激情在线观看 | 美女网站色在线观看 | 国产精彩视频 | 久久精品视频网站 | 久久国产精品一国产精品 | 亚洲天堂网在线播放 | 国产精品手机在线 | 日韩欧美视频一区二区三区 | 69精品视频| 欧美精品做受xxx性少妇 | 特级西西人体444是什么意思 | 欧美天天射 | 久久在线免费观看视频 | 涩涩色亚洲一区 | 亚洲精品视频一 | 久久久一本精品99久久精品 | 黄色亚洲大片免费在线观看 | 色射爱 | 国产品久精国精产拍 | 午夜婷婷网 | www.亚洲| 日韩精品高清视频 | 精品久久一区 | 久草视频在线新免费 | 日韩在线电影一区二区 | av手机版 | 亚洲欧美国产精品 | 天天射天 | 在线免费观看欧美日韩 | av一级片网站 | 91精品啪在线观看国产线免费 | 中文字幕在线久一本久 | 黄色av电影在线 | 成人丝袜| 亚洲欧洲一区二区在线观看 | 国产精品久久久久久久7电影 | 久久久久久久久久国产精品 | 色婷婷亚洲婷婷 | 亚洲精品色视频 | 久久在线视频在线 | 天天天天射 | 人人澡人人模 | 91最新视频 | 亚洲狠狠 | 中国美女一级看片 | 伊人电影在线观看 | 日韩欧美在线一区 | 99精品99 | 欧美肥妇free | 中文字幕999 | 精品国偷自产国产一区 | 久久综合九色综合欧美狠狠 | 精品国产理论 | 日韩在线欧美在线 | 久久夜色精品国产亚洲aⅴ 91chinesexxx | 成人在线网站观看 | 国产色视频一区二区三区qq号 | 天天插日日插 | 99热国产在线 | 91av视频免费在线观看 | 日韩精品一区二区在线观看视频 | 日韩av电影网站在线观看 | 天天草天天干天天 | 国产电影黄色av | 中文字幕观看视频 | 国产va饥渴难耐女保洁员在线观看 | a在线观看国产 | 成人免费xxx在线观看 | 在线性视频日韩欧美 | 成av在线 | 成人av久久| 色爱成人网 | 久久不见久久见免费影院 | 在线视频麻豆 | 韩国av一区二区 | 日韩av免费一区二区 | 成人av电影免费观看 | 精品欧美一区二区在线观看 | 夜夜躁天天躁很躁波 | 欧美一级电影免费观看 | 亚洲女人天堂成人av在线 | 国产麻豆电影在线观看 | 91福利视频在线 | www.av小说| 欧美国产日韩一区二区三区 | 人人爱人人射 | 99热这里只有精品1 av中文字幕日韩 | 91欧美日韩国产 | 最近高清中文在线字幕在线观看 | 久久成熟| 在线观看国产区 | 国产成人在线网站 | 在线精品在线 | 国产亚洲精品美女久久 | japanese黑人亚洲人4k | 久草在线资源观看 | 综合色中色 | 欧美一级日韩免费不卡 | 91麻豆精品久久久久久 | 久久99精品国产一区二区三区 | 日产乱码一二三区别在线 | 久久99九九99精品 | 色婷婷激情四射 | 精品久久久久久综合日本 | 天天操天天射天天操 | 久草亚洲视频 | 国产精品美女久久久 | 久久视频免费 | 中文字幕免费高清在线 | 999久久久久久久久久久 | 国产精品一区二区三区视频免费 | 天天射天天干 | 久久久一本精品99久久精品 | 色在线中文字幕 | 在线播放日韩 | av免费在线观看网站 | 免费av在线网站 | 亚洲综合在线观看视频 | 国产精品午夜在线观看 | 91麻豆精品一区二区三区 | 波多野结衣视频在线 | 超碰97在线资源 | 免费亚洲精品视频 | 操高跟美女 | 日韩欧美在线观看 | 色婷婷www | 久久视频精品在线观看 | 免费男女羞羞的视频网站中文字幕 | 天天色图| 亚洲天堂精品视频在线观看 | 国产精品手机在线观看 | 亚洲精品视频网站在线观看 | 午夜精品中文字幕 | 日韩精品一区二区在线视频 | 成人av一区二区在线观看 | 欧美另类sm图片 | 色插综合 | 狠狠干中文字幕 | av片子在线观看 | 欧美一级视频免费看 | 香蕉视频最新网址 | 国产91精品一区二区绿帽 | wwxxxx日本| 九九色综合 | 亚洲视频六区 | 国产精品毛片久久久久久久 | www.少妇| 成人午夜毛片 | 麻豆一区二区三区视频 | 天天操天天操天天操天天操天天操 | 九九热在线精品视频 | 欧美贵妇性狂欢 | 国产视频精品免费 | 国内成人综合 | 久久久视屏| 欧美人牲 | 亚洲精品国产精品乱码不99热 | 麻豆果冻剧传媒在线播放 | 美女黄久久 | 国产成人专区 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 欧美亚洲xxx | 亚洲欧美日韩精品一区二区 | 色999精品| 国产尤物视频在线 | 麻豆 videos| 天天操天天干天天玩 | 中文字幕亚洲精品日韩 | 日韩一区二区免费在线观看 | 亚洲高清在线观看视频 | 在线观看播放av | 成人91视频| 成人午夜电影在线 | 最新极品jizzhd欧美 | 欧美精品一区二区在线播放 | 91视频一8mav | 色婷久久 | 在线亚洲小视频 | 69精品 | 激情一区二区三区欧美 | 中文字幕久久精品亚洲乱码 | 成年人免费在线播放 | www.xxxx变态.com | 国产精品24小时在线观看 | 丁香婷婷自拍 | 99久久精品国产亚洲 | 亚欧日韩成人h片 | 一级大片在线观看 | 中文字幕日本特黄aa毛片 | 国产精品久久久久aaaa九色 | 美女在线观看网站 | 最新国产在线 | 久久久久亚洲最大xxxx | 免费av黄色 | 中文字幕刺激在线 | 日韩特级毛片 | 麻豆视频免费入口 | 手机在线永久免费观看av片 | 天天艹| 婷婷丁香激情网 | 在线观看视频日韩 | 国产精品一区二区av | 一区二区三区日韩在线观看 | 在线影视 一区 二区 三区 | 欧美精品乱码久久久久 | 天天干天天玩天天操 | 黄a网站| 国产免费看| 91最新中文字幕 | 一二三区在线 | 国产精品一区二区免费 | 久久精品播放 | 免费色婷婷 | 国产精品高 | 97精品国产91久久久久久久 | 国产精品色婷婷视频 | 午夜久久福利影院 | av片中文| 午夜精品一区二区三区在线观看 | 就要干b| 日韩精品视频在线免费观看 | 亚洲精品在线播放视频 | 中文字幕资源在线 | 99精品在线 | 操操操干干干 | 国产亚洲高清视频 | 欧美日韩超碰 | 色狠狠综合| 五月天亚洲综合 | 亚洲一级在线观看 | 免费看黄的| 免费在线观看成人av | 国产国产人免费人成免费视频 | 亚洲国产精品成人av | 精品欧美一区二区三区久久久 | 麻豆传媒在线免费看 | 色婷婷狠狠操 | 黄色视屏免费在线观看 | 国模精品在线 | 亚洲精品日韩在线观看 | 精品久久亚洲 | 麻豆91在线 | 粉嫩一区二区三区粉嫩91 | 日韩在线电影观看 | 在线免费观看亚洲视频 | 草久视频在线 | 婷婷色5月 | 一级c片| 国产一二三在线视频 | 九九九热精品免费视频观看网站 | 日本高清中文字幕有码在线 | 日本黄色大片免费看 | 久久久久在线 | 狠狠色丁香婷婷综合视频 | 亚洲午夜av电影 | 操久| 日韩试看| 热99在线视频 | 国产 日韩 欧美 中文 在线播放 | 欧美日韩在线第一页 | 色婷婷视频网 | 综合天天| 精品一区二区在线免费观看 | 成人免费观看视频大全 | 91免费看片黄 | 欧美成人999 | 99视频在线| 美女视频黄免费的 | 精品视频www | 亚洲精品国产精品久久99 | 中文字幕观看在线 | 免费日韩av电影 | 亚洲aⅴ在线 | 青青河边草免费视频 | 久久99国产精品久久 | 久草久热 | 精品一区 在线 | a视频在线播放 | 欧美 日韩 国产 成人 在线 | 国产精品久久久久一区二区三区 | 天天操天天射天天插 | 激情偷乱人伦小说视频在线观看 | 亚洲免费a| 国产精品12| 免费视频一级片 | 日韩成人免费在线电影 | 婷婷六月天综合 | 99r在线视频 | 日韩欧美一区二区三区在线观看 | 久久久久久网站 | 国产精品美女久久久久久 | av中文字幕免费在线观看 | 91原创在线观看 | 91看成人 | 精品视频在线看 | 久久精品国产亚洲精品2020 | 一级成人免费 | 91精品久久久久久久久久入口 | 天天干干 | 能在线看的av| 在线va视频| 日本少妇久久久 | 久久人人添人人爽添人人88v | 久久成人久久 | 久久天| 国产99久久久精品 | 国产麻豆精品95视频 | 六月丁香在线观看 | 免费av看片 | 久久久久欠精品国产毛片国产毛生 | 亚洲精品综合一区二区 | 久草爱| 国产精品久久久久免费a∨ 欧美一级性生活片 | 国产高清视频在线播放 | 亚洲精品综合欧美二区变态 | 亚洲欧洲精品在线 | 在线观看麻豆av | 五月综合激情 | 国产精品亚州 | 九九久久影视 | 91视频 - v11av | 中文字幕一区2区3区 | 国产精品1000 | 久久99中文字幕 | 国产麻豆精品传媒av国产下载 | 波多野结衣视频一区二区 | 99热精品国产 | 丁香六月五月婷婷 | 亚洲春色综合另类校园电影 | 91精品免费在线 | 欧美一区二区三区在线看 | 国产在线观看免费观看 | 国产福利电影网址 | 精品国产一区二区三区四 | 最近日本中文字幕a | 亚洲精品视频在线看 | 在线观看日韩免费视频 | 999久久久久久 | 国产精品女人久久久久久 | 91天天视频 | 偷拍区另类综合在线 | 天天干天天干 | 亚洲精品美女久久17c | 亚州中文av| av看片网址 | 久久综合五月天 | 免费在线观看日韩欧美 | 色www免费视频| 又爽又黄又无遮挡网站动态图 | 一区二区三区在线免费播放 | 五月宗合网| 亚洲视屏| 日韩综合视频在线观看 | 天天玩天天操天天射 | 国产91精品久久久久 | 色射爱 | 欧美国产日韩一区二区三区 | 亚洲一区二区黄色 | 免费在线观看视频a | 国色天香永久免费 | 国产麻豆剧传媒免费观看 | 亚洲欧美日韩精品一区二区 | 国产123区在线观看 国产精品麻豆91 | 国产又粗又猛又爽又黄的视频免费 | 日韩精品欧美精品 | 最近中文字幕在线中文高清版 | 色婷婷丁香 | 91av视频在线免费观看 | 婷婷六月天在线 | 国产一区二区视频在线 | 亚洲另类视频 | 碰天天操天天 | 亚洲精品视频在线观看免费视频 | 国产视频日韩 | 97精品一区 | 亚洲天天 | 黄色精品一区二区 | 免费在线国产黄色 | 欧美黄色软件 | 国产九色在线播放九色 | 欧美日韩免费在线视频 | 91在线亚洲 | 成人国产精品免费 | 97精品国产91久久久久久久 | 免费av网址在线观看 | 麻豆网站免费观看 | 激情丁香 | 麻豆精品国产传媒 | 在线视频手机国产 | 亚洲成a人片在线www | 免费观看mv大片高清 | 婷婷精品在线视频 | 成人综合免费 | 日韩中午字幕 | 成人cosplay福利网站 | 黄色网址在线播放 | 免费a视频在线 | 夜夜躁天天躁很躁波 | 911久久香蕉国产线看观看 | 少妇bbb| 欧美国产日韩在线视频 | 欧美三级在线播放 | 奇米影视777影音先锋 | 欧美极品少妇xxxxⅹ欧美极品少妇xxxx亚洲精品 | 中文字幕色在线视频 | www中文在线| 中文字幕 国产视频 | 日日夜夜精品网站 | 69精品久久 | 成人亚洲综合 | 精品高清美女精品国产区 | 亚洲一级电影 | 亚洲色图 校园春色 | 我要看黄色一级片 | 色综合久久中文字幕综合网 | 色午夜 | 在线观看你懂的网站 | 亚洲精品乱码久久久久久高潮 | 日韩在线免费电影 | 免费看成人片 | 麻豆视频在线免费 | 国产久草在线观看 | 超碰资源在线 | 九九久久久 | 在线国产视频一区 | 精品一区二区三区在线播放 | 中文字幕在线观看资源 | 久久福利剧场 | 国产精品毛片一区视频播不卡 | 在线观看日韩中文字幕 | 国产在线精品国自产拍影院 | 手机av网站| 久久久久免费网站 | 99视频在线免费播放 | 欧美一区二区精美视频 | 欧美日韩在线网站 | 91在线小视频 | 在线观看av黄色 | 午夜av一区二区三区 | 亚洲午夜在线视频 | 三级视频国产 | 免费精品 | 日韩精品一区二区三区视频播放 | 日韩字幕| 性色av免费看 | 五月婷婷久 | 免费在线观看成年人视频 | 最新国产在线视频 | 一级性视频 | 国产日韩欧美视频 | 日韩av中文 | 国产区 在线 | 中文字幕乱码在线播放 | 天天干亚洲 | 国产成人精品一二三区 | 色欲综合视频天天天 | 亚洲精品国产精品国自产观看 | 98涩涩国产露脸精品国产网 | 制服丝袜一区二区 | 97成人在线观看 | 人人dvd | 国产91免费在线 | 天天舔夜夜操 | 青青河边草观看完整版高清 | 免费看污网站 | 成全在线视频免费观看 | v片在线播放| 日韩电影黄色 | 在线看黄色的网站 | 亚欧日韩av| 视频一区二区在线观看 | 狠狠久久综合 | 色综合久久综合 | 四虎永久网站 | 国色综合 | 国产自产高清不卡 | 欧美与欧洲交xxxx免费观看 | 日韩大片在线免费观看 | 韩国av免费看 | 亚洲精品99久久久久久 | 日日爱夜夜爱 | 91久久国产精品 | 亚洲色五月| 成人av中文字幕在线观看 | 国产资源网 | 色综合人人| 久久成人午夜视频 | 国产午夜精品久久 | 天堂av免费观看 | 丁香婷婷综合激情五月色 | 亚洲在线国产 | 91视频在线看 | 中文字幕高清 | 中国美女一级看片 | 韩国精品福利一区二区三区 | 国产专区视频 | 精品久久一区 | 奇米四色影狠狠爱7777 | 日韩黄视频 | 天天插天天狠天天透 | 色资源在线观看 | 黄色免费网站下载 | 婷婷色在线播放 | 97人人模人人爽人人少妇 | 一区二区三区精品在线 | 国产精品永久 | 成人av免费看 | 91视频在线网址 | 日本中文字幕网址 | 国产精品男女视频 | 久久精品久久精品久久 | www.人人干 | 亚洲精品美女在线观看播放 | 中文字幕一区二区三区精华液 | 久久国产二区 | 不卡的av| 国产精品a久久 | 五月婷婷一区二区三区 | 黄色小说免费在线观看 | 97av超碰| 天天看天天干天天操 | 国产美女免费观看 | 粉嫩av一区二区三区入口 | 一级做a视频 | 亚洲成人精品影院 | 午夜精品视频免费在线观看 | 久久综合之合合综合久久 | 91免费视频黄 | 国产精品破处视频 | 亚洲综合成人专区片 | 久久a国产 | 久久国产精品久久久久 | 美女视频是黄的免费观看 | 久久久国产精品一区二区三区 | 免费黄色av电影 | 国产日韩欧美视频在线观看 | 丁香婷婷色月天 | 天天干天天拍 | 色丁香色婷婷 | 国产精品视频线看 | 808电影 | 97在线观看免费高清完整版在线观看 | 91精品国自产在线观看欧美 | 蜜臀av性久久久久av蜜臀三区 | 国内偷拍精品视频 | 国产原创在线观看 | 欧美一级日韩三级 | 日韩精品一区二 | 操操操日日日干干干 | 狠狠色丁香婷婷综合视频 | 日韩欧美一区二区三区黑寡妇 | 成 人 黄 色 视频 免费观看 | 亚洲成年片 | 青春草免费在线视频 | 亚洲日韩欧美一区二区在线 | 91香蕉久久 | 国产色视频网站 | 黄色1级大片 | 人人澡人人舔 | 美女黄视频免费 | 精品久久一二三区 | 有码中文在线 | 91欧美国产 | 热久精品| 国内精品视频久久 | 成人一级电影在线观看 | 日韩精品一区二区三区视频播放 | 午夜视频一区二区三区 | 91在线免费看片 | 麻豆手机在线 | 欧美精品久久久久a | 国产精品人成电影在线观看 | 成人国产精品一区二区 | 夜又临在线观看 | 欧美日韩国产色综合一二三四 | 国产精品区在线观看 | 免费看黄的视频 | 香蕉视频国产在线 | 国产成人黄色在线 | 婷婷新五月 | 亚洲精品动漫成人3d无尽在线 | 韩国av免费 | 日韩欧美精品一区二区三区经典 | 国产999免费视频 | 日韩二区在线播放 | 久久影视网 | 欧美性生活免费 | 国产精品久久久久久一区二区三区 | 成年免费在线视频 | 久久久麻豆视频 | 国产手机在线精品 | 国产美女精品视频免费观看 | 草久电影 | 国产999精品久久久久久 | 国产精品视频线看 | 国产精品毛片一区二区 | 97国产一区二区 | 亚洲视频综合在线 | 日韩久久精品一区二区三区下载 | 免费人成网 | 91视频在线国产 | 亚洲v欧美v国产v在线观看 | 波多野结衣一区二区 | 亚洲夜夜综合 | 黄色影院在线播放 | 狠狠久久伊人 | 国产精品99爱 | 国产成人精品久久亚洲高清不卡 | 精品久久久久久电影 | 日韩午夜在线观看 | 国产精品第 | 在线看中文字幕 | sesese图片| 成人免费影院 | 成人作爱视频 | 亚洲最大成人免费网站 | 91视频国产高清 | 91漂亮少妇露脸在线播放 | 亚洲3级| 97精品电影院 | 天天摸天天干天天操天天射 | 午夜在线免费观看视频 | 久久网站av | 亚洲91中文字幕无线码三区 | 97在线视频免费观看 | 在线播放第一页 | www.av免费观看 | 免费欧美精品 | 国产精品免费在线播放 | 在线观看国产一区二区 | 狠狠色丁香久久婷婷综合五月 | 麻豆va一区二区三区久久浪 | 国产精品久免费的黄网站 | 国产精品久久久久久久久久久久午夜片 | 国产黄色看片 | 成人影片在线免费观看 | 欧美一区二区三区不卡 | 成人av av在线| 狠狠色丁香婷婷综合久久片 | 99久久久免费视频 | 日韩欧美高清在线 | 99久久精品免费看国产四区 | 久久激情视频 | 人人添人人澡 | 免费黄色av电影 | 五月天综合网 | 黄a在线观看| 久久私人影院 | 六月色丁 | 高清不卡一区二区在线 | 亚州精品在线视频 | 国产福利av | 日韩精品一区二区三区高清免费 | 最新黄色av网址 | 欧美日韩国产区 | 在线视频 区| 九九欧美| 91一区二区三区在线观看 | 亚洲国产中文字幕在线 | av高清一区二区三区 | 欧美另类高清 videos | 99精品国产一区二区三区不卡 | 亚洲精品人人 | 欧美亚洲国产日韩 | 美女视频黄,久久 | 久草国产在线观看 | 久久夜视频 | 一级性生活片 | 九九免费在线视频 | 国产亚洲精品xxoo | 免费看搞黄视频网站 | av在线电影网站 | 狠狠操操操 | 深爱激情五月综合 | 免费看黄视频 | 中文字幕在线有码 | www.成人久久 | 国产一区视频在线观看免费 | 日韩欧美在线视频一区二区 | av观看网站 | 精品欧美乱码久久久久久 | 97超碰在线播放 | 大型av综合网站 | 久热免费| 在线观看日韩 | 91九色porny蝌蚪主页 | 日本中文字幕观看 | 91视频在线播放视频 | 麻豆高清免费国产一区 | 午夜av色| 精品福利视频在线 | 69国产成人综合久久精品欧美 | 精品欧美一区二区在线观看 | 中文一二区 | 人成午夜视频 | 毛片无卡免费无播放器 | 91精品国产91| 免费在线观看日韩 | 久久久综合九色合综国产精品 | 欧美老少交 | 久久综合久久综合久久 | 日韩高清在线不卡 | 亚洲精品午夜视频 | 国内精品在线一区 | 久久精品综合视频 | 久久狠狠亚洲综合 | 精品久久国产一区 | 中文有码在线视频 | 五月激情天| 国产精品岛国久久久久久久久红粉 | 中日韩三级视频 | 久久精品国产一区二区 | 91香蕉视频 mp4 | 一级片免费观看视频 | 中文字幕999| 久久成人免费视频 | 亚州精品视频 | 国产精品久久久久久久久久白浆 | 亚洲国内在线 | 91网免费看 | 在线观看电影av | 天堂久色 | 天天干天天玩天天操 | 久草网视频在线观看 | 国产精成人品免费观看 | 人人爽人人澡 | 亚洲 中文字幕av | 成人av资源站 | 中中文字幕av | 狠狠干中文字幕 | av免费线看| 一区三区视频在线观看 | 国产在线国偷精品产拍 | 免费视频黄色 | 久久亚洲欧美日韩精品专区 | 精品9999 | 久久黄色影院 | 色偷偷97| 久久综合色综合88 | 日韩一区在线播放 | 很黄很黄的网站免费的 | 免费看片亚洲 | 香蕉在线影院 | 久久av网| 日韩二区三区在线观看 | 国产精品视频免费看 | 欧美日韩在线播放 | 亚洲精品在线视频播放 | 777久久久| 超碰个人在线 | 国产日韩欧美在线影视 | 在线免费观看黄色 | 亚洲不卡在线 | 婷婷国产视频 | 天天操夜夜摸 | 亚洲欧美国产精品va在线观看 | 91av在线免费播放 | 欧美日韩精品久久久 | 欧美日韩首页 | 亚洲精品中文字幕在线观看 | 久久久久福利视频 | 日韩精品中文字幕在线观看 | 夜夜嗨av色一区二区不卡 | 成人av片免费看 | 福利视频导航网址 | 97精品超碰一区二区三区 | 美女黄视频免费 | 婷婷综合久久 | 日日日日日 | 亚洲精品欧美专区 | 在线观影网站 | 美女黄视频免费看 | 中文av不卡 | 久草在线综合网 | 亚洲人成在线观看 | 国产自偷自拍 | 中文视频在线看 | 日韩在线视频网 | 国产欧美精品一区二区三区 | 中文字幕在线字幕中文 | 最新中文字幕在线播放 | 久久免费黄色网址 | 欧美久久九九 | 欧美日韩视频在线观看免费 | 精品久久久久久久久亚洲 | 国产永久免费高清在线观看视频 | 久久精品99国产国产精 | 日日夜夜网 | 伊人中文网| 国产日产欧美在线观看 | 国产手机免费视频 | 国产精品亚洲a | 欧美专区国产专区 | 成年人黄色在线观看 | 久久久久久国产精品免费 | 永久中文字幕 | 久久久黄视频 | 五月宗合网| 国产在线国偷精品产拍 | 精品一区二区三区四区在线 | 免费观看性生活大片 | 中文字幕第一页在线视频 | av国产在线观看 | 91香蕉视频色版 | 久久激情小说 | 欧美日韩在线观看一区二区三区 | aaa日本高清在线播放免费观看 | 日本黄色大片儿 | 久久艹欧美| 日韩国产高清在线 | 精品国产1区2区3区 国产欧美精品在线观看 | 日韩一区二区三免费高清在线观看 | 久久久久免费精品国产 | 狠狠干干| 免费观看www小视频的软件 | 欧美一区二区三区激情视频 | 亚洲人在线7777777精品 | 国产艹b视频 | 91香蕉视频污在线 | 欧美性性网| 又黄又爽又无遮挡免费的网站 | 国内精品久久久久影院日本资源 | 日韩精品免费一线在线观看 | 国产成人高清av | 激情欧美xxxx | 91精品黄色 | 国产成人精品综合久久久 | 99精品在线看 | 亚洲精品乱码久久久久久 | 日本精品中文字幕在线观看 | 91成人精品观看 | 九九在线视频 | 免费在线观看污 | 亚洲欧美日本一区二区三区 | 二区视频在线 | 亚洲成人黄色在线观看 | 久久久国产高清 | 国产精品va | 视频一区二区在线 | 在线成人免费电影 | 国产精品一区二区三区99 | avhd高清在线谜片 | 免费看一级片 | 精品在线99 | 成人国产精品av | 干干干操操操 | 精品国产aⅴ麻豆 | 青青草在久久免费久久免费 | 91插插插网站 | 国产成人精品一区二区在线观看 | 看片一区二区三区 | 久久亚洲人| 91视频-88av| 深爱五月网 | 天天碰天天操 | 国外调教视频网站 | 久久久精品影视 | 91一区一区三区 | 午夜视频免费播放 | 91av视频在线观看 | 色诱亚洲精品久久久久久 | 韩国一区在线 | 黄色电影网站在线观看 | 激情丁香月 | 婷婷.com| 天天色天天综合网 | 夜色在线资源 | 成人黄色短片 | 亚洲三级性片 | 国产在线欧美日韩 | 久久精品精品电影网 | 黄色软件在线看 | 成人 亚洲 欧美 | 日韩精品国产一区 | av在线播放一区二区三区 | 精品视频专区 | 久久99在线 |