C 如何找到树的最后一级上的最后一个(最右边的)节点,这是一个堆(几乎完整的树)?

C 如何找到树的最后一级上的最后一个(最右边的)节点,这是一个堆(几乎完整的树)?,c,data-structures,queue,heap,C,Data Structures,Queue,Heap,我试图在堆的最后一级(树表示法)中找到最右边的节点,以便删除最小/最大堆中的特定元素 几乎所有的在线用户都在写文章,用堆中最右边的节点替换要删除的节点,该节点位于最低级别,我完全理解,但是我如何才能找到最后一个节点呢 根据我的解决方案:我有一个解决方案,使用级别顺序遍历(广度优先搜索)遍历该树(堆结构)在存储节点的地址时-当队列中只剩下一个元素没有子节点时,我将使用该元素进行替换。本例中最右边的节点是33: 是否有其他方法/链接可供我使用,因为使用队列似乎很长?让我们看一个完整的二叉树,忽略存

我试图在堆的最后一级(树表示法)中找到最右边的节点,以便删除最小/最大堆中的特定元素

几乎所有的在线用户都在写文章,用堆中最右边的节点替换要删除的节点,该节点位于最低级别,我完全理解,但是我如何才能找到最后一个节点呢

根据我的解决方案:我有一个解决方案,使用级别顺序遍历(广度优先搜索)遍历该树(堆结构)在存储节点的地址时-当队列中只剩下一个元素没有子节点时,我将使用该元素进行替换。本例中最右边的节点是33


是否有其他方法/链接可供我使用,因为使用队列似乎很长?

让我们看一个完整的二叉树,忽略存储在节点上的值,但对节点进行编号,从根1开始编号: 如果我们从根遍历到任何目标节点(根本身除外),左边缘(红色)为0,右边缘(蓝色)为1,我们会看到一个模式:

Path     Edges   Target (binary)
─────    ──────  ───────────────
1 → 2    0       1 0
1 → 3    1       1 1
1 → 4    0 0     1 0 0
1 → 5    0 1     1 0 1
1 → 6    1 0     1 1 0
1 → 7    1 1     1 1 1
1 → 8    0 0 0   1 0 0 0
1 → 9    0 0 1   1 0 0 1
1 → 10   0 1 0   1 0 1 0
1 → 11   0 1 1   1 0 1 1
1 → 12   1 0 0   1 1 0 0
1 → 13   1 0 1   1 1 0 1
1 → 14   1 1 0   1 1 1 0
1 → 15   1 1 1   1 1 1 1
从根到所需节点的路径与该节点编号的二进制表示形式相同(根为1),忽略最重要的二进制数字

因此,在一个完整的树中,要到达
K
的第四个节点,根为1,我们首先找到小于
K
的两个节点的最大幂,然后根据其下方的二进制数字按降序遍历,零表示左,一表示右

假设我们的节点结构类似于

typedef  struct node  node;
struct node {
    struct node  *left;
    struct node  *right;
    /* plus node data fields */
};
然后查找根节点的
i
th节点,
i=1
,可以实现如下

node *ith_node(node *root, const size_t i)
{
    size_t  b = i; 

    /* Sanity check: If no tree, always return NULL. */
    if (!root || i < 1)
        return NULL;

    /* If i is 1, we return the root. */
    if (i == 1)
        return root;

    /* Set b to the value of the most significant binary digit
       set in b. This is a known trick. */
    while (b & (b - 1))
        b &= b - 1;        

    /* We ignore that highest binary digit. */
    b >>= 1;

    /* Walk down the tree as directed by b. */
    while (b) {
        if (i & b) {
            if (root->right)
                root = root->right;
            else
                return NULL; /* Not a complete tree, or outside the tree. */
        } else {
            if (root->left)
                root = root->left;
            else
                return NULL; /* Not a complete tree, or outside the tree. */
        }

        /* Next step. */
        b >>= 1;
    }

    /* This is where we arrived at. */
    return root;
}
请注意,上述函数是在完成树的基础上进行预测的:即,除最后一个之外的所有级别都已填充,最后一个级别的所有最左侧节点都已填充。也就是说,如果使用上图所示编号的节点N已填充,则节点1到N-1也必须填充

上面示例中的逻辑是有效的。但是,由于示例代码是一次性编写的,没有经过适当的审查,因此可能存在错误。因此,如果您对示例代码有任何问题,或者在回答中的任何地方有任何问题,请在注释中告诉我,以便我可以根据需要进行检查和修复


请注意,二进制堆通常使用数组表示

(为了使用正确的数组索引,我们在这里切换到基于零的索引;即从这一点开始,根位于索引0处。)

节点没有指针。为了支持删除,我们通常将索引存储到节点所在的堆数组中,否则节点只有数据。(如果需要更改键值或删除除根以外的项,通常会添加一个指定当前堆数组索引的数据字段。不过,这会降低速度,因此通常不需要。为了简单起见,我将省略它。)

注意,
中的引用数组是根据需要动态分配/重新分配的,因此堆的大小没有固有的限制(当然,内存除外)

HEAP\u INIT
宏允许在声明时初始化堆。换句话说,
HEAP h=HEAP\u INIT;
相当于
HEAP h;HEAP\u INIT(&h);

向这样的堆中添加新元素非常简单:

static int heap_add(heap *h, heap_data *d, const heap_key k)
{
    size_t  i;

    if (!h)
        return -1; /* No heap specified. */

    /* Ensure there is room for at least one more entry. */
    if (h->len >= h->max) {
        size_t     max;
        reference *ref;

        /* Minimum size is 15 references; then double up
           to 1966080 entries; then set next multiple of
           1024*1024 + 1024*1024-2. */
        if (h->len < 15)
            max = 15;
        else
        if (h->len < 1966080)
            max = 2 * h->len;
        else
            max = (h->len | 1048575) + 1048574;

        ref = realloc(h->ref, max * sizeof h->ref[0]);
        if (!ref)
            return -2; /* Out of memory; cannot add more. */

        h->max = max; 
        h->ref = ref;
    }

    i = h->len++;
    h->ref[i].key = key;
    h->ref[i].val = data;

    /* Omitted:  Percolate 'i' towards root,
                 keeping the heap order property for keys. */

    /* if (!i) "i is root";
       For all other cases, the parent is at index ((i-1)/2), and
       if (i&1) "i is a left child, sibling is (i+1)";
       else     "i is a right child, sibling is (i-1)";
    */

    return 0;
}

同样,上述示例中的逻辑是有效的。但是,由于示例代码是一次性编写的,没有经过适当的审查,因此可能存在错误。因此,如果您对示例代码有任何问题,或者在回答中的任何地方有任何问题,请在注释中告诉我,以便我可以根据需要进行检查和修复。

让我们看看完整的bi不规则树,忽略存储在节点上的值,但对节点进行编号,从根的1开始编号: 如果我们从根遍历到任何目标节点(根本身除外),左边缘(红色)为0,右边缘(蓝色)为1,我们会看到一个模式:

Path     Edges   Target (binary)
─────    ──────  ───────────────
1 → 2    0       1 0
1 → 3    1       1 1
1 → 4    0 0     1 0 0
1 → 5    0 1     1 0 1
1 → 6    1 0     1 1 0
1 → 7    1 1     1 1 1
1 → 8    0 0 0   1 0 0 0
1 → 9    0 0 1   1 0 0 1
1 → 10   0 1 0   1 0 1 0
1 → 11   0 1 1   1 0 1 1
1 → 12   1 0 0   1 1 0 0
1 → 13   1 0 1   1 1 0 1
1 → 14   1 1 0   1 1 1 0
1 → 15   1 1 1   1 1 1 1
从根到所需节点的路径与该节点编号的二进制表示形式相同(根为1),忽略最重要的二进制数字

因此,在一个完整的树中,要到达
K
的第四个节点,根为1,我们首先找到小于
K
的两个节点的最大幂,然后根据其下方的二进制数字按降序遍历,零表示左,一表示右

假设我们的节点结构类似于

typedef  struct node  node;
struct node {
    struct node  *left;
    struct node  *right;
    /* plus node data fields */
};
然后查找根节点的
i
th节点,
i=1
,可以实现如下

node *ith_node(node *root, const size_t i)
{
    size_t  b = i; 

    /* Sanity check: If no tree, always return NULL. */
    if (!root || i < 1)
        return NULL;

    /* If i is 1, we return the root. */
    if (i == 1)
        return root;

    /* Set b to the value of the most significant binary digit
       set in b. This is a known trick. */
    while (b & (b - 1))
        b &= b - 1;        

    /* We ignore that highest binary digit. */
    b >>= 1;

    /* Walk down the tree as directed by b. */
    while (b) {
        if (i & b) {
            if (root->right)
                root = root->right;
            else
                return NULL; /* Not a complete tree, or outside the tree. */
        } else {
            if (root->left)
                root = root->left;
            else
                return NULL; /* Not a complete tree, or outside the tree. */
        }

        /* Next step. */
        b >>= 1;
    }

    /* This is where we arrived at. */
    return root;
}
请注意,上述函数是在完成树的基础上进行预测的:即,除最后一个之外的所有级别都已填充,最后一个级别的所有最左侧节点都已填充。也就是说,如果使用上图所示编号的节点N已填充,则节点1到N-1也必须填充

上面示例中的逻辑是有效的。但是,由于示例代码是一次性编写的,没有经过适当的审查,因此可能存在错误。因此,如果您对示例代码有任何问题,或者在回答中的任何地方有任何问题,请在注释中告诉我,以便我可以根据需要进行检查和修复


请注意,二进制堆通常使用数组表示

(为了使用正确的数组索引,我们在这里切换到基于零的索引;即从这一点开始,根位于索引0处。)

节点没有指针。为了支持删除,我们通常将索引存储到节点所在的堆数组中,否则节点只有数据。(如果需要更改键值或删除除根以外的项,通常会添加一个指定当前堆数组索引的数据字段。不过,这会降低速度,因此通常不需要。为了简单起见,我将省略它。)

N