查找树中的相邻节点

时间:2014-01-27 09:56:38

标签: c++ algorithm tree tree-traversal

我正在开发一个类似二叉树的结构,但是跨维度进行概括,因此您可以通过在初始化期间设置维度参数来设置它是二叉树,四叉树,八叉树等。

以下是它的定义:

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指向左上方(或左左方)。     [[左,左],[右,左],[左,右],[右,右]]

函数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 });

还有更多不正确行为的例子,例如树[0] [0](左,左),但许多其他行为正常。

Here是我正在处理的git repo的文件夹。如果有必要,只需从该目录运行g++ -std=c++11 main.cpp

3 个答案:

答案 0 :(得分: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的0(S0),我们应该以(S1)的01结束,如果我们上升,我们最终得到S(2)的节点10。 请注意,它们基本上是相同的水平/相对于S(0)的相邻值,只是它们处于不同的结构中。所以基本上如果我们弄清楚如何从一个结构跳到另一个结构,我们有一个算法。    让我们回到我们的例子:从节点00(S0)上升。我们应该最终进入S2;所以再次00-> 10翻转MSB。因此,如果我们应用我们在结构中使用的相同算法,我们应该没问题。

底线:     结构中的有效转换

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.   

我希望这个想法足够明确

答案 1 :(得分:1)

在八叉树中查看邻居搜索的答案:https://stackoverflow.com/a/21513573/3146587。基本上,您需要在节点中记录从根到节点的遍历,并操纵此信息以生成到达相邻节点所需的遍历。

答案 2 :(得分:0)

我能想到的最简单的答案是从树的根部取回你的节点。

可以为每个单元格分配一个坐标映射到树的最深节点。在您的示例中,(x,y)坐标的范围为0到2 dimension -1,即0到3.

首先,使用您喜欢的任何算法计算邻居的坐标(例如,确定从边缘向右移动是否应该包裹到同一行的第一个单元格,下到下一行或保持原位)

然后,将新坐标提供给常规搜索功能。它将以dimension步骤返回相邻小区。

您可以通过查看坐标的二进制值来优化它。基本上,最重要的差异等级告诉你应该走多少级别。

例如,让我们采用深度为4的四叉树。坐标范围为0到15。

假设我们从第5号单元格(0101b)向左移动。新坐标为4(0100b)。更改的最高位是位0,这意味着您可以在当前块中找到邻居。

现在如果你向右走,新的坐标是6(0110b),所以这个改变正在影响第1位,这意味着你必须上升一个级别来访问你的单元格。

所有这些都说,使用这些技巧所需的代码的计算时间和数量对我来说似乎不值得。