1. 程式人生 > >hiho一下 第124周 #1421 : 四叉樹 【二維線段樹】

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:恩,我來實現一下試試!