将用户放在二叉树中

时间:2017-06-12 22:55:27

标签: php laravel

如何在二叉树中找到放置用户的位置?考虑以下二叉树:

          1
      /        \
     2          3
   /   \       / \
  4     5     6   7
 / \   / \
8   9 10  11

如何构建一个函数来返回下一个用户应该放在哪里?

此处的规则是,用户必须在其赞助商下的树中尽可能高地放置,每个节点均匀地获取它们。例如,对于任何以1作为赞助商的人来说:下一个用户应该是6,一个是7,然后是6,然后是7,然后是8。

如果有人拥有5的赞助商,他们将获得10岁以下的赞助商。他们将获得8岁以下,等等。

我已经被困了好几个小时试图解决这个问题,并建立了一个递归函数,但它只是沿着左侧下降并且首先落在8上(因为它检查1,然后是2,然后是4,然后是8)

我的树模型非常基本(如果相关),它是一个具有自我关系的表,如下所示:

id | user_id | PARENT_ID

因此每个用户只能有2个孩子(2个条目的id为parent_id)

1 个答案:

答案 0 :(得分:1)

通常当你进行递归时,你最终会得到你所得到的东西,你向下飞过一边(在这种情况下是最左边的),随时检查一切,然后最终在树的底部找到一些东西。

您想要做的是使用先进先出(“FIFO”)堆栈。首先将树添加到堆栈中,while堆栈上有任何内容,但仍然没有找到空间,循环。每个循环都会从堆栈中获取第一个项目并再次循环以检查每个子项的空间。如果找到空格,则将子项放在一边,如果找不到空格,则将子项添加到堆栈的末尾,以便我们在以后的迭代中检查子项的子项。你一直这样做,直到找到一些空间。检查完所有子节点后,如果找到任何空间,则比较预留的节点,以查看哪个节点具有最大可用空间并且是您的目标。这使您可以通过从堆栈的开头拉出东西并在最后添加新内容来进行水平搜索。你最终会在孩子面前检查每个邻居。

这是一个示例,它将返回对找到的第一个打开节点的引用:

<?php
$tree = [
    1 => [
        2 => [
            4 => [
                8 => [],
                9 => [],
            ],
            5 => [
                10 => [],
                11 => [],
            ],
        ],
        3 => [
            6 => [],
            7 => [],
        ],
    ]
];

function &findNextLocation(&$tree, $maxChildren=2){
    //shortcut, check the root tree first
    if(count($tree) < $maxChildren){
        return $tree;
    }

    //fifo stack, start with the whole tree.
    //we use a FIFO stack here so that we can check all nodes horizontally
    //before traversing down another level. This stores all the child nodes
    //we still need to search.
    $stack = array(&$tree);

    //potential place with space
    $out = null;

    //go through and check everything
    //loop while there is something on the stack and we haven't
    //found space yet ($out is null).
    //we check $out here so that we stop as soon as somewhere with
    //space is found and assigned.
    while(is_null($out) && !empty($stack)){
        //get the first node from the tree
        $branch = &$stack[0];
        array_shift($stack);

        //loop over every node at this branch and look for space
        foreach($branch as $id=>&$node){
            //if there is space, assign it to our output variable
            if(count($node) < $maxChildren){
                //check the number of open spaces compared to our out spaces
                if(is_null($out) || count($out) > count($node)){
                    //better spot, assign this node
                    $out = &$node;

                    //if anyone has zero children, we can't find less than that so break
                    if(count($out) == 0){
                        break;
                    }
                }
            } else {
                //space not found here, add to our stack so we check the children
                $stack[] = &$node;
            }
        }
    }

    //not found
    return $out;
}

print_r($tree);

//loop a few more times starting at our next number
for($i=12; $i<=20; $i++){
    //get a reference to the open node
    $node = &findNextLocation($tree);
    //add this node as a child
    $node[$i] = [];
    //remove reference to the found node to prevent errors.
    unset($node);
}

print_r($tree);

您可以在此处查看演示:https://3v4l.org/X3BX4

您描述它的方式,听起来像是在SQL表中。您可以使用上述内容查找位置并将新值插入现有树。或者,如果向表中添加另一列depth表示与根节点的距离,则可以在查询中完成所有操作。深度不容易计算,因为它需要递归,这对于过程或用户函数来说是不可用的。但是,只需父级深度+1即可轻松更新当前记录。使用深度列,您可以执行以下操作:

SELECT *
FROM tree_table as tt
WHERE
    #where there is space (less than 2 children)
    (SELECT COUNT(*)
        FROM tree_table
        WHERE parent_id=tt.id) < 2
ORDER BY
    #distance from top of tree
    depth,
    #by free space
    (SELECT COUNT(*)
        FROM tree_table
        WHERE parent_id=tt.id),
    #leftmost
    id

在这里演示:http://sqlfiddle.com/#!9/5f6bfc/5