範圍搜索 (Range Query)

範圍搜索

Author: Subhash Suri

譯者:Koala++ / 屈偉

       前一陣把搜索引擎的RangeQuery的邏輯重新寫了一遍,我寫的時候就感覺很不對勁,我們的搜索引擎採用的是一種非常怪異的實現,至少我沒在別的搜索引擎裏見過,或是在資料中看到過。我要解決的是二維座標查詢,比如你想知道你周圍五公里內的醫院在什麼地方,蠻力解決方法就是把所有醫院座標得到,把x座標循環過濾一遍,再把y座標循環過濾一遍。其實這還好,因爲一個城市一共也沒多少醫院,但如果調用方把座標查詢寫前面,也就是先過濾xy座標,再過濾醫院,那就悲劇了。

       簡單點的辦法就是把xy座標有序地保存,那用二分查找定位到x-2.5km, x+2.5km, y-2.5km, y+2.5km,然後取x-2.5kmx+2.5kmposting listy-2.5kmy+2.5kmposting listand操作就可以了。

       但是還能不能再快呢?這個問題我想了想也沒什麼頭緒,偶然發現了RTree這個數據結構,我感覺這纔是正道。

       下面是Range Searching的翻譯。原文地址http://www.cs.ucsb.edu/~suri/cs235/RangeSearching.pdf

Range Search

我們這裏討論Range Search是希望能找到一個好的數據結構,它能高效地對對象(點,矩形,多邊形)的集合進行範圍查詢。

 


 

我們要做的是根據對象的類型和查詢的類型,來尋找一個能在多種應用場景下使用的基本數據結構。

時間-空間的平衡:我們在預處理和存儲上耗費地越多,那我們就可以更快地完成一次查詢。

這裏主要考慮使用(近似)線性空間的數據結構。

Orthogonal Range Search

對於一個有n個點的集合P。它有2n個子集。對於一個幾何的查詢,它會有多少個可能的結果呢?

 


效率能提高的原因是在查詢結果中只會有子集中的一部分。

正交範圍搜索只處理與座標軸平行的矩形空間中的點集合。(譯註,也就是不能用查詢像圓形,五角形這樣的範圍)

接下來,我們先討論一維空間中的排序和搜索問題,然後將一維空間的數據結構推廣到多維空間。

1-Dimensional Search

    令一維空間的點集合爲P={p1, p2, ...,pn}

    查詢是一個區間:

 


 

    如果這個區間包含k個點,我們想在時間複雜度爲O(log n + k)的情況下解決這個問題。

    Hashing可以做到嗎?爲什麼不能?(譯註:Hash的查詢時間複雜度爲O(1+a),a是負載因子,hash在查詢時只能依次查找,它的時間複雜度是O(n+an))

    一個排序後的數組可以得到這個時間複雜度邊界,但是它無法推廣到多維空間。

    我們採用一種替代方案,用一個二叉平衡樹。

Tree Search

   


    在一個排序後的點(key)數組上建立一個平衡二叉樹。

    葉子結點對應的是點,中間結點是分支結點。

    給定一個區間[xl0,xhi],在樹上搜索xl0xhi

    搜索得到的兩個葉子結點中的葉子結點就是搜索的結果。

    樹搜索部分的時間複雜度是2log n,將葉子結點放入結果集的時間複雜度爲O(k),這裏假設葉子結點是被鏈在一起的。

Canonical subsets

    S1, S1... SkCanonical子集,Si屬於P。如果範圍查詢的結果可以寫成幾個Si的並集。(譯註:canonical子集的解釋http://en.wikipedia.org/wiki/Canonical,不必深究,就認爲是子集就行了)

    Canonical子集有可能會重疊。

    (Key)是用來確定正確的Si,和高效地確定對於一個給定的查詢,使用哪個Si

    在一維空間問題上,樹中每一個結點都有一個Canonical子集:Sv是以v的根結點的子樹的Canonical子集,它其中的元素是這個子樹所有根結點中的點。

 


1D Range Query

   


    給定查詢[xl0,xhi],查詢樹中滿足u>= xl0的最左葉子結點,和滿足v>= xhi的最左葉子結點。

    所有在uv之間的葉子結點都在所查詢範圍之內。

    如果u= xl0或是v= xhi,那麼u, vcanonical子集也包含在範圍中。

    [u,v)區間範圍內的所有其它點由區間中的最大子樹決定。(譯註:陰影中的子樹)

Query Processing

    z爲從rootu,v搜索路徑上的最後一個普通結點。

    先看從zu的最左邊路徑,當路徑向左時,將右子樹的canonical子集加入結果集。

    zv的右邊路徑中,當路徑向右時,將左子樹的canonical子集加入結果集。

 


Analysis

   


 

     因爲搜索路徑有O(log n)個結點,故有O(log n)canonical子集,所以可以在O(log n)時間內找到。

    得到結果集合,可以在線性時間內遍歷這些子樹,時間複雜度爲O(k)

    如果只需要得到範圍內點的數量,可以在每個結點中記錄canonical集合的元素個數。

    數據結構的空間複雜度爲O(n),它查詢一個範圍的時間複雜度爲O(log n)


Multi-Dimensional Data

    如何在高維空間中進行範圍搜索?

 


    kD-tree [Jon Bertley 1975]。它是k-dimensional tree的縮寫。

    它適用於一般的任意維空間。漸近搜索複雜度不是很好。

    通過在x-y座標上分裂這種方式來推廣一維的樹。在k維空間中,將會循環所有維座標。

KD-Trees

   


    一個二叉樹,每個結點有兩個值,所分裂的維,和分裂的值。

    如果分裂在x座標的s處,那麼左子樹的點都有x座標<=s,右子樹的點都有x座標>s。對y座標也是一樣。

    當只有O(1)個點剩餘時,將它們放到一個葉子結點。

    只有葉子結點中有點位置的信息,非葉子結點只用於分裂。

Splitting

    爲得到一個平衡樹,使用座標中位數來分裂——中位數本身放到左子樹或是右子樹都可以。

    使用中位數分裂,可以保證樹的高度爲O(log n)

    分裂可以使用循環所有維的方式,或是根據數據做出選擇,比如,可以選擇數據分佈最廣的維。

Space Partitioning View

    kD-tree是對空間的劃分,每個結點都會引入對x軸或是y軸的劃分。

    點被劃分成兩部分,這兩部分分別是左子樹和右子樹。

    這些劃分由矩形區域組成,稱爲單元(cell)(可能是沒有邊界的)

    根對應的是整個空間,隨後每個子結點都只繼承一半的空間。

    葉子對應的是終止單元。

    它是二分空間(Binary space Partitioning)的一種特殊情況。

construction

    它可以以遞歸方式在O(n log n)時間內建成。

    預先對x座標和y座標排序,並將兩個排序數組交叉鏈接起來。(譯註,即將每個點的x,y座標鏈接起來)

    比如,可以通過掃描x數組得到x座標的中位數,將數組分成兩部分。再用交叉鏈接將y數組在O(n)時間複雜度內分成兩部分。

    現在有兩個子問題,每個都有n/2的大小,並且它們都有自己的排序數據。遞歸。

    遞歸式爲T(n)=2T(n/2) + n,故它的時間複雜度爲T(n) = O(n log n)(譯註,可以參考算法導論4.3節的主方法)

Searching kD-Trees


    設查詢矩形爲R。從根結點開始查找。

    假設當前分裂的線是垂直的(也可以是水平的)。令v,w分別是左子結點和右子結點。

    如果v是一個葉子結點,返回cell(v)R,如果cell(v)屬於R,返回cell(v)中所有點,如果cell(v)R爲空集,跳過。

    w進行相同的處理。

    查詢過程明顯是沒有問題的。那麼它的時間複雜度是什麼呢?

Search complexity

    cell(v)屬於R,時間複雜度與cell(v)的大小成線性關係。

    結點v訪問的結點個數滿足由cell(v)R交集邊界的限定。

   如果cell(v)R區域之外,我們就不用去查找它,如果cell(v)R之內,我們就要枚舉v中的所有點。只有在cell(v)R局部重疊時纔會去遞歸調用。kD-Tree的高度是O(log n)

    l爲穿過R的一條直線。

    有多少個單元會與這條線相交?

    因爲分裂的維是從兩個維中選擇一個,所以思考的關鍵在於每次將樹的兩層一起考慮。

    假設第一次劃分是垂直的,第二次是水平的,我們就有四個單元,每一個有n/4個點。

    一條直接只與兩個單元相交。單元或是在R區域之內或是R區域之外。

    遞歸式爲:

 


 

    從遞歸式可以得到時間複雜度爲:Q(n)=O(n1/2)

    kD-Tree是一個有O(n)空間複雜度的數據結構,它可以在最壞時間O(n1/2+m)內完成二維空間範圍查找,其中m是輸出的大小。

d-Dim Search Complexity

更高維的時間複雜度是什麼呢?

先嚐試三維情況,再推廣。

遞歸式是:

Q(n)=2d-1Q(n/2d)+1

從它推出時間複雜度:

    Q(n)=O(n1-1/d)

    kD-Tree是一個有(nd)空間複雜度的數據結構,它在d維空間中查找範圍的最壞時間複雜度爲O(n1-1/d+m),其中m是輸出的大小。


Orthogonal Range Trees

   


 

    將一維查找樹推廣到d維空間。

    每次查找都會遞歸地分解到多個低維空間中去查找。

    查找時間複雜度爲O((log n)d+k),其中k是結果集大小。

    時間和空間複雜度是O(n(log n)d-1)

    分散層疊(fractional cascading)從查找時間中消除了一個log n因子。

    我們着眼於二維情況,但是它的思想是可以擴展到多維空間的。

2D Range Tree

    令二維空間中的一個點集合爲P={p1, p2, ...,pn}

    一個查詢的一般形式爲:R=[xlo,xhi]*[ylo,yhi]

    我們開始不考慮y座標,在P上建立一個一維的x區域樹。

 


    [xlo,xhi]區間的點集屬於O(log n)canonical集合。

    這是一個結果的超集。它明顯比|RP|要大,所以我們不能採用依次查看每個點在不在canonical集合中這種耗時的方式。

Level 2 Trees

    思想的核心是取得每個canonical集合的點,並在它們上面建立y區域樹。

    比如,canonical集合{9,12,14,15},用它的y座標建立了一個新的區域樹。

 


    我們查找每個O(log n) canonical集合需要查找x區域樹中的[xlo,xhi]範圍和用它們的y區域樹查找[ylo,yhi]範圍。

    y區域的查找中得到RP這個結果集。

Canonical Sets


analysis

    二維的查找時間複雜度爲O((log n)2)

1.    查找O(log n)canonical集合。

2.    每個集合進行y範圍查找需要O(log n)時間複雜性。

 


空間複雜度爲O(n log n)

1.    x區域樹中cononical集合的總大小是多少?

2.    非葉子結點個數=葉子結點個數。

3.    有一個大小爲n的集合,二個大小爲n/2的集合,以此類推。

4.    總共是O(n log n)

5.    每個大小爲mcanonical集合需要O(m)的空間來存儲y區域樹。

6.    所以,總的空間複雜度是O(n log n)

construction

   


    x區域樹可以在O(n log n)時間內建立。

    初看起來,因爲y樹的總大小是O(n log n),所以它需要O(n(log n)2)的時間來建立。

    但是通過自底向上建立它們,我們可以避免在每個結點上的排序時間。

    一旦子結點的y區域樹建立了,我們就可以將它們的y數組在線性時間內合併。

    建立一維區域樹在排序後的時間複雜度是線性的。

    所以,總的時間複雜度是O(n log n),是所有y區域樹的總大小。

d-Dim Range Trees

    多級區域樹的思想可以很自然地擴展到任意d維。

    在第一維上建立一個x區域樹。

    在樹中每個結點v上,在v結點的cononical集合上爲剩餘的(d-1)維建立一個(d-1)維的區域樹。

    查找時間複雜度每一維增加log n因子——每一維都會增加canonical集合總大小log n倍。

    所以查找時間爲O((log n)d)

    建樹的空間和時間複雜度是O(n(log n)d-1)


Fractional Cascading

    有一種方法可以提高區域樹的查找時間log n。二維查找可以在O(log n)時間內完成。

    基本思想:區域樹首先找到在[xlo,xhi]區間的點集,這些點集是由O(log n)canonical集合組成的。

    接下來,每個canonical集合用y區域樹查找[ylo,yhi]。我們先找到ylo的位置,然後一直讀到yhi邊界。

    我們接下來要做的是,首先在O(log n)時間內進行第一次查找,但是會利用查找的信息到另一種數據結構上更高效地查找。

    方法的關鍵是在canonical集合上加上一些指針。

Basic Idea

    我們通過一個簡單的例子來了解這種方法的基本思想。

    假設我們有兩個數組,A1A2,它們都是無序的。

    給定一個範圍[x,x’],我們想得到A1A2中所有在這個範圍中的元素。

    一個直觀的做法是用兩分查找找到A1A2k的位置,它的時間複雜度是O(2log n + k)

Fractional Cascading Idea

    假設有A2屬於A1,添加一些從A1指向A2的指針。

    如果A1[i]=yi,將一個指針指向A2中大於yi的最小元素。

    假設我們想得到範圍[y,y’]中的元素。

    A1中查找y,然後一直遍歷到y’,時間複雜度爲O(log n + k1)

    如果A1A1[i]位置找到y,可以使用它的指針開始在A2中的查找。這一步的時候複雜度爲O(1 + k2)

 


 

    例子所示爲查找[20,65]

FC in Range Trees

    元素的特點:S(l(v))S(r(v))canonical集合是S(v)canonical集合的子集。

    X區域樹還是和以前一樣。但在這裏不建立y區域樹,而是根據y座標將它們保存到排序數組裏。

    A(v)中的每個entry保存兩個指針,指向A(l(v))A(r(v))數組。

    如果A(v)[i]保存着點p,那麼指針指向A(l(v))中最小大於的y座標元素(y(p))。對r(v)也是一樣。

Range Tree FC

    下圖中給出了一部分指針的示例。

 


 


 

FC Search


    考慮一個區域R=[x,x’]*[y,y’]

    x區域樹中查找x, x’

    vsplit爲兩條查找路徑開分開的結點。

    O(log n)canonical子集對應vsplit下的子結點,它們是在查找x(x’)路徑向左()時的右()子結點。

    vsplit結點,在A(vsplit)中二分查找y,時間複雜度爲O(log n)

    用我們x區域樹查找x,x’的方式向下查找。從entries的指向得到指向子結點中>=y的位置,這個操作的時間複雜度爲O(1)

    A(v)O(log n)canonical結點的一個,現在要在A(v)中查找[y,y’]區域內的元素。

    我們只需要找到A(v)>=y的最小entry

    我們可以在O(1)時間複雜度內找到是因爲parent(v)在查找路徑上,並且我們通過v數組中的指針,知道最小>=yentryA(parent(v))中的位置,

    所以我們可以將在A(v)中在[y,y’]中的所有點在O(1 + kv)時間複雜度內輸出,其中kv是結果集大小。

    對二維範圍搜索,最終的時間複雜度爲O(log n + k),空間複雜度爲O(n log n)

    d維範圍搜索使用分散層疊的時間複雜度爲O((log n)d-1)

轉發至微博


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