hiho一下 第124周 #1421 : 四叉樹 【二維線段樹】
小Ho:樸素的想法是我用一個二維陣列來把整個平面圖表示出來。假設座標的範圍是L,那麼就需要一個L*L的陣列。
對於(a,b)和r,我就檢查a-r到a+r行的b-r列到b+r列,看其中是否存在有點,並且點到(a,b)的距離是小於等於r的。
對於L超過10000的情況就沒有辦法實現了。
小Hi:沒錯,在座標範圍和點數都很大的情況下,確實會有這樣的問題。
小Ho:我在想能不能把整個區域分割成若干的小區域,每次都在附近的小區域去找臨近的點呢。
小Hi:小Ho你這個想法很棒,我們不妨來試試吧?
小Ho:那應該怎麼分割呢?
小Hi:你還記得線段樹麼?線上段樹的處理中,我們將一個區間從中點分成兩段。這裡我們也用同樣的方法,將一個區域分割為4塊好了:
從上到下,從左到右,分別標記為1234。
我們將所有的點放進這些區域中,為了讓一個區域中點數不過多,我們設定一個區域的點數上限。
若一個區域的點數過多,我們就將這個區域四分,把新的點放到子區域中去。
小Ho:聽上去好像很有道理。
小Hi:當然有道理了,這種資料結構叫做"四叉樹(Quadtree)"。其每個基本單元為:
QuadtreeNode: const NODE_CAPACITY; // 每個節點包含的點數限制,常量 boundary; // 該節點的範圍,包含4個引數,區域的上下左右邊界 points; // 該區域內節點的列表 childNode; // 包含4個引數,分別表示4個子區域
假設NODE_CAPACITY=1,那麼我們可以把整個區域分割為:
小Ho:恩,這個我理解了,因為跟線段樹差不多,那麼也就是同樣存在插入和查詢操作了?
小Hi:沒錯。
四叉樹的插入操作:將新的節點(x,y)插入時,若不在當前區域內,退出;否則將其加入該區域的節點列表points,若當前區域的節點列表已經滿了。那麼將該區域進行四分,同時將節點加入子區域中。
insert(QuadtreeNode nowNode, point p): If (p not in nowNode.boundary) Then Return End If If (nowNode.points.length < NODE_CAPACITY) Then nowNode.points.append(p) Else nowNode.divide() // 將區域四分 For each childNode of nowNode insert(childNode, p) End For End If
四叉樹的查詢操作一般是求一個範圍內的點,因此帶入的引數也是一個區域range:
query(QuadtreeNode nowNode, range): If (QuadtreeNode.boundary does not intersect range) Then //該節點的區域與查詢區域不相交 Return empty End If For each p in nowNode.points If (p in range) Then pointsInRange.append(p) End For End For If (nowNode.isDivide) Then // 如果該區域有分割過,那麼子區域中的節點也有可能在其中 For each childNode of nowNode query(childNode, range) End For End If Return pointsInRange
在我們這次的問題中,我們可以用圓的外接正方形作為區域來求得點的列表,再以此檢查是否在圓內。
小Ho:這樣看上去的確搜尋量變少了。
小Hi:沒錯,而且動態建立四叉樹的過程也減少了空間的開銷。
小Ho:恩,我來實現一下試試!