【问题标题】:counting number of elements less than X in a BST计算 BST 中小于 X 的元素数
【发布时间】:2022-01-06 10:10:38
【问题描述】:

我使用下面的 C++ 代码为多集实现了 BST,而每个节点包含每个不同数字 data 的出现次数 num,我尝试找到小于某个值 x 的元素数,使用下面的order 函数。

但是,它在执行时间方面效率低下。 有没有时间复杂度更好的方法?

struct Node {
    int data;
    int height;
    int num;

    Node *left;
    Node *right;
};
int order(Node *n, int x) {
    int sum = 0;
    if (n != NULL) {
        if (n->data < x) {
            sum += n->num;
            sum += order(n->right, x);
        }
        sum += order(n->left, x);
    }
    return sum;
}

【问题讨论】:

  • 什么是 Node::num?
  • 那个数(数据)出现的次数,比如2出现一次,代表它的节点就会有data=2和num=1
  • 一般来说,递归函数并不是最有效的。观察有多少相同的int x 副本。
  • 另一个观察 - 如果Node 有一个“父”指针,您可以遍历整个树而不需要任何堆栈。
  • 我认为你不能走得更快,除非你在每个节点中存储更多数据(但插入会花费更长的时间)。例如。每个节点可以包含以它为根的整个子树中的值的总数。然后对于n-&gt;data &lt; x,您已经准备好答案,并且只需要在n-&gt;data &gt;= x 时检查左子树。这将成为尾递归,并且可以转换为迭代。

标签: c++ c++11 search binary-search-tree


【解决方案1】:

如果您可以将大小添加到您的结构中,我强烈建议您使用 Dario Petrillo 的答案。

如果你必须坚持你的结构,你可以减少指令和递归的数量。

int count_all(Node* n) {
    int acc = n->num;
    if (n->left != NULL) acc += count_all(n->left);
    if (n->right != NULL) acc += count_all(n->right);
    return acc;
}

int order(Node *n, int x) {
    if (n == NULL) return 0;
    
    // Find the first left node which is < x
    while (n->data >= x) {
        n = n->left;
        if (n == NULL) return 0;
    }
    
    assert(n != NULL && n->data < x);
    
    int sum = n->num;
    
    // Grab everything left because all of them are < x
    if (n->left != NULL) sum += count_all(n->left);
    
    // Some of the right nodes may be < x, some may not
    // Repeat the algorithm to find out
    if (n->right != NULL) sum += order(n->right, x);

    return sum;
}

当根大于x 并且您想快速找到满足n-&gt;data &lt; x 的下一个左节点时,这会减少递归次数。它还消除了与x 的大量不必要的比较,因为您已经知道所有内容都是&lt; x。

【讨论】:

    【解决方案2】:

    您可以通过在每个节点中存储它作为根的子树中元素的数量来将算法降低到 O(logN) 时间。然后你只需要递归每个节点的两个子节点之一(如果x &lt; node-&gt;data,则向左,如果x &gt; node-&gt;data,则向右),如果树是平衡的,则只需要对数时间。

    struct Node {
        int data;
        int height;
        int num;
        int size; // numer of elements in the subtree starting at this node
    
        Node *left;
        Node *right;
    };
    
    int order(Node *n, int x) {
        if(n == NULL) return 0;
    
        // elements less than n->data make up the whole left subtree
        if (x == n->data) {
            return n->left ? n->left->size : 0;
        }
        // even smaller? just recurse left
        else if (x < n->data) {
            return order(n->left, x);
        }
        // bigger? take the whole left subtree and part of the right one
        else {
            return (n->left ? n->left->size : 0) + order(n->right, x);
        }
    }
    

    当然,现在您必须跟踪大小,但这在更新树时可以非常有效地完成:只需重新计算插入或删除中每个修改节点的大小 (n-&gt;left-&gt;size + n-&gt;right-&gt;size + 1)。

    【讨论】:

    • 通常项目以随机顺序插入 BST os 如何在添加新项目时更新每个项目的 size 而不会影响复杂性?
    • @MarekR 在插入您沿着树中的路径行进时,只需重新计算递归函数末尾的大小,以便您从底部更新路径。通过这种方式,您可以更新所有可能以相同的插入成本更改的节点,这是对数的。删除也是一样,备份时更新
    猜你喜欢
    • 2019-06-14
    • 1970-01-01
    • 2014-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多