2014-01-27 127 views
3

我正在開發一種類似於二叉樹的結構,但通用於各維,因此您可以通過在初始化過程中設置維參數來設置它是二叉樹,四叉樹,八叉樹等。查找樹中的相鄰節點

這裏是它的定義:

template <uint Dimension, typename StateType> 
class NDTree { 
public: 
    std::array<NDTree*, cexp::pow(2, Dimension)> * nodes; 
    NDTree * parent; 
    StateType state; 
    char position; //position in parents node list 
    bool leaf; 

    NDTree const &operator[](const int i) const 
    { 
     return (*(*nodes)[i]); 
    } 

    NDTree &operator[](const int i) 
    { 
     return (*(*nodes)[i]); 
    } 
} 

所以,初始化它 - 我設置尺寸,然後細分。我打算深度2的四叉樹用於說明這裏:

const uint Dimension = 2; 
NDTree<Dimension, char> tree; 
tree.subdivide(); 

for(int i=0; i<tree.size(); i++) 
    tree[i].subdivide(); 

for(int y=0; y<cexp::pow(2, Dimension); y++) { 
    for(int x=0; x<cexp::pow(2, Dimension); x++) { 
     tree[y][x].state = ((y)*10)+(x); 
    } 
} 
std::cout << tree << std::endl; 

這將導致在四叉樹,每個值的狀態被初始化爲[0-4] [0-4]。

([{0}{1}{2}{3}][{10}{11}{12}{13}][{20}{21}{22}{23}][{30}{31}{32}{33}]) 

我無法從任何一塊中找到相鄰節點。它需要做的是採取一個方向,然後(如有必要)在樹的方向離開父節點邊緣時向上遍歷樹(例如,如果我們在四叉樹的右下方,並且需要獲得一塊在它的右邊)。我的算法返回僞造值。

這裏是陣列的佈局方式:

enter image description here

這裏有必要了解它的結構:

這僅僅適用於項目的方向。

enum orientation : signed int {LEFT = -1, CENTER = 0, RIGHT = 1}; 

這有一個方向和是否要深入。

template <uint Dimension> 
struct TraversalHelper { 
    std::array<orientation, Dimension> way; 
    bool deeper; 
}; 

node_orientation_table保存結構中的方向。所以在2d中,0 0指的是左上方(或左上方)。 [[LEFT,LEFT],[RIGHT,LEFT],[LEFT,RIGHT],[右,右]]

和函數getPositionFromOrientation將採取LEFT,LEFT和返回0。這僅僅是基本上相反上面的node_orientation_table。

TraversalHelper<Dimension> traverse(const std::array<orientation, Dimension> dir, const std::array<orientation, Dimension> cmp) const 
{ 
    TraversalHelper<Dimension> depth; 

    for(uint d=0; d < Dimension; ++d) { 
     switch(dir[d]) { 
      case CENTER: 
       depth.way[d] = CENTER; 
       goto cont; 

      case LEFT: 
       if(cmp[d] == RIGHT) { 
        depth.way[d] = LEFT; 
       } else { 
        depth.way[d] = RIGHT; 
        depth.deeper = true; 
       } 
       break; 

      case RIGHT: 
       if(cmp[d] == LEFT) { 
        depth.way[d] = RIGHT; 
       } else { 
        depth.way[d] = LEFT; 
        depth.deeper = true; 
       } 
       break; 
     } 

     cont: 
      continue; 
    } 

    return depth; 
} 

std::array<orientation, Dimension> uncenter(const std::array<orientation, Dimension> dir, const std::array<orientation, Dimension> cmp) const 
{ 
    std::array<orientation, Dimension> way; 

    for(uint d=0; d < Dimension; ++d) 
     way[d] = (dir[d] == CENTER) ? cmp[d] : dir[d]; 

    return way; 
} 

NDTree * getAdjacentNode(const std::array<orientation, Dimension> direction) const 
{ 
    //our first traversal pass 
    TraversalHelper<Dimension> pass = traverse(direction, node_orientation_table[position]); 

    //if we are lucky the direction results in one of our siblings 
    if(!pass.deeper) 
     return (*(*parent).nodes)[getPositionFromOrientation<Dimension>(pass.way)]; 


    std::vector<std::array<orientation, Dimension>> up; //holds our directions for going up the tree 
    std::vector<std::array<orientation, Dimension>> down; //holds our directions for going down 
    NDTree<Dimension, StateType> * tp = parent;   //tp is our tree pointer 
    up.push_back(pass.way); //initialize with our first pass we did above 

    while(true) { 
     //continue going up as long as it takes, baby 
     pass = traverse(up.back(), node_orientation_table[tp->position]); 
     std::cout << pass.way << " :: " << uncenter(pass.way, node_orientation_table[tp->position]) << std::endl; 

     if(!pass.deeper) //we've reached necessary top 
      break; 
     up.push_back(pass.way); 

     //if we don't have any parent we must explode upwards 
     if(tp->parent == nullptr) 
      tp->reverseBirth(tp->position); 

     tp = tp->parent; 
    } 

    //line break ups and downs 
    std::cout << std::endl; 

    //traverse upwards combining the matrices to get our actual position in cube 
    tp = const_cast<NDTree *>(this); 
    for(int i=1; i<up.size(); i++) { 
     std::cout << up[i] << " :: " << uncenter(up[i], node_orientation_table[tp->position]) << std::endl; 
     down.push_back(uncenter(up[i], node_orientation_table[tp->parent->position])); 
     tp = tp->parent; 
    } 

    //make our way back down (tp is still set to upmost parent from above) 
    for(const auto & i : down) { 
     int pos = 0; //we need to get the position from an orientation list 

     for(int d=0; d<i.size(); d++) 
      if(i[d] == RIGHT) 
       pos += cexp::pow(2, d); //consider left as 0 and right as 1 << dimension 

     //grab the child of treepointer via position we just calculated 
     tp = (*(*tp).nodes)[pos]; 
    } 

    return tp; 
} 

對於這樣的例子:

std::array<orientation, Dimension> direction; 
direction[0] = LEFT; //x 
direction[1] = CENTER; //y 

NDTree<Dimension> * result = tree[3][0]->getAdjacentNode(direction); 

enter image description here

這應該抓住左下方,例如內右上方tree[2][1]如果我們讀取它的狀態,它的值就是21。自從我上次編輯(算法被修改)後,這起作用。不過,許多查詢不會返回正確的結果。

//Should return tree[3][1], instead it gives back tree[2][3] 
NDTree<Dimension, char> * result = tree[1][2].getAdjacentNode({ RIGHT, RIGHT }); 

//Should return tree[1][3], instead it gives back tree[0][3] 
NDTree<Dimension, char> * result = tree[3][0].getAdjacentNode({ RIGHT, LEFT }); 

還有更多錯誤行爲的例子,例如tree [0] [0](LEFT,LEFT),但其他許多工作正常。

Here是我正在與此工作的git回購的文件夾。如果有必要,請從該目錄運行g++ -std=c++11 main.cpp

+0

我無法編譯。它正在尋找'gmp.h'。你能說你正在使用的所有庫嗎? – Gene

+0

進入test/treet文件夾並從那裏進行編譯。根文件夾在問題之外使用不同的代碼。 – jett

+0

謝謝。它在這裏編譯得很好。這是一個相當巴洛克式的實現。大量的冗餘信息。你確定你不想要更多的空間保守嗎?空間通常是現實2^n-tree應用程序中的問題。 – Gene

回答

2

這裏是一個屬性,你可以嘗試利用: 只考慮4個節點:

00 01 
10 11 

任何節點都可以有多達4個鄰居節點;兩個將存在於相同的結構(較大的正方形)中,並且您必須在相鄰的結構中尋找其他兩個。 讓我們着重於識別處於相同結構中的鄰居:00的鄰居是01和10; 11的鄰居是01和10。請注意,相鄰節點之間只有一個位不同,並且鄰居可以分爲水平和垂直兩種。 SO

 00 - 01 00 - 01 //horizontal neighbors 
    |    | 
    10    11 //vertical neighbors 

注意如何翻轉MSB得到垂直鄰居和翻轉LSB獲得水平的節點?讓我們來仔細看看:

MSB: 0 -> 1 gets the node directly below 
     1 -> 0 sets the node directly above 

    LSB: 0 -> 1 gets the node to the right 
     1 -> 0 gets the node to the left 

所以現在我們可以判斷該節點的每個方向假定它們在同一子存在。那麼00或10以上的節點呢?根據目前的邏輯,如果你想要一個水平的鄰居,你應該翻轉LSB;但翻轉它會檢索10(右側的節點)。因此,讓我們爲不可能的操作添加新規則:

you can't go left for x0 , 
    you can't go right for x1, 
    you can't go up for 0x, 
    you can't go down for 1x 

*不可能的操作是指在同一結構中的操作。 讓我們看看00的最大和最左邊的鄰居的大圖。如果我們離開結構0(S0)的00,我們應該以(S1)的01結束,如果我們走了,我們結束S(2)的結點10。 注意,它們基本上與S(0)形式的水平/垂直鄰居值相同,只是它們具有不同的結構。所以基本上,如果我們弄清楚如何從一個結構跳到另一個結構,我們有一個算法。我們回到我們的例子:從節點00(S0)上去。我們應該在S2中結束;所以再次00-> 10翻轉MSB。所以如果我們應用我們在結構中使用的相同算法,我們應該沒問題。

底線:一個strucutres

MSB 0, go down 
    1, go up 
LSB 0, go right 
    1, go left 

for invalid transitions (like MSB 0, go up) 
determine the neighbor structure by flipping the MSB for vertical and LSB for vertical 
and get the neighbor you are looking for by transforming a illegal move in structure A 
into a legal one in strucutre B-> S0: MSB 0 up, becomes S2:MSB 0 down. 

我希望這個想法是不夠明確的

+0

是的,這就是我在走路時想要做的。它確實有助於看到其他人寫出來。 – jett

+0

所以我重寫了我的算法,稍微有點不同,我對它進行了更深入的研究,它解決了我的舊算法(但不是全部)的一些問題。 – jett

+0

好的,你現在面臨什麼問題? – Pandrei

1

看看這個答案爲鄰居搜索八分之一:https://stackoverflow.com/a/21513573/3146587。基本上,您需要在節點中記錄從根節點到節點的遍歷,並操縱此信息以生成到達相鄰節點的所需遍歷。

+0

感謝您的鏈接;有了這個結構,就沒有一個靜態根(並且更新新根的所有節點的代價非常昂貴)。我必須從當前節點向上進行本地方法,而不是自上而下。 – jett

+0

然後,您可以通過步行直到找到根,在我引用的答案(從根節點到節點的遍歷記錄)中生成所謂的「nD索引」。並且計算所需鄰居的「nD索引」仍然適用,以及相應的遍歷來找到這個鄰居。 – user3146587

+0

我不喜歡每次都要去根的最壞情況;我知道如果在節點父節點的節點列表中有位置,就可以不必一路走下去,例如,父母,職位和孩子。大概有1/2^n的機會不得不每次都更高,所以默認情況下完整的路線是很多額外的工作。 – jett

0

我能想到的最簡單的答案是從你的樹的根拿回你的節點內 有效過渡。

每個單元都可以分配一個座標映射到樹的最深節點。在你的例子中,(x,y)座標範圍從0到2 尺寸 -1即0到3.

首先,用任何你喜歡的算法計算鄰居座標(例如,從邊緣向右移動應該包裹到同一行的第一個單元格,向下移動到下一行或保持原位)。

然後,將新座標輸入到常規搜索功能。它將返回dimension步驟中的鄰居單元。

您可以通過查看座標的二進制值來優化。基本上,差異最重要的排名告訴你應該去多少層次。

例如,讓我們深度4.座標範圍的四叉樹從0到15

假設我們從去細胞數5(0101B)離開。新座標是4(0100b)。最有意義的位是0,這意味着你可以在當前塊中找到鄰居。

現在,如果你向右走,新的座標是6(0110b),所以這個變化影響到位1,這意味着你必須上升一級來訪問你的單元格。

所有這一切都表明,使用這些技巧所需的代碼的計算時間和數量看起來幾乎不值得我付出努力。