【问题标题】:Time complexity of finding k successors in BST在 BST 中找到 k 个后继的时间复杂度
【发布时间】:2017-03-26 11:11:46
【问题描述】:

给定高度为 h 的二叉搜索树 (BST),需要 O(k+h) 连续应用BST InOrder Successor algorithm k 次的时间,从任何节点开始,在上一个调用返回的节点上应用每个下一个调用。 p>

伪代码:

get_kth_successor(node):
    for times = 1 to k:
        node = successor(node)
    return node

我如何证明这个时间复杂度?

特别是,我试图在 k 和访问的节点数之间建立关系,但在这里找不到任何模式。

【问题讨论】:

  • 你的意思是从任意一个节点开始,找到k个后继者,还是找到任意k个节点的后继者?
  • 你连续 k 次调用是为了什么???这是什么树后继算法。请给出清楚的解释
  • 1.) 从任意节点开始,寻找 k 个后继节点。 2.) 树后继算法在 BST 中找到给定节点的后继。我正在调用这个过程 k 次。
  • @GameOfChess,所以你有 k 个节点,你想为所有 k 个给定节点找到后继节点。我说的对吗?
  • @GameOfChess,我已经对你的问题进行了相当多的编辑,希望它能更清楚地说明问题。你能检查一下这确实反映了你的问题吗?

标签: algorithm binary-search-tree


【解决方案1】:

这是一个非常简单的算法。

创建一个空堆栈S 和一个变量curr = NULL

  1. 在树中找到起始节点。此外,将它的所有祖先(和节点本身)推入堆栈S
  2. 现在从堆栈S 中弹出一个节点top 并检查curr 是否是正确的孩子。如果不是对它的右子树进行中序遍历
    • 如果我们在遍历过程中发现 k 或更多节点,我们就完成了
  3. 如果我们还没有找到 k 个后继节点,则设置 curr = top,并重复 2 直到找到 k 个节点

总体时间完整性为O(h+k)。第 1 步需要 O(h) 时间。第 2 步需要 O(h+k) 时间(第 2 步的所有迭代加起来需要 O(h+k) 时间。)!

【讨论】:

  • 看来您的解释需要改进。如果之前弹出的元素是当前弹出的元素的右子元素怎么办?查看我的答案以获得更好的解释!!!
  • @Shasha99 你是对的。这是一个微不足道的检查。更新了我的答案。
【解决方案2】:

对后继遍历采取以下事实:

  1. 您可以遍历一个分支不超过两次:向下和向上。

  2. 每次对分支的两次访问都对应于找到至少一个后继者:当您通过向上的分支回溯时,您访问的后继者将比您第一次通过同一分支时多, 向下。

  3. 您将只遍历一次的分支数不能超过2h。这种最坏的情况发生在您从树左下角的叶子开始并且必须一直向上到根(后继)然后再向下到底部叶子以找到根的后继时。但是如果在那之后您需要更多的后继者,您将不得不再次访问其中一些分支(在回溯中),然后才能第一次遍历其他分支。因此,您只遍历一次的分支总数不能超过 2h

因此,要找到 k 个后继者,您最多需要遍历 k 个分支(向下和向上) ,参见第 2 点)和 2h 分支一次(第 3 点),这归结为 2k+ 的最坏情况分支遍历计数2h,即O(h+k)

【讨论】:

    【解决方案3】:

    我将为这个问题编写完整的实现,以便于证明我对所花费时间的论点。

    .

    假设BST的每个节点都具有如下结构:

    typedef struct node{
        int vale;
        struct node* left;
        struct node* right;
    }node;
    

    该算法将有 2 个步骤:

    a. 从树的根节点开始,找到起始节点和该节点的所有祖先。将所有这些存储在传递的堆栈中:

    //root -> root node of the tree.
    //val  -> value of the node to find.
    // s   -> stack to store all ancestor.
    node* find_node(node* root, int val,std::stack<node*> &s)
    {
        if(root != NULL)  s.push(root);
        if(root == NULL || root->value == val) return root;
        if(root->value > val) return find_node(root->left);
        else return find_node(root->right);
    
    
    }
    

    并且对该方法的调用如下所示:

    //Assuming that the root is the root node of the tree.
    std::stack<node*> s;
    node* ptr = find_node(root,s); // we have all ancestors of ptr along with ptr in stack s now.
    

    b. 现在我们必须打印树的下一个直接大于(大于 ptr)的元素。我们将从节点(即ptr)开始:

    // s  -> stack of all ancestor of the node.
    // k  -> k-successor to find.
    void print_k_bigger(stack<node*> s, int k)
    {
        //since the top element of stack is the starting node. So we won't print it.
        // We will just pop the first node and perform inorder traversal on its right child.
        prev = s.top();
        s.pop();
        inorder(prev->right,k);
    
        // Now all the nodes present in the stack are ancestor of the node. 
        while(!s.empty() && k>0)
        {
            //pop the node at the top of stack.
            ptr = s.top();
            s.pop();
    
            //if the node popped previously (prev) was the right child of the current 
            //node, i.e. prev was bigger than the current node. So we will have to go 
            //one more level up to search for successors. 
            if(prev == ptr->right) continue;
    
            //else the current node is immidiate bigger than prev node. Print it.
            printf("%d",ptr->value);
    
            //reduce the count.
            k--;
    
            //inorder the right subtree of the current node.
            inorder(ptr->right);
    
            //Note this.
            prev = ptr;
    
        }
    
    }
    

    这是我们的 inorder 的样子:

    void inorder(node* ptr, int& k)
    {
        if(ptr != NULL)
        {
            inorder(ptr->left,k);
            printf("%d",ptr->value);
            k--;
            inorder(ptr->right,k);
        }
    }
    

    时间分析:

    1. find_node 方法是 O(h),因为它可以达到最大根到叶路径的长度。
    2. print_k_bigger 方法是 O(h+k),因为在循环的每次迭代中,堆栈的大小都在减小,或者 k 的值是减少。请注意,当从 while 循环内部调用 inorder() 时,它不会增加额外的开销,因为对 inorder() 的所有调用一起将占用最大值 O(k)

    【讨论】:

    • 这是一个很好的答案,但我认为它太详细了,可能会让只想了解时间复杂度的 OP 感到困惑。
    • 我也不认为 OP 正在寻找实现或 如何 来完成工作,尽管提供代码可能有助于了解时间复杂度。对ptr-&gt;right 的第一个引用可能需要是prev-&gt;right。短语 “它不会增加额外的开销,因为所有对 inorder() 的调用加起来最大 O(k)。是必不可少的,我认为 OP 将专注于这方面。但我不是 OP :-) 你有我的投票权。
    猜你喜欢
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-05
    • 2014-01-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多