希尔伯特曲线的绘制c语言,用四叉树和希尔伯特曲线做空间索引
《用四叉樹和希爾伯特曲線做空間索引》由會員分享,可在線閱讀,更多相關(guān)《用四叉樹和希爾伯特曲線做空間索引(11頁珍藏版)》請在人人文庫網(wǎng)上搜索。
1、超酷算法:用四叉樹和希爾伯特曲線做空間索引閱讀四叉樹,希爾伯特曲線,空間索引,算法Avalon探索之旅基礎(chǔ)教程- 簡單綁定Gopher China 2015 上海大會Android必學(xué)-異步加載Android必學(xué)-BaseAdapter的使用與優(yōu)化本文由伯樂在線-demoZ翻譯,黃利民校稿。未經(jīng)許可,禁止轉(zhuǎn)載!英文出處:blog.notdot.net。歡迎加入翻譯組。隨著越來越多的數(shù)據(jù)和應(yīng)用和地理空間相關(guān),空間索引變得愈加重要。然而,有效地查詢地理空間數(shù)據(jù)是相當(dāng)大的挑戰(zhàn),因?yàn)閿?shù)據(jù)是二維的(有時候更高),不能用標(biāo)準(zhǔn)的索引技術(shù)來查詢位置。空間索引通過各種各樣的技術(shù)來解決這個問題。在這篇博文中,我將。
2、介紹幾種:四叉樹,geohash(不要和geohashing混淆)以及空間填充曲線,并揭示它們是怎樣相互關(guān)聯(lián)的。四叉樹四叉樹是種很直接的空間索引技術(shù)。在四叉樹中,每個節(jié)點(diǎn)表示覆蓋了部分進(jìn)行索引的空間的邊界框,根節(jié)點(diǎn)覆蓋了整個區(qū)域。每個節(jié)點(diǎn)要么是葉節(jié)點(diǎn),有包含一個或多個索引點(diǎn)的列表,沒有孩子。要么是內(nèi)部節(jié)點(diǎn),有四個孩子,每個孩子對應(yīng)將區(qū)域沿兩根軸對半分得到的四個象限中的一個,四叉樹也因此得名。圖1 展示四叉樹是怎樣劃分索引區(qū)域的 來源:維基百科將數(shù)據(jù)插入四叉樹很簡單:從根節(jié)點(diǎn)開始,判斷你的數(shù)據(jù)點(diǎn)屬于哪個象限。遞歸到相應(yīng)的節(jié)點(diǎn),重復(fù)步驟,直到到達(dá)葉節(jié)點(diǎn),然后將該點(diǎn)加入節(jié)點(diǎn)的索引點(diǎn)列表中。如果列表中。
3、的元素個數(shù)超出了預(yù)設(shè)的最大數(shù)目,則將節(jié)點(diǎn)分裂,將其中的索引點(diǎn)移動到相應(yīng)的子節(jié)點(diǎn)中去。圖2 四叉樹的內(nèi)部結(jié)構(gòu)查詢四叉樹時從根節(jié)點(diǎn)開始,檢查每個子節(jié)點(diǎn)看是否與查詢的區(qū)域相交。如果是,則遞歸進(jìn)入該子節(jié)點(diǎn)。當(dāng)?shù)竭_(dá)葉節(jié)點(diǎn)時,檢查點(diǎn)列表中的每一個項看是否與查詢區(qū)域相交,如果是則返回此項。注意四叉樹是非常規(guī)則的,事實(shí)上它是一種字典樹,因?yàn)闃涔?jié)點(diǎn)的值不依賴于插入的數(shù)據(jù)。因此我們可以用直接的方式給節(jié)點(diǎn)編號:用二進(jìn)制給每個象限編號(左上是00,右上是10等等譯者注:第一個比特位為0表示在左半平面,為1在右半平面。第二個比特位為0表示在上半平面,為1在下半平面),任一節(jié)點(diǎn)的編號是由從根開始,它的各祖先的象限號碼串接。
4、而成的。在這個編號系統(tǒng)中,圖2中右下角節(jié)點(diǎn)的編號是1101。如果我們定義了樹的最大深度,不需通過樹就可以計算數(shù)據(jù)點(diǎn)所在節(jié)點(diǎn)的編號:只要把節(jié)點(diǎn)的坐標(biāo)標(biāo)準(zhǔn)化到適當(dāng)?shù)恼麛?shù)區(qū)間中(比如32位整數(shù)),然后把轉(zhuǎn)化后x, y坐標(biāo)的比特位交錯組合。每對比特指定了假想的四叉樹中的一個象限。(譯者注:不了解的讀者可看看Z-order,它和下文的希爾伯特曲線都是將二維的點(diǎn)映射到一維的方法)Geohash上述編號系統(tǒng)可能看起來有些熟悉,沒錯,就是geohash!此刻,你可以把四叉樹扔掉了。節(jié)點(diǎn)編號,或者說geohash,包含了對于節(jié)點(diǎn)在樹中位置我們需要的全部信息。全高樹中的每個葉節(jié)點(diǎn)是個完整的geohash,每個內(nèi)部。
5、節(jié)點(diǎn)代表從它最小的葉節(jié)點(diǎn)到最大的葉節(jié)點(diǎn)的區(qū)間。因此,通過查詢所需的節(jié)點(diǎn)覆蓋的數(shù)值區(qū)間中的一切(在geohash上索引),你可以有效地定位任意內(nèi)部節(jié)點(diǎn)下的所有數(shù)據(jù)點(diǎn)。一旦我們丟掉了四叉樹,查詢就變得復(fù)雜一點(diǎn)了。我們需要事先構(gòu)建搜索集合而不是在樹中遞歸地精煉搜索集合。首先,找到完全覆蓋查詢區(qū)域的最小前綴(或者說四叉樹節(jié)點(diǎn)譯者注:注意在我們的編號系統(tǒng)中節(jié)點(diǎn)由比特串表示)。在最壞情況下,這可能遠(yuǎn)大于實(shí)際的查詢區(qū)域,比如對于在索引區(qū)域中心、和四個象限都相交的小塊地方,查詢將要從根節(jié)點(diǎn)開始。現(xiàn)在的目標(biāo)是構(gòu)建一組完全包含查詢區(qū)域的前綴,并且盡可能少包含區(qū)域外的部分。如果沒有其他約束,我們可以簡單地選擇與查詢。
6、區(qū)域相交的葉節(jié)點(diǎn),但這會造成大量的查詢。所以要加一個約束:使得要查詢的不同區(qū)間最少。一種達(dá)到這個目的的方法是先設(shè)置我們愿意承受的查詢區(qū)間的最大數(shù)目。構(gòu)建一組區(qū)間,最開始都設(shè)為我們之前指定的前綴。從中選擇可以再分裂而不超出最大區(qū)間數(shù)并將從查詢區(qū)域刪除最不受歡迎區(qū)域的節(jié)點(diǎn)。重復(fù)這個過程直到集合中再沒有區(qū)間可以細(xì)分。最后,檢查得到的集合,如果可能的話合并相鄰的區(qū)間。下面的圖說明了這對于查詢一個圓形區(qū)域且限制最大5個查詢區(qū)間是如何工作的。圖3 一個對區(qū)域的查詢是怎樣分解成一連串geohash前綴/區(qū)間的這個方法工作地很好,它使我們避免了遞歸查找。我們執(zhí)行的一整套區(qū)間查找都可以并行完成。由于每次查找都預(yù)。
7、期要一次硬盤搜索,將查詢并行化大大減少了返回結(jié)果需要的時間。然而,我們還可以做得更好。你可能注意到上圖中我們要查詢的所有區(qū)域都是相鄰的,但我們卻只能將其中兩個合并(選擇區(qū)域的右下角的兩個)成一個單獨(dú)的查詢,進(jìn)而只要4次單獨(dú)查詢。(譯者注:這兩個區(qū)域可以合并是因?yàn)樗鼈冊趃eohash以Z字形遍歷區(qū)域的路徑上是相鄰的)這個后果部分是由于geohash訪問子區(qū)域的順序,在每個象限中從左到右,從上到下。從右上角象限到左下角象限的不連續(xù)性使得我們不得不將本可以使之連續(xù)的區(qū)間分裂。如果以不同的順序訪問區(qū)域,可能我們就可以最小化或者消除這些不連續(xù)性,使得更多的區(qū)域可以被看做是相鄰的,一次查詢就可得到結(jié)果。通。
8、過這樣效率上的提升,對于同樣的覆蓋區(qū)域,我們可以做更少的查詢,或者相反地,同樣的查詢次數(shù)的情況下包含更少的無關(guān)區(qū)域。圖4 geohash訪問象限的順序希爾伯特曲線現(xiàn)在假設(shè)我們以U字形來訪問區(qū)域。在每個象限中,我們同樣以U字形來訪問子象限,但是要調(diào)整好U字形的朝向使得和相鄰的象限銜接起來。如果我們正確地組織了這些U字形的朝向,我們就能完全消除不連續(xù)性,不管我們選擇了什么分辨率,都能連續(xù)地訪問整個區(qū)域,可以在完全地探訪了一個區(qū)域后才移動到下一個。這個方案不僅消除了不連續(xù)性,而且提高了總體的局域性。按照這個方案得到的圖案看起來有些熟悉,沒錯,就是希爾伯特曲線。希爾伯特曲線屬于一類被稱為空間填充曲線的。
9、一維分形,因?yàn)樗鼈冸m然是一維的線,卻可以填充固定區(qū)域的所有空間。它們相當(dāng)有名,部分是由于XKCD把它們用于互聯(lián)網(wǎng)地圖。如你所見,對于空間索引它們也是有用的,因?yàn)樗鼈冋宫F(xiàn)的正是我們需要的局域性和連續(xù)性。再看看之前用一組查詢來覆蓋圓的例子,我們發(fā)現(xiàn)(應(yīng)用希爾伯特曲線)還可以減少一次查詢:左下方的小區(qū)域現(xiàn)在和它右邊的區(qū)域連起來了(減少一次),雖然底部的兩塊區(qū)域不再連續(xù)了(增加一次),右下角的區(qū)域現(xiàn)在卻和它上方的連續(xù)了(減少一次)。圖5 希爾伯特曲線訪問象限的順序到目前為止,我們優(yōu)雅的系統(tǒng)還缺一樣?xùn)|西:將(x,y)坐標(biāo)轉(zhuǎn)換為希爾伯特曲線上相應(yīng)位置的方法。對于geohash,這是簡單而明顯的只需將x, 。
10、y坐標(biāo)交錯,但沒有明顯的方法修改這個方案使之對希爾伯特曲線也適用。在網(wǎng)上搜索,你很可能遇到很多關(guān)于希爾伯特曲線是怎樣畫出來的描述,但很少有關(guān)于找到任意點(diǎn)(在曲線上)位置的。為了搞定它,我們需要更仔細(xì)看看希爾伯特曲線是怎么遞歸構(gòu)建的。首先要注意到雖然大多數(shù)關(guān)于希爾伯特曲線的文獻(xiàn)都關(guān)注曲線是怎么畫出來的,卻容易讓我們忽略曲線的本質(zhì)屬性以及其重要性:曲線規(guī)定了平面上點(diǎn)的順序。如果我們用這順序來表達(dá)希爾伯特曲線,畫曲線就不值一提了:僅僅是把點(diǎn)連起來。忘記怎么把子曲線連起來吧,把注意力集中在怎么遞歸地列舉點(diǎn)上。圖6 希爾伯特曲線規(guī)定了二維平面上的點(diǎn)的順序在根這一層,列舉點(diǎn)很簡單:選定一個方向和一個起始點(diǎn)。
11、,環(huán)繞四個象限,用0到3給他們編號。當(dāng)我們要確定訪問子象限的順序同時維護(hù)總體的鄰接屬性,困難就來了。通過檢查我們發(fā)現(xiàn),子象限的曲線是原曲線的簡單變換,而且只有四種變換。自然地,這個結(jié)論也適用于子子象限,等等。對于一個給定的象限,我們在其中畫出的曲線是由象限所在大的方形的曲線以及該象限的位置決定的。只需要費(fèi)一點(diǎn)力,我們就能構(gòu)建出如下概況所有情況的表。圖7假設(shè)我們想用這個表來確定某個點(diǎn)在第三層希爾伯特曲線上的位置。在這個例子中,假設(shè)點(diǎn)的坐標(biāo)是(5,2)。(譯者注:請參照圖8)從上圖的第一個方形開始,找到你的點(diǎn)所在的象限。在這個例子中,是在右上方的象限。那么點(diǎn)在希爾伯特曲線上的位置的第一部分是3(二。
12、進(jìn)制是11)。接著我們進(jìn)入象限3里面的方塊,在這個例子中,它是(圖7中的)第二個方塊。重復(fù)剛才的過程:我們的點(diǎn)落在哪個子象限?這次是左下角,意味著位置的下一部分是1(二進(jìn)制01),我們將進(jìn)入的小方塊又是第二個。最后一次重復(fù)這個過程,發(fā)現(xiàn)點(diǎn)落在右上角的子子象限,因此位置的最后部分是3(二進(jìn)制11)。把這些位置連接起來,我們得到點(diǎn)在曲線上的位置是二進(jìn)制的,或者十進(jìn)制的55。圖8 三階希爾伯特曲線讓我們更系統(tǒng)一些,寫出從x, y坐標(biāo)到希爾伯特曲線位置轉(zhuǎn)換的方法。首先,我們要以計算機(jī)看得懂的形式表達(dá)圖7:12345hilbert_map = a: (0, 0): (0, d), (0, 1): (1,。
13、 a), (1, 0): (3, b), (1, 1): (2, a), b: (0, 0): (2, b), (0, 1): (1, b), (1, 0): (3, a), (1, 1): (0, c), c: (0, 0): (2, c), (0, 1): (3, d), (1, 0): (1, c), (1, 1): (0, b), d: (0, 0): (0, a), (0, 1): (3, c), (1, 0): (1, d), (1, 1): (2, d)上面的代碼中,每個hilbert_map的元素對應(yīng)圖7四個方形中的一個。為了容易區(qū)分,我用一個字母來標(biāo)識每個方塊:a是第一個方塊。
14、,b是第二個,等等。每個方塊的值是個字典,將(子)象限的x, y坐標(biāo)映射到曲線上的位置(元組值的第一部分)以及下一個用到的方塊(元組值的第二部分)。下面的代碼展示了怎么用這個來將x, y坐標(biāo)轉(zhuǎn)換成希爾伯特曲線上的位置:12345678910def point_to_hilbert(x, y, order=16):current_square = aposition = 0for i in range(order - 1, -1, -1):position point_to_hilbert(5,2,3)55對了!為了進(jìn)一步測試,我們可以用這個函數(shù)生成一條希爾伯特曲線的有序點(diǎn)的完整列表,然后用電子。
15、制表軟件把它們畫出來看我們是否真的得到了一條希爾伯特曲線。在Python交互解釋器中輸入如下代碼:123 points = (x, y) for x in range(8) for y in range(8) sorted_points = sorted(points, key=lambda k: point_to_hilbert(k0, k1, 3) print n.join(%s,%s % x for x in sorted_points)將輸出的文本粘貼到文件中,保存為hilbert.csv,用你最喜歡的電子制表軟件打開,將數(shù)據(jù)畫成一個散點(diǎn)圖。結(jié)果當(dāng)然是一條漂亮的希爾伯特曲線!將hilbert_map做簡單的反轉(zhuǎn)就能實(shí)現(xiàn)point_to_hilbert的逆向功能(將希爾伯特曲線上的位置轉(zhuǎn)換為x, y坐標(biāo)),把這個留給讀者作為練習(xí)吧。結(jié)論空間索引,從四叉樹到geohash到希爾伯特曲線,到這就結(jié)束了。最后一點(diǎn)說明:如果你將一條希爾伯特曲線上的x, y坐標(biāo)的有序序列寫成二進(jìn)制形式,對于順序你注意到什么有趣的東西嗎?你想到了什么?結(jié)束前的一點(diǎn)警告:我在這里描述的全部索引方法都只適用于索引點(diǎn)。如果你想索引線、折線或者多邊形,這些方法可能就不管用了。據(jù)我所知,已知的唯一能有效索引形體的算法是R-tree,這是一種完全不同且更復(fù)雜的方法。
總結(jié)
以上是生活随笔為你收集整理的希尔伯特曲线的绘制c语言,用四叉树和希尔伯特曲线做空间索引的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言快速排序案例,什么是快速排序?C语
- 下一篇: 内存编程 c语言 c,C语言编程入门之内