空間數據索引RTree完全解析及Java實現

版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/MongChia1993/article/details/69941783
  • 第一部分 空間數據的背景介紹
    • 空間數據的建模
      • 基于實體的模型(基于對象)Entity-based models (or object based)
    • 常用的空間數據查詢方式
    • 空間數據獲取的方法
    • R樹
      • 簡介
      • R樹的數據結構
      • 一個更具體的使用場景
      • 一棵R樹滿足如下的性質:
      • 結點的結構
    • R樹的操作
      • 搜索
      • 插入
        • 插入操作存在的情況
        • 如何合理地分裂到兩個組
      • 刪除
        • 另一個例子再次理解刪除
  • 第二部分 R樹的Java實現
    • UML

第一部分 空間數據的背景介紹

空間數據的建模

基于實體的模型(基于對象)Entity-based models (or object based)

  • 0-dimensional objects?: 一般使用點point來表示那些對于不需要使用到形狀信息的實體。
  • 1-dimensional objects or linear objects: 用于表示一些路網的邊,一般用于表示道路road。 (polyline)
  • 2-dimensional objects or surfacic objects: 用于表示有區域面積的實體。 (polygon)

常用的空間數據查詢方式

  • 窗口查詢:給定一個查詢窗口(通常是一個矩形),返回與查詢窗口相重疊的物體。

點查詢:給定一個點,返回包含這個點的所有幾何圖形。

空間數據獲取的方法

  • 通常,我們不選擇去索引幾何物體本身,而是采用最小限定箱MBB(minimum bounding box ) 作為不規則幾何圖形的key來構建空間索引。

    • 在二維的情況下,我們稱之為最小限定矩形。MBR(minimum bounding retangle)

    • 三維的情況下,我們稱最新限定箱MBB(minimum bounding box)

  • 通過索引操作對象的MBB來進行查詢一共分為兩步

    • Filtering: 過濾掉MBB不相交的數據集,剩下的MBB被索引到的稱為一個數據的超集。

    • Refinement: 測試實際的幾何形狀會不會滿足查詢條件,精確化。

    • 如何用數據表示一個MBR

      通常,我們只需要兩個點就可限定一個矩形,也就是矩形某個對角線的兩個點就可以決定一個唯一的矩形。通常我們使用(左下,右上兩個點表示)或者使用右上左下,都是可以的。

表示一個點的數據:

  1. public class Point{ //用一個類來表示一個點
  2. public Float x;
  3. public Float y
  4. }

表示一個MBR的數據

  1. public class MBR{
  2. public Point BottomLeft;
  3. public Point TopRight;
  4. }
  • 如何判斷兩個MBR是否相交? >如果一個MBR的TopLeft或者BottomRight的(x,y)位于另一個MBR的xRange和yRangle里面,則說明這兩個MBR相交。

-c

R樹

對于B/B+-Trees 由于它的線性特點,通常用來索引一維數據。(比它大的往一邊走,比它小的往一邊走,但只是在一個維度下進行比較)。
B樹是一棵平衡樹,它是把一維直線分為若干段線段,當我們查找滿足某個要求的點的時候,只要去查找它所屬的線段即可。這種思想其實就是先找一個大的空間,再逐步縮小所要查找的空間,最終在一個自己設定的最小不可分空間內找出滿足要求的解。一個典型的B樹查找如下:

-c
要查找某一滿足條件的點,先去找到滿足條件的線段,然后遍歷所在線段上的點,即可找到答案。B樹是一種相對來說比較復雜的數據結構,尤其是在它的刪除與插入操作過程中,因為它涉及到了葉子結點的分解與合并。

簡介

B樹是解決低緯度數據(通常一維,也就是一個數據維度上進行比較),R樹很好的解決了這種高維空間搜索問題。它把B樹的思想很好的擴展到了多維空間,采用了B樹分割空間的思想(如果B樹在一維的線段進行分割,R樹就是在二維甚至多維度的空間),并在添加、刪除操作時采用合并、分解結點的方法,保證樹的平衡性。因此,R樹就是一棵用來存儲高維數據的平衡樹。

我們說過,B樹是采用切分線段來縮小數據查詢范圍的一種思想,我們又說了,R樹是b樹的多維版,以及R樹也采用了B樹的這一種分割的思想,那么,如果說線段的分割是一維的分割。那二維的分割就應該是區域的分割,而三維的就是幾何空間的分割了。要注意的是R樹并不只是二維空間數據的索引而已,它還可以索引三維甚至更高維。

一個三維的R樹

此外R樹還可以退化成一維,但是分割的線段存在重疊問題,效果不如Btree。

R樹的數據結構

如上所述,R樹是B樹在高維空間的擴展,是一棵平衡樹。每個R樹的葉子結點包含了多個指向不同數據的指針,這些數據可以是存放在硬盤中的,也可以是存在內存中。

根據R樹的這種數據結構,當我們需要進行一個高維空間查詢時,我們只需要遍歷少數幾個葉子結點所包含的指針(即縮小到某個區域下去進行查詢,還是采用縮小范圍的思想),查看這些指針指向的數據是否滿足要求即可。這種方式使我們不必遍歷所有數據即可獲得答案,效率顯著提高。下圖1是R樹的一個簡單實例:

解釋一下這張圖。

  • 首先我們假設所有數據都是二維空間下的幾何形狀,圖中僅僅標志了R8,R9,R10區域中的數據,其他的葉子節點僅僅用MBB表示。為了實現R樹結構,我們用一個最小邊界矩形恰好框住這個不規則區域,這樣,我們就構造出了一個區域:R8。R8的特點很明顯,就是正正好好框住所有在此區域中的數據。其他實線包圍住的區域,如R9,R10,R11等都是同樣的道理。這樣一來,我們一共得到了12個最最基本的最小矩形。這些矩形都將被存儲在子結點中。
  • 下一步操作就是進行高一層次的處理。我們發現R8,R9,R10三個矩形距離最為靠近,因此就可以用一個更大的矩形R3恰好框住這3個矩形。
  • 同樣道理,R15,R16被R6恰好框住,R11,R12被R4恰好框住,等等。所有最基本的最小邊界矩形被框入更大的矩形中之后,再次迭代,用更大的框去框住這些矩形。

用地圖的例子來解釋,就是所有的數據都是餐廳所對應的地點,先把相鄰的餐廳劃分到同一塊區域,劃分好所有餐廳之后,再把鄰近的區域劃分到更大的區域,劃分完畢后再次進行更高層次的劃分,直到劃分到只剩下兩個最大的區域為止。要查找的時候就方便了。

下面就可以把這些大大小小的矩形存入我們的R樹中去了。根結點存放的是兩個最大的矩形,這兩個最大的矩形框住了所有的剩余的矩形,當然也就框住了所有的數據。下一層的結點存放了次大的矩形,這些矩形縮小了范圍。每個葉子結點都是存放的最小的矩形,這些矩形中可能包含有n個數據。

以餐廳為例,假設我要查詢廣州市天河區天河城附近一公里的所有餐廳地址怎么辦?

  • 打開地圖(也就是整個R樹),先選擇國內還是國外(也就是根結點)。
  • 然后選擇華南地區(對應第一層結點),選擇廣州市(對應第二層結點),
  • 再選擇天河區(對應第三層結點),
  • 最后選擇天河城所在的那個區域(對應葉子結點,存放有最小矩形),遍歷所有在此區域內的結點,看是否滿足我們的要求即可。

R樹的查找規則跟查地圖很像吧?對應下圖:

一個更具體的使用場景

假設我們有一個地圖路網要進行道路的快速索引,那么我們可以將每一條路的最小MBB作為R樹的數據單元來進行構建R樹。

每一條路使用一個最小MBB來進行包裹,使它成為R樹的葉子結點(也就是那些數據結點)

(這里采用的是R樹的改進版本R*樹)然后對于建立起來的R樹在進行查找道路的使用就可以使用我們那種“縮小范圍”的查找思想。從上往下一層一層查找。

一棵R樹滿足如下的性質:

  • 1. 除非它是根結點之外,所有葉子結點包含有m至M個記錄索引(條目)。作為根結點的葉子結點所具有的記錄個數可以少于m。通常,m=M/2。
  • 2. 對于所有在葉子中存儲的記錄(條目),I是最小的可以在空間中完全覆蓋這些記錄所代表的點的矩形(注意:此處所說的“矩形”是可以擴展到高維空間的)。
  • 3. 每一個非葉子結點擁有m至M個孩子結點,除非它是根結點。
  • 4. 對于在非葉子結點上的每一個條目,i是最小的可以在空間上完全覆蓋這些條目所代表的點的矩形(同性質2)。
  • 5. 所有葉子結點都位于同一層,因此R樹為平衡樹。

結點的結構

先來探究一下葉子結點的結構。葉子結點所保存的數據形式為:(I, tuple-identifier)

其中,tuple-identifier表示的是一個存放于數據庫中的tuple,也就是一條記錄,它是n維的。I是一個n維空間的矩形,并可以恰好框住這個葉子結點中所有記錄代表的n維空間中的點。I=(I0,I1,…,In-1)。其結構如下圖所示:

Rectangle代表可以包裹E1,E2,E3,E4.E5的最小限度框。

R樹的操作

搜索

R樹的搜索操作很簡單,跟B樹上的搜索十分相似。它返回的結果是所有符合查找信息的記錄條目。而輸入是什么?輸入不僅僅是一個范圍了,它更可以看成是一個空間中的矩形。也就是說,我們輸入的是一個搜索矩形。
先給出偽代碼:

Function:Search
描述:假設T為一棵R樹的根結點,查找所有搜索矩形S覆蓋的記錄條目。

  • S1:[查找子樹] 如果T是非葉子結點,如果T所對應的矩形與S有重合,那么檢查所有T中存儲的條目,對于所有這些條目,使用Search操作作用在每一個條目所指向的子樹的根結點上(即T結點的孩子結點)。
  • S2:[查找葉子結點] 如果T是葉子結點,如果T所對應的矩形與S有重合,那么直接檢查S所指向的所有記錄條目。返回符合條件的記錄。

我們通過下圖來理解這個Search操作。

紅色查詢區域與P3子樹P4子樹相重疊,所以根據“縮小空間”的思想,只需要遍歷P3和P4所在子樹就行而無需遍歷P1,P2.

插入

插入操作存在的情況

R樹的插入操作也同B樹的插入操作類似。當新的數據記錄需要被添加入葉子結點時,若葉子結點溢出,那么我們需要對葉子結點進行分裂操作。顯然,葉子結點的插入操作會比搜索操作要復雜。插入操作需要一些輔助方法才能夠完成。
來看一下偽代碼:

【Function:Insert】
描述:將新的記錄條目E插入給定的R樹中。

  • I1:[為新記錄找到合適插入的葉子結點]開始ChooseLeaf方法選擇葉子結點L以放置記錄E。
  • I2:[添加新記錄至葉子結點] 如果L有足夠的空間來放置新的記錄條目,則向L中添加E。如果沒有足夠的空間,則進行SplitNode方法以獲得兩個結點L與LL,這兩個結點包含了所有原來葉子結點L中的條目與新條目E。
  • I3:[將變換向上傳遞] 開始對結點L進行AdjustTree操作,如果進行了分裂操作,那么同時需要對LL進行AdjustTree操作。
  • I4:[對樹進行增高操作] 如果結點分裂,且該分裂向上傳播導致了根結點的分裂,那么需要創建一個新的根結點,并且讓它的兩個孩子結點分別為原來那個根結點分裂后的兩個結點。

?

【Function:ChooseLeaf】
描述:選擇葉子結點以放置新條目E。
- CL1:[Initialize]設置N為根結點。
- CL2:[葉子結點的檢查] 如果N為葉子結點,則直接返回N。
- CL3:[選擇子樹] 如果N不是葉子結點,則遍歷N中的結點,找出添加E.I時擴張最小的結點,并把該結點定義為F。如果有多個這樣的結點,那么選擇面積最小的結點。
- CL4:[下降至葉子結點] 將N設為F,從CL2開始重復操作。


【Function:AdjustTree】
描述:葉子結點的改變向上傳遞至根結點以改變各個矩陣。在傳遞變換的過程中可能會產生結點的分裂。

  • AT1:[初始化] 將N設為L。
  • AT2:[檢驗是否完成] 如果N為根結點,則停止操作。
  • AT3:[調整父結點條目的最小邊界矩形] 設P為N的父節點,EN為指向在父節點P中指向N的條目。調整EN.I以保證所有在N中的矩形都被恰好包圍。
  • AT4:[向上傳遞結點分裂] 如果N有一個剛剛被分裂產生的結點NN,則創建一個指向NN的條目ENN。如果P有空間來存放ENN,則將ENN添加到P中。如果沒有,則對P進行SplitNode操作以得到P和PP。
  • AT5:[升高至下一級] 如果N等于L且發生了分裂,則把NN置為PP。從AT2開始重復操作。

有足夠的空間插入的情況,由于插入的x所在的區域P2的數據條目仍然有足夠的空間容納條目x,且x的區域面積即MBR也位于區域P2之內,所以這種情況下,我們認為x擁有足夠的插入空間。

需要增大MBR的插入情況,由于插入的y所在的區域P2的數據條目仍然有足夠的空間容納條目y,但是y的區域面積即MBR并不完全位于P2的區域之內,因此我們在插入數據y后會導致P2區域的相應擴大。

需要進行分裂的插入情況,由于插入的w所在的區域P1的數據條目已經沒有足夠的空間容納條目w,因為假設我們定義R樹的階m=4,而區域P1已經容納了四個條目「A,B,C,K」了,插入w后孩子數為5,以及超過m=4了,所以要進行分類操作,來保證樹的平衡性。

采用分裂算法(下面會進行介紹)對結點(或者說區域)P2進行合理地分裂。使其分裂成P1(包含A,B)和P5(包含k,w)兩個結點。并且需要向上傳遞這種分裂。由于分裂之后原來根結點「P1,P2,P3,P4」變成了「P1,P2,P3,P,P5」,因此根結點的孩子數由4變成5,超過了階數m=4.所以根結點要(采用我們的分裂算法)進行分裂,分裂成Q1(包含P1,P5,P2)和Q2(包含P3,P4)兩個結點,由于此時分裂已經傳遞到根結點,所以生成新的根結點記錄Q1,Q2。

如何合理地分裂到兩個組

挑選種子有多個方法,這里Quadratic(二次方)方案,對于所有條目中的每一對E1和E2,計算可以包裹著E1,E2的最小限定框J=MBR(E1, E2) ,然后計算增量d= J-E1-E2.計算結束后選擇d最大的一對(即增量最大)。(這里為什么要這樣呢:之所以要挑選d最大的一對,是因為如果d較大,說明挑選出來的兩對條目基于對角線離得比較遠,這樣的好處就是分裂后的兩個分組可以盡量不重疊)

挑選出seed1和seed2之后,就將剩下的要分配的分裂條目分個這兩個seed使它們各稱為一個組。而這個分配的原則就是離誰比較“近”就和誰一組。這里的“近”指的是任何一個條目MBB--E和seed1,seed2分別計算可以包裹著E和seed1的最小限定框J1=MBR(E,seed1), 可以包裹著E和seed2的最小限定框J2=MBR(E,seed2)。再分別計算增量d1=J1-E-seed1,d2=J2-E-seed2。d1,d2哪個小就說明哪個“近”。

(-_-)!!!所以分裂的具體情況還是很復雜的,真想不懂這些大神怎么會想得到這些。除了上述Quadratic的分裂算法之外還有其他的分裂算法,如下圖中間圖,但是分裂的效果都不如R*樹(R樹的改進版)的算法好。

刪除

R樹的刪除操作與B樹的刪除操作會有所不同,不過同B樹一樣,會涉及到壓縮等操作。相信讀者看完以下的偽代碼之后會有所體會。R樹的刪除同樣是比較復雜的,需要用到一些輔助函數來完成整個操作。
偽代碼如下:

【Function:Delete】
描述:將一條記錄E從指定的R樹中刪除。
D1:[找到含有記錄的葉子結點] 使用FindLeaf方法找到包含有記錄E的葉子結點L。如果搜索失敗,則直接終止。
D2:[刪除記錄] 將E從L中刪除。
D3:[傳遞記錄] 對L使用CondenseTree操作
D4:[縮減樹] 當經過以上調整后,如果根結點只包含有一個孩子結點,則將這個唯一的孩子結點設為根結點。

【Function:FindLeaf】
描述:根結點為T,期望找到包含有記錄E的葉子結點。
FL1:[搜索子樹] 如果T不是葉子結點,則檢查每一條T中的條目F,找出與E所對應的矩形相重合的F(不必完全覆蓋)。對于所有滿足條件的F,對其指向的孩子結點進行FindLeaf操作,直到尋找到E或者所有條目均以被檢查過。
FL2:[搜索葉子結點以找到記錄] 如果T是葉子結點,那么檢查每一個條目是否有E存在,如果有則返回T。

【Function:CondenseTree】
描述:L為包含有被刪除條目的葉子結點。如果L的條目數過少(小于要求的最小值m),則必須將該葉子結點L從樹中刪除。經過這一刪除操作,L中的剩余條目必須重新插入樹中。此操作將一直重復直至到達根結點。同樣,調整在此修改樹的過程所經過的路徑上的所有結點對應的矩形大小。
CT1:[初始化] 令N為L。初始化一個用于存儲被刪除結點包含的條目的鏈表Q。
CT2:[找到父條目] 如果N為根結點,那么直接跳轉至CT6。否則令P為N 的父結點,令EN為P結點中存儲的指向N的條目。
CT3:[刪除下溢結點] 如果N含有條目數少于m,則從P中刪除EN,并把結點N中的條目添加入鏈表Q中。
CT4:[調整覆蓋矩形] 如果N沒有被刪除,則調整EN.I使得其對應矩形能夠恰好覆蓋N中的所有條目所對應的矩形。
CT5:[向上一層結點進行操作] 令N等于P,從CT2開始重復操作。
CT6:[重新插入孤立的條目] 所有在Q中的結點中的條目需要被重新插入。原來屬于葉子結點的條目可以使用Insert操作進行重新插入,而那些屬于非葉子結點的條目必須插入刪除之前所在層的結點,以確保它們所指向的子樹還處于相同的層。

R樹刪除記錄過程中的CondenseTree操作是不同于B樹的。我們知道,B樹刪除過程中,如果出現結點的記錄數少于半滿(即下溢)的情況,則直接把這些記錄與其他葉子的記錄“融合”,也就是說兩個相鄰結點合并。然而R樹卻是直接重新插入。
具體的例子

假設結點最大條目數為4,最小條目數為2。在這張圖中,我們的目標是刪除記錄c。首先使用FindLeaf操作找到c所處在的葉子結點的位置——R11。當c從R11刪除時,R11就只有一條記錄了,少于最小條目數2,出現下溢,此時要調用CondenseTree操作。這樣,c被刪除,R11剩余的條目——指向記錄d的指針——被插入鏈表Q。然后向更高一層的結點進行此操作。這樣R12會被插入鏈表中。原理是一樣的,在這里就不再贅述。

有一點需要解釋的是,我們發現這個刪除操作向上傳遞之后,根結點的條目R1也被插入了Q中,這樣根結點只剩下了R2。別著急,重新插入操作會有效的解決這個問題。我們插入R3,R12,d至它原來所處的層。這樣,我們發現根結點只有一個條目了,此時根據Inert中的操作,我們把這個根結點刪除,它的孩子結點,即R5,R6,R7,R3所在的結點被置為根結點。至此,刪除操作結束。

另一個例子再次理解刪除

第二部分 R樹的Java實現

UML

Point

  1. package rtree;
  2. /**
  3. * @ClassName Point
  4. * @Description n維空間中的點,所有的維度被存儲在一個float數組中
  5. */
  6. public class Point implements Cloneable {
  7. private float[] data;
  8. public Point(float[] data) {
  9. if (data == null) {
  10. throw new IllegalArgumentException("Coordinates cannot be null."); // ★坐標不能為空
  11. }
  12. if (data.length < 2) {
  13. throw new IllegalArgumentException("Point dimension should be greater than 1."); // ★點的維度必須大于1
  14. }
  15. this.data = new float[data.length];
  16. System.arraycopy(data, 0, this.data, 0, data.length); // 復制數組
  17. }
  18. public Point(int[] data) {
  19. if (data == null) {
  20. throw new IllegalArgumentException("Coordinates cannot be null."); // ★坐標不能為空
  21. }
  22. if (data.length < 2) {
  23. throw new IllegalArgumentException("Point dimension should be greater than 1."); // ★點的維度必須大于1
  24. }
  25. this.data = new float[data.length];
  26. for (int i = 0; i < data.length; i++) {
  27. this.data[i] = data[i]; // 復制數組
  28. }
  29. }
  30. @Override // 重寫clone接口
  31. protected Object clone() {
  32. float[] copy = new float[data.length];
  33. System.arraycopy(data, 0, copy, 0, data.length);
  34. return new Point(copy);
  35. }
  36. @Override // 重寫tostring()方法
  37. public String toString() {
  38. StringBuffer sBuffer = new StringBuffer("(");
  39. for (int i = 0; i < data.length - 1; i++) {
  40. sBuffer.append(data[i]).append(",");
  41. }
  42. sBuffer.append(data[data.length - 1]).append(")"); // 最后一位數據后面不再添加逗號,追加放在循環外面
  43. return sBuffer.toString();
  44. }
  45. /*
  46. * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ ★ 測試 ★
  47. * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
  48. */
  49. public static void main(String[] args) {
  50. float[] test = { 1.2f, 2f, 34f };
  51. Point point1 = new Point(test);
  52. System.out.println(point1);
  53. int[] test2 = { 1, 2, 3, 4 };
  54. point1 = new Point(test2);
  55. System.out.println(point1);
  56. int[] test3 = { 11, 22 }; // 二維的點
  57. point1 = new Point(test3);
  58. System.out.println(point1);
  59. }
  60. /**
  61. * @return 返回Point的維度
  62. */
  63. public int getDimension() {
  64. return data.length;
  65. }
  66. /**
  67. * @param index
  68. * @return 返回Point坐標第i位的float值
  69. */
  70. public float getFloatCoordinate(int index) {
  71. return data[index];
  72. }
  73. /**
  74. * @param index
  75. * @return 返回Point坐標第i位的int值
  76. */
  77. public int getIntCoordinate(int index) {
  78. return (int) data[index];
  79. }
  80. @Override
  81. public boolean equals(Object obj) {
  82. if (obj instanceof Point) // 如果obj是point的實例
  83. {
  84. Point point = (Point) obj;
  85. if (point.getDimension() != getDimension()) // 維度相同的點才能比較
  86. throw new IllegalArgumentException("Points must be of equal dimensions to be compared.");
  87. for (int i = 0; i < getDimension(); i++) {
  88. if (getFloatCoordinate(i) != point.getFloatCoordinate(i))
  89. return false;
  90. }
  91. }
  92. if (!(obj instanceof Point))
  93. return false;
  94. return true;
  95. }
  96. }

Rectangle

  1. package rtree;
  2. /**
  3. * 外包矩形
  4. *
  5. * @ClassName Rectangle
  6. * @Description
  7. */
  8. public class Rectangle implements Cloneable // 繼承克隆接口
  9. {
  10. private Point low; // 左下角的點
  11. private Point high; // 右上角的點
  12. public Rectangle(Point p1, Point p2) // 初始化時,第一個參數為左下角,第二個參數為右上角
  13. {
  14. if (p1 == null || p2 == null) // 點對象不能為空
  15. {
  16. throw new IllegalArgumentException("Points cannot be null.");
  17. }
  18. if (p1.getDimension() != p2.getDimension()) // 點的維度應該相等
  19. {
  20. throw new IllegalArgumentException("Points must be of same dimension.");
  21. }
  22. // 先左下角后右上角
  23. for (int i = 0; i < p1.getDimension(); i++) {
  24. if (p1.getFloatCoordinate(i) > p2.getFloatCoordinate(i)) {
  25. throw new IllegalArgumentException("坐標點為先左下角后右上角");
  26. }
  27. }
  28. low = (Point) p1.clone();
  29. high = (Point) p2.clone();
  30. }
  31. /**
  32. * 返回Rectangle左下角的Point
  33. *
  34. * @return Point
  35. */
  36. public Point getLow() {
  37. return (Point) low.clone();
  38. }
  39. /**
  40. * 返回Rectangle右上角的Point
  41. *
  42. * @return Point
  43. */
  44. public Point getHigh() {
  45. return high;
  46. }
  47. /**
  48. * @param rectangle
  49. * @return 包圍兩個Rectangle的最小Rectangle
  50. */
  51. public Rectangle getUnionRectangle(Rectangle rectangle) {
  52. if (rectangle == null) // 矩形不能為空
  53. throw new IllegalArgumentException("Rectangle cannot be null.");
  54. if (rectangle.getDimension() != getDimension()) // 矩形維度必須相同
  55. {
  56. throw new IllegalArgumentException("Rectangle must be of same dimension.");
  57. }
  58. float[] min = new float[getDimension()];
  59. float[] max = new float[getDimension()];
  60. for (int i = 0; i < getDimension(); i++) {
  61. // 第一個參數是當前矩形的坐標值,第二個參數是傳入的參數的矩形的坐標值
  62. min[i] = Math.min(low.getFloatCoordinate(i), rectangle.low.getFloatCoordinate(i));
  63. max[i] = Math.max(high.getFloatCoordinate(i), rectangle.high.getFloatCoordinate(i));
  64. }
  65. return new Rectangle(new Point(min), new Point(max));
  66. }
  67. /**
  68. * @return 返回Rectangle的面積
  69. */
  70. public float getArea() {
  71. float area = 1;
  72. for (int i = 0; i < getDimension(); i++) {
  73. area *= high.getFloatCoordinate(i) - low.getFloatCoordinate(i);
  74. }
  75. return area;
  76. }
  77. /**
  78. * @param rectangles
  79. * @return 包圍一系列Rectangle的最小Rectangle
  80. */
  81. public static Rectangle getUnionRectangle(Rectangle[] rectangles) {
  82. if (rectangles == null || rectangles.length == 0)
  83. throw new IllegalArgumentException("Rectangle array is empty.");
  84. Rectangle r0 = (Rectangle) rectangles[0].clone();
  85. for (int i = 1; i < rectangles.length; i++) {
  86. r0 = r0.getUnionRectangle(rectangles[i]); // 獲得包裹矩形r0與r[i]的最小邊界的矩形再賦值給r0
  87. }
  88. return r0; // 返回包圍一系列Rectangle的最小Rectangle
  89. }
  90. @Override
  91. // 重寫clone()函數
  92. protected Object clone() {
  93. Point p1 = (Point) low.clone();
  94. Point p2 = (Point) high.clone();
  95. return new Rectangle(p1, p2);
  96. }
  97. @Override
  98. // 重寫tostring()方法
  99. public String toString() {
  100. return "Rectangle Low:" + low + " High:" + high;
  101. }
  102. /*
  103. * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ ★ 測試 ★
  104. * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
  105. */
  106. public static void main(String[] args) {
  107. // 新建兩point再根據兩個point構建一個Rectangle
  108. float[] f1 = { 1.3f, 2.4f };
  109. float[] f2 = { 3.4f, 4.5f };
  110. Point p1 = new Point(f1);
  111. Point p2 = new Point(f2);
  112. Rectangle rectangle = new Rectangle(p1, p2);
  113. System.out.println(rectangle);
  114. // Point point = rectangle.getHigh();
  115. // point = p1;
  116. // System.out.println(rectangle);
  117. float[] f_1 = { -2f, 0f };
  118. float[] f_2 = { 0f, 2f };
  119. float[] f_3 = { -2f, 1f };
  120. float[] f_4 = { 3f, 3f };
  121. float[] f_5 = { 1f, 0f };
  122. float[] f_6 = { 2f, 4f };
  123. p1 = new Point(f_1);
  124. p2 = new Point(f_2);
  125. Point p3 = new Point(f_3);
  126. Point p4 = new Point(f_4);
  127. Point p5 = new Point(f_5);
  128. Point p6 = new Point(f_6);
  129. Rectangle re1 = new Rectangle(p1, p2);
  130. Rectangle re2 = new Rectangle(p3, p4);
  131. Rectangle re3 = new Rectangle(p5, p6);
  132. // Rectangle re4 = new Rectangle(p3, p4); //輸入要先左下角,再右上角
  133. System.out.println(re1.isIntersection(re2));
  134. System.out.println(re1.isIntersection(re3));
  135. System.out.println(re1.intersectingArea(re2));
  136. System.out.println(re1.intersectingArea(re3));
  137. }
  138. /**
  139. * 兩個Rectangle相交的面積
  140. *
  141. * @param rectangle
  142. * Rectangle
  143. * @return float
  144. */
  145. public float intersectingArea(Rectangle rectangle) {
  146. if (!isIntersection(rectangle)) // 如果不相交,相交面積為0
  147. {
  148. return 0;
  149. }
  150. float ret = 1;
  151. // 循環一次,得到一個維度的相交的邊,累乘多個維度的相交的邊,即為面積
  152. for (int i = 0; i < rectangle.getDimension(); i++) {
  153. float l1 = this.low.getFloatCoordinate(i);
  154. float h1 = this.high.getFloatCoordinate(i);
  155. float l2 = rectangle.low.getFloatCoordinate(i);
  156. float h2 = rectangle.high.getFloatCoordinate(i);
  157. // rectangle1在rectangle2的左邊
  158. if (l1 <= l2 && h1 <= h2) {
  159. ret *= (h1 - l1) - (l2 - l1);
  160. }
  161. // rectangle1在rectangle2的右邊
  162. else if (l1 >= l2 && h1 >= h2) {
  163. ret *= (h2 - l2) - (l1 - l2);
  164. }
  165. // rectangle1在rectangle2里面
  166. else if (l1 >= l2 && h1 <= h2) {
  167. ret *= h1 - l1;
  168. }
  169. // rectangle1包含rectangle2
  170. else if (l1 <= l2 && h1 >= h2) {
  171. ret *= h2 - l2;
  172. }
  173. }
  174. return ret;
  175. }
  176. /**
  177. * @param rectangle
  178. * @return 判斷兩個Rectangle是否相交
  179. */
  180. public boolean isIntersection(Rectangle rectangle) {
  181. if (rectangle == null)
  182. throw new IllegalArgumentException("Rectangle cannot be null.");
  183. if (rectangle.getDimension() != getDimension()) // 進行判斷的兩個矩形維度必須相等
  184. {
  185. throw new IllegalArgumentException("Rectangle cannot be null.");
  186. }
  187. for (int i = 0; i < getDimension(); i++) {
  188. /*
  189. * 當前矩形左下角的坐標值大于傳入矩形右上角的坐標值 || 當前矩形右上角角的坐標值小于傳入矩形左下角的坐標值
  190. */
  191. if (low.getFloatCoordinate(i) > rectangle.high.getFloatCoordinate(i)
  192. || high.getFloatCoordinate(i) < rectangle.low.getFloatCoordinate(i)) {
  193. return false; // 沒有相交
  194. }
  195. }
  196. return true;
  197. }
  198. /**
  199. * @return 返回Rectangle的維度
  200. */
  201. private int getDimension() {
  202. return low.getDimension();
  203. }
  204. /**
  205. * 判斷rectangle是否被包圍
  206. *
  207. * @param rectangle
  208. * @return
  209. */
  210. public boolean enclosure(Rectangle rectangle) {
  211. if (rectangle == null) // 矩形不能為空
  212. throw new IllegalArgumentException("Rectangle cannot be null.");
  213. if (rectangle.getDimension() != getDimension()) // 判斷的矩形必須維度相同
  214. throw new IllegalArgumentException("Rectangle dimension is different from current dimension.");
  215. // 只要傳入的rectangle有一個維度的坐標越界了就不被包含
  216. for (int i = 0; i < getDimension(); i++) {
  217. if (rectangle.low.getFloatCoordinate(i) < low.getFloatCoordinate(i)
  218. || rectangle.high.getFloatCoordinate(i) > high.getFloatCoordinate(i))
  219. return false;
  220. }
  221. return true;
  222. }
  223. @Override
  224. // 重寫equals方法
  225. public boolean equals(Object obj) {
  226. if (obj instanceof Rectangle) {
  227. Rectangle rectangle = (Rectangle) obj;
  228. if (low.equals(rectangle.getLow()) && high.equals(rectangle.getHigh()))
  229. return true;
  230. }
  231. return false;
  232. }
  233. }

RTNode

  1. package rtree;
  2. import java.util.List;
  3. import rtree.Constants;
  4. /**
  5. * @ClassName RTNode
  6. * @Description
  7. */
  8. public abstract class RTNode {
  9. protected RTree rtree; // 結點所在的樹
  10. protected int level; // 結點所在的層
  11. protected Rectangle[] datas; // 相當于條目
  12. protected RTNode parent; // 父節點
  13. protected int usedSpace; // 結點已用的空間
  14. protected int insertIndex; // 記錄插入的搜索路徑索引
  15. protected int deleteIndex; // 記錄刪除的查找路徑索引
  16. /**
  17. * 構造函數初始化
  18. */
  19. public RTNode(RTree rtree, RTNode parent, int level) {
  20. this.rtree = rtree;
  21. this.parent = parent;
  22. this.level = level;
  23. datas = new Rectangle[rtree.getNodeCapacity() + 1];// 多出的一個用于結點分裂
  24. usedSpace = 0;
  25. }
  26. /**
  27. * @return 返回父節點
  28. */
  29. public RTNode getParent() {
  30. return parent;
  31. }
  32. /**
  33. * -->向結點中添加Rectangle,即添加條目
  34. *
  35. * @param rectangle
  36. */
  37. protected void addData(Rectangle rectangle) {
  38. // 如果節點已用空間==r樹的節點容量
  39. if (usedSpace == rtree.getNodeCapacity()) {
  40. throw new IllegalArgumentException("Node is full.");
  41. }
  42. datas[usedSpace++] = rectangle;
  43. }
  44. /**
  45. * -->刪除結點中的第i個條目
  46. *
  47. * @param i
  48. */
  49. protected void deleteData(int i) {
  50. if (datas[i + 1] != null) // 如果為中間節點(非尾節點),采用拷貝數組的方式鏈接條目
  51. {
  52. System.arraycopy(datas, i + 1, datas, i, usedSpace - i - 1);
  53. datas[usedSpace - 1] = null;
  54. } else // 如果為末尾節點,將節點置空
  55. datas[i] = null;
  56. // 刪除之后已用節點自減
  57. usedSpace--;
  58. }
  59. /**
  60. * 壓縮算法 葉節點L中剛剛刪除了一個條目,如果這個結點的條目數太少下溢, 則刪除該結點,同時將該結點中剩余的條目重定位到其他結點中。
  61. * 如果有必要,要逐級向上進行這種刪除,調整向上傳遞的路徑上的所有外包矩形,使其盡可能小,直到根節點。
  62. *
  63. * @param list
  64. * 存儲刪除結點中剩余條目
  65. */
  66. protected void condenseTree(List<RTNode> list) {
  67. if (isRoot()) {
  68. // 根節點只有一個條目了,即只有左孩子或者右孩子 ,
  69. // 將唯一條目刪除,釋放根節點,將原根節點唯一的孩子設置為新根節點
  70. if (!isLeaf() && usedSpace == 1) {
  71. RTDirNode root = (RTDirNode) this;
  72. RTNode child = root.getChild(0);
  73. root.children.remove(this);
  74. child.parent = null;
  75. rtree.setRoot(child);
  76. }
  77. } else {
  78. RTNode parent = getParent();
  79. // 計算節點最小容量,用于判斷是否引起下溢
  80. int min = Math.round(rtree.getNodeCapacity() * rtree.getFillFactor());
  81. if (usedSpace < min) {
  82. parent.deleteData(parent.deleteIndex);// 其父節點中刪除此條目
  83. ((RTDirNode) parent).children.remove(this);
  84. this.parent = null;
  85. list.add(this);// 之前已經把數據刪除了
  86. } else {
  87. parent.datas[parent.deleteIndex] = getNodeRectangle();
  88. }
  89. parent.condenseTree(list);
  90. }
  91. }
  92. /**
  93. * 分裂結點的平方算法
  94. * <p>
  95. * 1、為兩個組選擇第一個條目--調用算法pickSeeds()來為兩個組選擇第一個元素,分別把選中的兩個條目分配到兩個組當中。<br>
  96. * 2、檢查是否已經分配完畢,如果一個組中的條目太少,為避免下溢,將剩余的所有條目全部分配到這個組中,算法終止<br>
  97. * 3、調用pickNext來選擇下一個進行分配的條目--計算把每個條目加入每個組之后面積的增量,選擇兩個組面積增量差最大的條目索引,
  98. * 如果面積增量相等則選擇面積較小的組,若面積也相等則選擇條目數更少的組<br>
  99. *
  100. * @param rectangle
  101. * 導致分裂的溢出Rectangle
  102. * @return 兩個組中的條目的索引
  103. */
  104. protected int[][] quadraticSplit(Rectangle rectangle) {
  105. if (rectangle == null) {
  106. throw new IllegalArgumentException("Rectangle cannot be null.");
  107. }
  108. datas[usedSpace] = rectangle; // 先添加進去
  109. int total = usedSpace + 1; // 結點總數
  110. // 標記訪問的條目
  111. int[] mask = new int[total];
  112. for (int i = 0; i < total; i++) {
  113. mask[i] = 1;
  114. }
  115. // 分裂后每個組只是有total/2個條目
  116. int c = total / 2 + 1;
  117. // 每個結點最小條目個數
  118. int minNodeSize = Math.round(rtree.getNodeCapacity() * rtree.getFillFactor());
  119. // 至少有兩個
  120. if (minNodeSize < 2)
  121. minNodeSize = 2;
  122. // 記錄沒有被檢查的條目的個數
  123. int rem = total;
  124. int[] group1 = new int[c];// 記錄分配的條目的索引
  125. int[] group2 = new int[c];// 記錄分配的條目的索引
  126. // 跟蹤被插入每個組的條目的索引
  127. int i1 = 0, i2 = 0;
  128. int[] seed = pickSeeds();
  129. group1[i1++] = seed[0];
  130. group2[i2++] = seed[1];
  131. rem -= 2;
  132. mask[group1[0]] = -1;
  133. mask[group2[0]] = -1;
  134. while (rem > 0) {
  135. // 將剩余的所有條目全部分配到group1組中,算法終止
  136. if (minNodeSize - i1 == rem) {
  137. for (int i = 0; i < total; i++)// 總共rem個
  138. {
  139. if (mask[i] != -1)// 還沒有被分配
  140. {
  141. group1[i1++] = i;
  142. mask[i] = -1;
  143. rem--;
  144. }
  145. }
  146. // 將剩余的所有條目全部分配到group1組中,算法終止
  147. } else if (minNodeSize - i2 == rem) {
  148. for (int i = 0; i < total; i++)// 總共rem個
  149. {
  150. if (mask[i] != -1)// 還沒有被分配
  151. {
  152. group2[i2++] = i;
  153. mask[i] = -1;
  154. rem--;
  155. }
  156. }
  157. } else {
  158. // 求group1中所有條目的最小外包矩形
  159. Rectangle mbr1 = (Rectangle) datas[group1[0]].clone();
  160. for (int i = 1; i < i1; i++) {
  161. mbr1 = mbr1.getUnionRectangle(datas[group1[i]]);
  162. }
  163. // 求group2中所有條目的外包矩形
  164. Rectangle mbr2 = (Rectangle) datas[group2[0]].clone();
  165. for (int i = 1; i < i2; i++) {
  166. mbr2 = mbr2.getUnionRectangle(datas[group2[i]]);
  167. }
  168. // 找出下一個進行分配的條目
  169. double dif = Double.NEGATIVE_INFINITY;
  170. double areaDiff1 = 0, areaDiff2 = 0;
  171. int sel = -1;
  172. for (int i = 0; i < total; i++) {
  173. if (mask[i] != -1)// 還沒有被分配的條目
  174. {
  175. // 計算把每個條目加入每個組之后面積的增量,選擇兩個組面積增量差最大的條目索引
  176. Rectangle a = mbr1.getUnionRectangle(datas[i]);
  177. areaDiff1 = a.getArea() - mbr1.getArea();
  178. Rectangle b = mbr2.getUnionRectangle(datas[i]);
  179. areaDiff2 = b.getArea() - mbr2.getArea();
  180. if (Math.abs(areaDiff1 - areaDiff2) > dif) {
  181. dif = Math.abs(areaDiff1 - areaDiff2);
  182. sel = i;
  183. }
  184. }
  185. }
  186. if (areaDiff1 < areaDiff2)// 先比較面積增量
  187. {
  188. group1[i1++] = sel;
  189. } else if (areaDiff1 > areaDiff2) {
  190. group2[i2++] = sel;
  191. } else if (mbr1.getArea() < mbr2.getArea())// 再比較自身面積
  192. {
  193. group1[i1++] = sel;
  194. } else if (mbr1.getArea() > mbr2.getArea()) {
  195. group2[i2++] = sel;
  196. } else if (i1 < i2)// 最后比較條目個數
  197. {
  198. group1[i1++] = sel;
  199. } else if (i1 > i2) {
  200. group2[i2++] = sel;
  201. } else {
  202. group1[i1++] = sel;
  203. }
  204. mask[sel] = -1;
  205. rem--;
  206. }
  207. } // end while
  208. int[][] ret = new int[2][];
  209. ret[0] = new int[i1];
  210. ret[1] = new int[i2];
  211. for (int i = 0; i < i1; i++) {
  212. ret[0][i] = group1[i];
  213. }
  214. for (int i = 0; i < i2; i++) {
  215. ret[1][i] = group2[i];
  216. }
  217. return ret;
  218. }
  219. /**
  220. * 1、對每一對條目E1和E2,計算包圍它們的Rectangle J,計算d = area(J) - area(E1) - area(E2);<br>
  221. * 2、Choose the pair with the largest d
  222. *
  223. * @return 返回兩個條目如果放在一起會有最多的冗余空間的條目索引
  224. */
  225. protected int[] pickSeeds() {
  226. double inefficiency = Double.NEGATIVE_INFINITY;
  227. int i1 = 0, i2 = 0;
  228. // 兩個for循環對任意兩個條目E1和E2進行組合
  229. for (int i = 0; i < usedSpace; i++) {
  230. for (int j = i + 1; j <= usedSpace; j++)// 注意此處的j值
  231. {
  232. Rectangle rectangle = datas[i].getUnionRectangle(datas[j]);
  233. double d = rectangle.getArea() - datas[i].getArea() - datas[j].getArea();
  234. if (d > inefficiency) {
  235. inefficiency = d;
  236. i1 = i;
  237. i2 = j;
  238. }
  239. }
  240. }
  241. return new int[] { i1, i2 }; // 返回擁有最小d的一對條目
  242. }
  243. /**
  244. * @return 返回包含結點中所有條目的最小Rectangle
  245. */
  246. public Rectangle getNodeRectangle() {
  247. if (usedSpace > 0) {
  248. Rectangle[] rectangles = new Rectangle[usedSpace];
  249. System.arraycopy(datas, 0, rectangles, 0, usedSpace);
  250. return Rectangle.getUnionRectangle(rectangles); // 返回包含這一系列矩形的最小矩形
  251. } else {
  252. return new Rectangle(new Point(new float[] { 0, 0 }), new Point(new float[] { 0, 0 }));
  253. }
  254. }
  255. /**
  256. * @return 是否根節點
  257. */
  258. public boolean isRoot() {
  259. return (parent == Constants.NULL);
  260. }
  261. /**
  262. * @return 是否非葉子結點
  263. */
  264. public boolean isIndex() {
  265. return (level != 0);
  266. }
  267. /**
  268. * @return 是否葉子結點
  269. */
  270. public boolean isLeaf() {
  271. return (level == 0);
  272. }
  273. /**
  274. * <b>步驟CL1:</b>初始化――記R樹的根節點為N。<br>
  275. * <b>步驟CL2:</b>檢查葉節點――如果N是個葉節點,返回N<br>
  276. * <b>步驟CL3:</b>選擇子樹――如果N不是葉節點,則從N中所有的條目中選出一個最佳的條目F,
  277. * 選擇的標準是:如果E加入F后,F的外廓矩形FI擴張最小,則F就是最佳的條目。如果有兩個
  278. * 條目在加入E后外廓矩形的擴張程度相等,則在這兩者中選擇外廓矩形較小的那個。<br>
  279. * <b>步驟CL4:</b>向下尋找直至達到葉節點――記Fp指向的孩子節點為N,然后返回步驟CL2循環運算, 直至查找到葉節點。
  280. * <p>
  281. *
  282. * @param Rectangle
  283. * @return RTDataNode
  284. */
  285. protected abstract RTDataNode chooseLeaf(Rectangle rectangle);
  286. /**
  287. * R樹的根節點為T,查找包含rectangle的葉子結點
  288. * <p>
  289. * 1、如果T不是葉子結點,則逐個查找T中的每個條目是否包圍rectangle,若包圍則遞歸調用findLeaf()<br>
  290. * 2、如果T是一個葉子結點,則逐個檢查T中的每個條目能否匹配rectangle<br>
  291. *
  292. * @param rectangle
  293. * @return 返回包含rectangle的葉節點
  294. */
  295. protected abstract RTDataNode findLeaf(Rectangle rectangle);
  296. }

RTDataNode

  1. package rtree;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import rtree.Constants;
  5. /**
  6. * @ClassName RTDataNode
  7. * @Description 葉子結點
  8. */
  9. public class RTDataNode extends RTNode {
  10. public RTDataNode(RTree rTree, RTNode parent) {
  11. super(rTree, parent, 0);
  12. }
  13. /**
  14. * -->葉節點中插入Rectangle 在葉節點中插入Rectangle,插入后如果其父節點不為空則需要向上調整樹直到根節點;
  15. * 如果其父節點為空,則是從根節點插入 若插入Rectangle之后超過結點容量則需要分裂結點 【注】插入數據后,從parent處開始調整數據
  16. *
  17. * @param rectangle
  18. * @return
  19. */
  20. public boolean insert(Rectangle rectangle) {
  21. if (usedSpace < rtree.getNodeCapacity()) // 已用節點小于節點容量
  22. {
  23. datas[usedSpace++] = rectangle;
  24. RTDirNode parent = (RTDirNode) getParent();
  25. if (parent != null)
  26. // 調整樹,但不需要分裂節點,因為 節點小于節點容量,還有空間
  27. parent.adjustTree(this, null);
  28. return true;
  29. }
  30. // 超過結點容量
  31. else {
  32. RTDataNode[] splitNodes = splitLeaf(rectangle);
  33. RTDataNode l = splitNodes[0];
  34. RTDataNode ll = splitNodes[1];
  35. if (isRoot()) {
  36. // 根節點已滿,需要分裂。創建新的根節點
  37. RTDirNode rDirNode = new RTDirNode(rtree, Constants.NULL, level + 1);
  38. rtree.setRoot(rDirNode);
  39. // getNodeRectangle()返回包含結點中所有條目的最小Rectangle
  40. rDirNode.addData(l.getNodeRectangle());
  41. rDirNode.addData(ll.getNodeRectangle());
  42. ll.parent = rDirNode;
  43. l.parent = rDirNode;
  44. rDirNode.children.add(l);
  45. rDirNode.children.add(ll);
  46. } else {// 不是根節點
  47. RTDirNode parentNode = (RTDirNode) getParent();
  48. parentNode.adjustTree(l, ll);
  49. }
  50. }
  51. return true;
  52. }
  53. /**
  54. * 葉子節點的分裂 插入Rectangle之后超過容量需要分裂
  55. *
  56. * @param rectangle
  57. * @return
  58. */
  59. public RTDataNode[] splitLeaf(Rectangle rectangle) {
  60. int[][] group = null;
  61. switch (rtree.getTreeType()) {
  62. case Constants.RTREE_LINEAR:
  63. break;
  64. case Constants.RTREE_QUADRATIC:
  65. group = quadraticSplit(rectangle);
  66. break;
  67. case Constants.RTREE_EXPONENTIAL:
  68. break;
  69. case Constants.RSTAR:
  70. break;
  71. default:
  72. throw new IllegalArgumentException("Invalid tree type.");
  73. }
  74. RTDataNode l = new RTDataNode(rtree, parent);
  75. RTDataNode ll = new RTDataNode(rtree, parent);
  76. int[] group1 = group[0];
  77. int[] group2 = group[1];
  78. for (int i = 0; i < group1.length; i++) {
  79. l.addData(datas[group1[i]]);
  80. }
  81. for (int i = 0; i < group2.length; i++) {
  82. ll.addData(datas[group2[i]]);
  83. }
  84. return new RTDataNode[] { l, ll };
  85. }
  86. @Override
  87. public RTDataNode chooseLeaf(Rectangle rectangle) {
  88. insertIndex = usedSpace;// 記錄插入路徑的索引
  89. return this;
  90. }
  91. /**
  92. * 從葉節點中刪除此條目rectangle
  93. * <p>
  94. * 先刪除此rectangle,再調用condenseTree()返回刪除結點的集合,把其中的葉子結點中的每個條目重新插入;
  95. * 非葉子結點就從此結點開始遍歷所有結點,然后把所有的葉子結點中的所有條目全部重新插入
  96. *
  97. * @param rectangle
  98. * @return
  99. */
  100. protected int delete(Rectangle rectangle) {
  101. for (int i = 0; i < usedSpace; i++) {
  102. if (datas[i].equals(rectangle)) {
  103. deleteData(i);
  104. // 用于存儲被刪除的結點包含的條目的鏈表Q
  105. List<RTNode> deleteEntriesList = new ArrayList<RTNode>();
  106. condenseTree(deleteEntriesList);
  107. // 重新插入刪除結點中剩余的條目
  108. for (int j = 0; j < deleteEntriesList.size(); j++) {
  109. RTNode node = deleteEntriesList.get(j);
  110. if (node.isLeaf())// 葉子結點,直接把其上的數據重新插入
  111. {
  112. for (int k = 0; k < node.usedSpace; k++) {
  113. rtree.insert(node.datas[k]);
  114. }
  115. } else {// 非葉子結點,需要先后序遍歷出其上的所有結點
  116. List<RTNode> traverseNodes = rtree.traversePostOrder(node);
  117. // 把其中的葉子結點中的條目重新插入
  118. for (int index = 0; index < traverseNodes.size(); index++) {
  119. RTNode traverseNode = traverseNodes.get(index);
  120. if (traverseNode.isLeaf()) {
  121. for (int t = 0; t < traverseNode.usedSpace; t++) {
  122. rtree.insert(traverseNode.datas[t]);
  123. }
  124. }
  125. }
  126. }
  127. }
  128. return deleteIndex;
  129. } // end if
  130. } // end for
  131. return -1;
  132. }
  133. @Override
  134. protected RTDataNode findLeaf(Rectangle rectangle) {
  135. for (int i = 0; i < usedSpace; i++) {
  136. if (datas[i].enclosure(rectangle)) {
  137. deleteIndex = i;// 記錄搜索路徑
  138. return this;
  139. }
  140. }
  141. return null;
  142. }
  143. }

RTDirNode

  1. package rtree;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import rtree.Constants;
  5. /**
  6. * @ClassName RTDirNode
  7. * @Description 非葉節點
  8. */
  9. public class RTDirNode extends RTNode {
  10. /**
  11. * 孩子結點集
  12. */
  13. protected List<RTNode> children;
  14. // 構造函數
  15. public RTDirNode(RTree rtree, RTNode parent, int level) {
  16. super(rtree, parent, level); // 調用父類的構造函數
  17. children = new ArrayList<RTNode>(); // 新建一個RTNode類型的結點數組
  18. }
  19. /**
  20. * @param index
  21. * @return 對應索引下的孩子結點
  22. */
  23. public RTNode getChild(int index) {
  24. return children.get(index);
  25. }
  26. @Override
  27. /*-->選擇葉子結點*/
  28. public RTDataNode chooseLeaf(Rectangle rectangle) {
  29. int index;
  30. switch (rtree.getTreeType()) {
  31. case Constants.RTREE_LINEAR:
  32. case Constants.RTREE_QUADRATIC:
  33. case Constants.RTREE_EXPONENTIAL:
  34. index = findLeastEnlargement(rectangle); // 獲得面積增量最小的結點的索引
  35. break;
  36. case Constants.RSTAR:
  37. if (level == 1)// 即此結點指向葉節點
  38. {
  39. index = findLeastOverlap(rectangle); // 獲得最小重疊面積的結點的索引
  40. } else {
  41. index = findLeastEnlargement(rectangle); // 獲得面積增量最小的結點的索引
  42. }
  43. break;
  44. default:
  45. throw new IllegalStateException("Invalid tree type.");
  46. }
  47. insertIndex = index;// 記錄插入路徑的索引
  48. return getChild(index).chooseLeaf(rectangle); // 非葉子節點的chooseLeaf()實現遞歸調用
  49. }
  50. /**
  51. * @param rectangle
  52. * @return -->返回最小重疊面積的結點的索引, 如果重疊面積相等則選擇加入此Rectangle后面積增量更小的,
  53. * 如果面積增量還相等則選擇自身面積更小的
  54. */
  55. private int findLeastOverlap(Rectangle rectangle) {
  56. float overlap = Float.POSITIVE_INFINITY;
  57. int sel = -1;
  58. for (int i = 0; i < usedSpace; i++) {
  59. RTNode node = getChild(i);
  60. float ol = 0; // 用于記錄每個孩子的datas數據與傳入矩形的重疊面積之和
  61. for (int j = 0; j < node.datas.length; j++) {
  62. // 將傳入矩形與各個矩形重疊的面積累加到ol中,得到重疊的總面積
  63. ol += rectangle.intersectingArea(node.datas[j]);
  64. }
  65. if (ol < overlap) {
  66. overlap = ol;// 記錄重疊面積最小的
  67. sel = i;// 記錄第幾個孩子的索引
  68. }
  69. // 如果重疊面積相等則選擇加入此Rectangle后面積增量更小的,如果面積增量還相等則選擇自身面積更小的
  70. else if (ol == overlap) {
  71. double area1 = datas[i].getUnionRectangle(rectangle).getArea() - datas[i].getArea();
  72. double area2 = datas[sel].getUnionRectangle(rectangle).getArea() - datas[sel].getArea();
  73. if (area1 == area2) {
  74. sel = (datas[sel].getArea() <= datas[i].getArea()) ? sel : i;
  75. } else {
  76. sel = (area1 < area2) ? i : sel;
  77. }
  78. }
  79. }
  80. return sel;
  81. }
  82. /**
  83. * @param rectangle
  84. * @return -->面積增量最小的結點的索引,如果面積增量相等則選擇自身面積更小的
  85. */
  86. private int findLeastEnlargement(Rectangle rectangle) {
  87. double area = Double.POSITIVE_INFINITY; // double類型的正無窮
  88. int sel = -1;
  89. for (int i = 0; i < usedSpace; i++) {
  90. // 增量enlargement = 包含(datas[i]里面存儲的矩形與查找的矩形)的最小矩形的面積 -
  91. // datas[i]里面存儲的矩形的面積
  92. double enlargement = datas[i].getUnionRectangle(rectangle).getArea() - datas[i].getArea();
  93. if (enlargement < area) {
  94. area = enlargement; // 記錄增量
  95. sel = i; // 記錄引起增量的【包含(datas[i]里面存儲的矩形與查找的矩形)的最小矩形】里面的datas[i]的索引
  96. } else if (enlargement == area) {
  97. sel = (datas[sel].getArea() < datas[i].getArea()) ? sel : i;
  98. }
  99. }
  100. return sel;
  101. }
  102. /**
  103. * --> 插入新的Rectangle后從插入的葉節點開始向上調整RTree,直到根節點
  104. *
  105. * @param node1
  106. * 引起需要調整的孩子結點
  107. * @param node2
  108. * 分裂的結點,若未分裂則為null
  109. */
  110. public void adjustTree(RTNode node1, RTNode node2) {
  111. // 先要找到指向原來舊的結點(即未添加Rectangle之前)的條目的索引
  112. datas[insertIndex] = node1.getNodeRectangle();// 先用node1覆蓋原來的結點
  113. children.set(insertIndex, node1);// 替換舊的結點
  114. if (node2 != null) {
  115. insert(node2);// 插入新的結點
  116. }
  117. // 還沒到達根節點
  118. else if (!isRoot()) {
  119. RTDirNode parent = (RTDirNode) getParent();
  120. parent.adjustTree(this, null);// 向上調整直到根節點
  121. }
  122. }
  123. /**
  124. * -->非葉子節點插入
  125. *
  126. * @param node
  127. * @return 如果結點需要分裂則返回true
  128. */
  129. protected boolean insert(RTNode node) {
  130. // 已用結點小于樹的節點容量,不需分裂,只需插入以及調整樹
  131. if (usedSpace < rtree.getNodeCapacity()) {
  132. datas[usedSpace++] = node.getNodeRectangle();
  133. children.add(node);// 新加的
  134. node.parent = this;// 新加的
  135. RTDirNode parent = (RTDirNode) getParent();
  136. if (parent != null) // 不是根節點
  137. {
  138. parent.adjustTree(this, null);
  139. }
  140. return false;
  141. } else {// 非葉子結點需要分裂
  142. RTDirNode[] a = splitIndex(node);
  143. RTDirNode n = a[0];
  144. RTDirNode nn = a[1];
  145. if (isRoot()) {
  146. // 新建根節點,層數加1
  147. RTDirNode newRoot = new RTDirNode(rtree, Constants.NULL, level + 1);
  148. // 把兩個分裂的結點n和nn添加到根節點
  149. newRoot.addData(n.getNodeRectangle());
  150. newRoot.addData(nn.getNodeRectangle());
  151. newRoot.children.add(n);
  152. newRoot.children.add(nn);
  153. // 設置兩個分裂的結點n和nn的父節點
  154. n.parent = newRoot;
  155. nn.parent = newRoot;
  156. // 最后設置rtree的根節點
  157. rtree.setRoot(newRoot);// 新加的
  158. } else {
  159. // 如果不是根結點,向上調整樹
  160. RTDirNode p = (RTDirNode) getParent();
  161. p.adjustTree(n, nn);
  162. }
  163. }
  164. return true;
  165. }
  166. /**
  167. * -->非葉子結點的分裂
  168. *
  169. * @param node
  170. * @return
  171. */
  172. private RTDirNode[] splitIndex(RTNode node) {
  173. int[][] group = null;
  174. switch (rtree.getTreeType()) {
  175. case Constants.RTREE_LINEAR:
  176. break;
  177. case Constants.RTREE_QUADRATIC:
  178. group = quadraticSplit(node.getNodeRectangle());
  179. children.add(node);// 新加的
  180. node.parent = this;// 新加的
  181. break;
  182. case Constants.RTREE_EXPONENTIAL:
  183. break;
  184. case Constants.RSTAR:
  185. break;
  186. default:
  187. throw new IllegalStateException("Invalid tree type.");
  188. }
  189. // 新建兩個非葉子節點
  190. RTDirNode index1 = new RTDirNode(rtree, parent, level);
  191. RTDirNode index2 = new RTDirNode(rtree, parent, level);
  192. int[] group1 = group[0];
  193. int[] group2 = group[1];
  194. // 為index1添加數據和孩子
  195. for (int i = 0; i < group1.length; i++) {
  196. index1.addData(datas[group1[i]]);
  197. index1.children.add(this.children.get(group1[i]));// 新加的
  198. // 讓index1成為其父節點
  199. this.children.get(group1[i]).parent = index1;// 新加的
  200. }
  201. for (int i = 0; i < group2.length; i++) {
  202. index2.addData(datas[group2[i]]);
  203. index2.children.add(this.children.get(group2[i]));// 新加的
  204. this.children.get(group2[i]).parent = index2;// 新加的
  205. }
  206. return new RTDirNode[] { index1, index2 };
  207. }
  208. @Override
  209. // 尋找葉子
  210. protected RTDataNode findLeaf(Rectangle rectangle) {
  211. for (int i = 0; i < usedSpace; i++) {
  212. if (datas[i].enclosure(rectangle)) {
  213. deleteIndex = i;// 記錄搜索路徑
  214. RTDataNode leaf = children.get(i).findLeaf(rectangle); // 遞歸查找
  215. if (leaf != null)
  216. return leaf;
  217. }
  218. }
  219. return null;
  220. }
  221. }

Rtree

  1. package rtree;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import rtree.Constants;
  5. /**
  6. * @ClassName RTree
  7. * @Description
  8. */
  9. public class RTree {
  10. private RTNode root; // 根節點
  11. private int tree_type; // 樹類型
  12. private int nodeCapacity = -1; // 結點容量
  13. private float fillFactor = -1; // 結點填充因子 ,用于計算每個結點最小條目個數
  14. private int dimension; // 維度
  15. public RTree(int capacity, float fillFactor, int type, int dimension) {
  16. this.fillFactor = fillFactor;
  17. tree_type = type;
  18. nodeCapacity = capacity;
  19. this.dimension = dimension;
  20. root = new RTDataNode(this, Constants.NULL); // 根節點的父節點為NULL
  21. }
  22. /**
  23. * @return RTree的維度
  24. */
  25. public int getDimension() {
  26. return dimension;
  27. }
  28. /** 設置跟節點 */
  29. public void setRoot(RTNode root) {
  30. this.root = root;
  31. }
  32. /**
  33. * @return 填充因子
  34. */
  35. public float getFillFactor() {
  36. return fillFactor;
  37. }
  38. /**
  39. * @return 返回結點容量
  40. */
  41. public int getNodeCapacity() {
  42. return nodeCapacity;
  43. }
  44. /**
  45. * @return 返回樹的類型
  46. */
  47. public int getTreeType() {
  48. return tree_type;
  49. }
  50. /**
  51. * --> 向Rtree中插入Rectangle 1、先找到合適的葉節點 2、再向此葉節點中插入
  52. *
  53. * @param rectangle
  54. */
  55. public boolean insert(Rectangle rectangle) {
  56. if (rectangle == null)
  57. throw new IllegalArgumentException("Rectangle cannot be null.");
  58. if (rectangle.getHigh().getDimension() != getDimension()) // 矩形維度與樹的維度不一致
  59. {
  60. throw new IllegalArgumentException("Rectangle dimension different than RTree dimension.");
  61. }
  62. RTDataNode leaf = root.chooseLeaf(rectangle);
  63. return leaf.insert(rectangle);
  64. }
  65. /**
  66. * 從R樹中刪除Rectangle
  67. * <p>
  68. * 1、尋找包含記錄的結點--調用算法findLeaf()來定位包含此記錄的葉子結點L,如果沒有找到則算法終止。<br>
  69. * 2、刪除記錄--將找到的葉子結點L中的此記錄刪除<br>
  70. * 3、調用算法condenseTree<br>
  71. *
  72. * @param rectangle
  73. * @return
  74. */
  75. public int delete(Rectangle rectangle) {
  76. if (rectangle == null) {
  77. throw new IllegalArgumentException("Rectangle cannot be null.");
  78. }
  79. if (rectangle.getHigh().getDimension() != getDimension()) {
  80. throw new IllegalArgumentException("Rectangle dimension different than RTree dimension.");
  81. }
  82. RTDataNode leaf = root.findLeaf(rectangle);
  83. if (leaf != null) {
  84. return leaf.delete(rectangle);
  85. }
  86. return -1;
  87. }
  88. /**
  89. * 從給定的結點root開始遍歷所有的結點
  90. *
  91. * @param node
  92. * @return 所有遍歷的結點集合
  93. */
  94. public List<RTNode> traversePostOrder(RTNode root) {
  95. if (root == null)
  96. throw new IllegalArgumentException("Node cannot be null.");
  97. List<RTNode> list = new ArrayList<RTNode>();
  98. list.add(root);
  99. if (!root.isLeaf()) {
  100. for (int i = 0; i < root.usedSpace; i++) {
  101. List<RTNode> a = traversePostOrder(((RTDirNode) root).getChild(i));
  102. for (int j = 0; j < a.size(); j++) {
  103. list.add(a.get(j));
  104. }
  105. }
  106. }
  107. return list;
  108. }
  109. public static void main(String[] args) throws Exception {
  110. // 結點容量:4、填充因子:0.4、樹類型:二維
  111. RTree tree = new RTree(4, 0.4f, Constants.RTREE_QUADRATIC, 2);
  112. // 每一行的四個數構成兩個點(一個矩形)
  113. float[] f = { 5, 30, 25, 35, 15, 38, 23, 50, 10, 23, 30, 28, 13, 10, 18, 15, 23, 10, 28, 20, 28, 30, 33, 40, 38,
  114. 13, 43, 30, 35, 37, 40, 43, 45, 8, 50, 50, 23, 55, 28, 70, 10, 65, 15, 70, 10, 58, 20, 63, };
  115. // 插入結點
  116. for (int i = 0; i < f.length;) {
  117. Point p1 = new Point(new float[] { f[i++], f[i++] });
  118. Point p2 = new Point(new float[] { f[i++], f[i++] });
  119. final Rectangle rectangle = new Rectangle(p1, p2);
  120. tree.insert(rectangle);
  121. Rectangle[] rectangles = tree.root.datas;
  122. System.out.println("level:" + tree.root.level);
  123. for (int j = 0; j < rectangles.length; j++)
  124. System.out.println(rectangles[j]);
  125. }
  126. System.out.println("---------------------------------");
  127. System.out.println("Insert finished.");
  128. // 刪除結點
  129. System.out.println("---------------------------------");
  130. System.out.println("Begin delete.");
  131. for (int i = 0; i < f.length;) {
  132. Point p1 = new Point(new float[] { f[i++], f[i++] });
  133. Point p2 = new Point(new float[] { f[i++], f[i++] });
  134. final Rectangle rectangle = new Rectangle(p1, p2);
  135. tree.delete(rectangle);
  136. Rectangle[] rectangles = tree.root.datas;
  137. System.out.println(tree.root.level);
  138. for (int j = 0; j < rectangles.length; j++)
  139. System.out.println(rectangles[j]);
  140. }
  141. System.out.println("---------------------------------");
  142. System.out.println("Delete finished.");
  143. Rectangle[] rectangles = tree.root.datas;
  144. for (int i = 0; i < rectangles.length; i++)
  145. System.out.println(rectangles[i]);
  146. }
  147. }

Constants

  1. package rtree;
  2. import rtree.RTNode;
  3. public class Constants {
  4. public static final int MAX_NUMBER_OF_ENTRIES_IN_NODE = 20;// 結點中的最大條目數
  5. public static final int MIN_NUMBER_OF_ENTRIES_IN_NODE = 8;// 結點中的最小條目數
  6. public static final int RTDataNode_Dimension = 2;
  7. /** Available RTree variants. */ // 樹的類型常量
  8. public static final int RTREE_LINEAR = 0; // 線性
  9. public static final int RTREE_QUADRATIC = 1; // 二維
  10. public static final int RTREE_EXPONENTIAL = 2; // 多維
  11. public static final int RSTAR = 3; // 星型
  12. public static final int NIL = -1;
  13. public static final RTNode NULL = null;
  14. }

參考文章:
1.從B樹、B+樹、B*樹談到R 樹?http://blog.csdn.net/v_JULY_v/article/details/6530142/

2.R樹的Java實現?http://blog.csdn.net/renyisheng/article/details/40347223

3.R樹wiki?https://en.wikipedia.org/wiki/R-tree

轉載于:https://www.cnblogs.com/ExMan/p/9674308.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/279328.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/279328.shtml
英文地址,請注明出處:http://en.pswp.cn/news/279328.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android 中的ORM框架

在android 中&#xff0c;內置了sqlite數據庫&#xff0c;java web 中&#xff0c;用慣了Hibernate &#xff0c;想找找android中是否也有類似的orm框架&#xff0c;后來在開源中國看到了orman&#xff0c;這是一個很不錯的框架。 這個可以幫我們快捷方便的實現數據庫的CURD操作…

android頁面布局 如何讓中間的listview填充剩余部分_谷歌駕駛設計—界面設計布局...

本節提供了可在不同屏幕尺寸范圍內縮放的屏幕布局的設計指南。此處定義的padding和keyline值用于Components&#xff0c;Media規范、Notification Center規范和Dialer規范中。指南概覽&#xff08;TL&#xff1a;DR&#xff09;&#xff1a;基于適當的屏幕尺寸類別的基本布局使…

ios 禁用滑動手勢_如何禁用筆記本電腦上的Windows 8滑動手勢?

ios 禁用滑動手勢If you’re not a fan of the touchpad-based swipe gestures in Windows 8 there is a way to completely disable them and reclaim your touchpad. 如果您不喜歡Windows 8中基于觸摸板的滑動手勢&#xff0c;可以使用一種方法來完全禁用它們并收回您的觸摸板…

Java快速入門-01-基礎篇

Java快速入門-01-基礎篇 如果基礎不好或者想學的很細&#xff0c;請參看&#xff1a;菜鳥教程-JAVA本筆記適合快速學習&#xff0c;文章后面也會包含一些常見面試問題&#xff0c;記住快捷鍵操作&#xff0c;一些內容我就不轉載了&#xff0c;直接附上鏈接&#xff0c;嘻嘻開發…

Excel導入MS SQL SERVER 操作

關于Excel導入到sql操作的相關問題總結&#xff1a; 一、大批量數據導入 方法1、從Excel大批量數據導入時我們可以使用sql里面有一個batch copy的功能 方法2、在sql中建一個table type結構&#xff0c;在前端將excel讀到datatable中&#xff0c;把整個datatable作為存儲過程參數…

蘋果mac閃退_自從Mac有了WPS,從此和雙系統說再見!

薛崗13,712本文共計2266個字&#xff0c;預計閱讀時長需要6分鐘。大部分使用Macbook的用戶都有一個痛點&#xff0c;就是編輯好的office文件&#xff0c;在朋友或同事的windows電腦上展示效果與自己的會有差異。除此外&#xff0c;卡頓、閃退、數據丟失等也是Windows版office在…

初學者計算機_初學者極客:如何在計算機上重新安裝Windows

初學者計算機Reinstalling Windows is one of the easiest ways to fix software problems on your computer, whether it’s running slow or infected by viruses. You should also reinstall Windows before you get rid of an old PC. 重新安裝Windows是修復計算機上軟件問…

win7 32位 安裝opencv-python后,運行時提示 from .cv2 import *: DLL load failed: 找不到指定的模塊 的解決辦法...

安裝opencv后&#xff0c;運行一個測試程序提示"from .cv2 import *: DLL load failed: 找不到指定的模塊"。于是百度一下解決辦法&#xff0c;結果試了N多方法后也沒能解決這個問題。 最后不得不耐心的下載了dependency walker來查看opencv到底是缺少了哪個dll文件。…

goahead處理json_GoAhead Web Server遠程代碼執行漏洞分析(附PoC)

*本文中涉及到的相關漏洞已報送廠商并得到修復&#xff0c;本文僅限技術研究與討論&#xff0c;嚴禁用于非法用途&#xff0c;否則產生的一切后果自行承擔。本文是關于GoAhead web server遠程代碼執行漏洞(CVE-2017-17562)的分析&#xff0c;該漏洞源于在初始化CGI腳本環境時使…

項目中的模塊剝離成項目_使用MCEBuddy 2從電視錄制中剝離廣告

項目中的模塊剝離成項目One of the great things about time-shifting your television viewing is that you are able to watch the shows you love at a time that suits you. Just because you have an appointment on Wednesday evening there’s no need to miss out on y…

有上下界限制可行流

無源匯有上下界限制可行流&#xff08;循環流&#xff09; 即每條邊的流量限制為[L,R]&#xff0c;判斷有沒有滿足整個網絡的可行流。 看看以前學的網絡流&#xff0c;實際上它的流量限制為[0,C]&#xff0c;現在無非多了一個下限的限制。 網絡流的一個重要性質&#xff1a;除了…

.gitignore文件將已經納入版本管理的文件刪除

git rm -r --cached . git add . git commit -m update .gitignore git push -u origin master 先將本地緩存刪除&#xff0c;再提交&#xff0c;.gitignore文件只針對那些沒有被staged的文件有效 參考博客&#xff1a;https://www.cnblogs.com/kevingrace/p/5690241.html 轉載…

gmail收件箱標簽設置_通過在Gmail中啟用實驗室功能來啟動收件箱

gmail收件箱標簽設置We recently looked at how you can make it easier to manage multiple inboxes in Gmail using the Multiple Inboxes Lab feature. This is a non-standard feature and it’s far from being the only one available to you. In fact there are numerou…

linux rmp命令安裝包在哪里_rpm命令_Linux rpm 命令用法詳解:RPM軟件包的管理工具...

rpm命令是RPM軟件包的管理工具。rpm原本是Red Hat Linux發行版專門用來管理Linux各項套件的程序&#xff0c;由于它遵循GPL規則且功能強大方便&#xff0c;因而廣受歡迎。逐漸受到其他發行版的采用。RPM套件管理方式的出現&#xff0c;讓Linux易于安裝&#xff0c;升級&#xf…

【題解】洛谷P1066 [NOIP2006TG] 2^k進制數(復雜高精+組合推導)

洛谷P1066&#xff1a;https://www.luogu.org/problemnew/show/P1066 思路 挺難的一道題 也很復雜 滿足題目要求的種數是兩類組合數之和 r的最多位數m為 w/k&#xff08;當w mod k0 時&#xff09;w/k1&#xff08;當 w mod k1 時&#xff09;First: 位數為2~m的種數 即從2k-1中…

cmd命令不識別exp_Cmder-超量級的Cmd

Windows命令行工具cmd缺點窗口size不能便捷縮放復制文本&#xff0c;不能直接用鼠標拷貝&#xff0c;還需要多一道菜單操作&#xff1b;而且&#xff0c;還只能塊狀拷貝&#xff0c;而不是按行字符&#xff0c;極其不便不支持多Tab頁&#xff0c;多窗口管理不便cmd界面丑陋&…

sizeof string

char a[] "hello"; string s "hello"; cout<<sizeof(a)<<endl; cout<<sizeof(s)<<endl; cout<<sizeof(s.c_str())<<endl;輸出為 6 32 4最后一個c_str返回的是char*,所有指針的長度都為4。sizeof(s)為什么為32&#…

iTOP-4412開發板實現3路ADC數模轉換驅動例程

學習下 linux 數模程序驅動的編寫&#xff0c;本節我們實現的功能是實現三路ADC 數模轉換。驅動程序驅動程序的名字&#xff1a;“itop4412_adc.c”。要想把這個驅動注冊到內核,先把這個驅動程序放到內核的“driver/char”目錄下面&#xff0c;如下圖所示&#xff1a; Makefile…

β射線與哪些物質可產生較高的韌致輻射_輻射無所不在,香蕉土豆里都有?我們還能愉快生活嗎?...

作為一枚受過系統科學教育&#xff0c;耳聰目明的當代年輕人&#xff0c;你是不是隔三差五被長輩親友群里各種“XX有放射性&#xff0c;趕緊遠離&#xff01;”的科學謠言搞得哭笑不得&#xff1f;又或者&#xff0c;稍一不注意&#xff0c;長輩親友就買回了各種號稱黑科技滿滿…

requests保存圖片

1.創建07_save_jpg.py文件 import requests#發送請求respone requests.get("https://www.baidu.com/img/bd_logo1.png?wheresuper")#保存with open("a.png","wb")as f: f.write(respone.content)2.運行代碼 轉載于:https://www.cnblogs.com…