构建二叉堆时,为什么大家都用从最后一个非叶子结点开始逐个进行下沉调整,而不采用从根节点开始逐个进行下沉调整,它们的时间复杂度有没有差异?

以下我说的从根节点开始逐个进行下沉调整也是不遍历叶子结点的。

  由于时间复杂度的概念问题,其实在比较两个对象的时间复杂度的时候会不清晰,比如A的时间复杂度是O(n/2),B的时间复杂度是O(n)在课本的概念上我们应该去掉系数,所以导致AB的时间复杂度相同,但是你在实际应用的时候不傻的人都会选择A吧,毕竟A里面的n除了一个2,时间复杂度会低一些。

  所以我们为了算的比较清晰,所以接下来的算时间复杂度的过程会比较细致。

---------------------------------------------------------------------------------------------------------------------------------

  首先,从根结点开始逐个进行下沉调整,需要遍历数组中的每一个非叶子结点。如果说有n个结点,那么所有非叶子结点就有n/2,所以时间复杂度为O(n/2)

  接着,在进行下沉调整的过程中,每个结点最少不需要下沉,最多需要下沉到树的深度,树的深度为logn。因此,每个结点下沉的时间复杂度为O(logn/2)

  因此,从根结点开始逐个进行下沉调整总的时间复杂度为O((n/2)*(logn/2))

                             -----------------------------------------------------------------------------

  接下来我们计算一下从最后一个非叶子结点开始逐个进行下沉调整的时间复杂度,同样的是所有非叶子结点的个数还是n/2,时间复杂度为O(n/2)

  接着,在进行下沉调整的过程中,每个结点最少不需要下沉,最多需要下沉到树的深度,树的深度为logn。

  因此,每个结点下沉的时间复杂度也为O((n/2)*(logn/2))。

事实上,从最后一个非叶子结点开始逐个进行下沉调整和从根节点开始逐个进行下沉调整的时间复杂度化简后都是O(nlogn)。因此,在时间复杂度方面,它们并没有本质上的区别。

 上述是本人在看书的时候引发的一些思想和感悟,不一定正确,欢迎读者们讨论!大家一起学习一起进步

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 算法思路: 1. 从根节点开始遍历二叉树,当遇到叶子节点,记录下该节点的值,并将其加入到一个栈中。 2. 重复步骤1,直到遍历完整棵树。 3. 从栈中依次取出节点的值,输出即可。 算法实现: ``` void reversePath(TreeNode* root) { if (root == NULL) { return; } stack<int> s; reversePathHelper(root, s); while (!s.empty()) { cout << s.top() << " "; s.pop(); } } void reversePathHelper(TreeNode* node, stack<int>& s) { if (node == NULL) { return; } if (node->left == NULL && node->right == NULL) { s.push(node->val); return; } reversePathHelper(node->left, s); reversePathHelper(node->right, s); s.push(node->val); } ``` 其中,`reversePath`函数是主函数,`reversePathHelper`函数是递归函数,用于遍历整棵树并将叶子节点的值加入到栈中。最后,从栈中依次取出节点的值并输出即可。 ### 回答2: 二叉树采用二叉链存储结构存储,每个结点都包含左、右子树的指针和父结点的指针。要输出从每个叶子结点到根结点的逆路径,首先需要遍历二叉树的所有叶子结点。可以采用后序遍历来实现这一操作。在遍历到一个叶子结点,记录下该结点的父结点指针,并将该结点回溯到根结点。在回溯的过程中,逐个输出经过的结点,直到回溯到根结点。 具体地,可以使用递归算法来实现后序遍历。对于一个结点,先遍历其左子树,再遍历其右子树,最后输出该结点。在遍历到叶子结点,记录下该结点的父结点指针,并将该结点回溯到根结点。在回溯的过程中,逐个输出经过的结点,直到回溯到根结点。 下面是伪代码实现: void postOrderTraversal(TreeNode* root) { if (root == nullptr) { return; } postOrderTraversal(root->left); // 遍历左子树 postOrderTraversal(root->right); // 遍历右子树 if (root->left == nullptr && root->right == nullptr) { // 遍历到叶子结点 TreeNode* p = root->parent; while (p != nullptr) { // 回溯到根结点 cout << p->val << " "; p = p->parent; } cout << endl; } cout << root->val << " "; // 输出该结点 } 在输出逆路径,可以采用堆栈来实现。每遍历到一个结点,将其压入堆栈中,并更新指针指向其父结点。在回溯到根结点后,依次弹出堆栈中的结点,即可输出逆路径。 下面是伪代码实现: void postOrderTraversal(TreeNode* root) { if (root == nullptr) { return; } stack<TreeNode*> s; TreeNode* p = root; TreeNode* pre = nullptr; // 记录上一次遍历的结点 while (p != nullptr || !s.empty()) { while (p != nullptr) { s.push(p); p = p->left; } p = s.top(); if (p->right == nullptr || p->right == pre) { // 已遍历完右子树或右子树为空 if (p->left == nullptr && p->right == nullptr) { // 遍历到叶子结点 TreeNode* q = p; while (q != nullptr) { // 回溯到根结点 cout << q->val << " "; q = q->parent; } cout << endl; } s.pop(); pre = p; p = nullptr; } else { p = p->right; } } } 综上所述,可以使用后序遍历和堆栈来实现输出从每个叶子结点到根结点的逆路径。 ### 回答3: 二叉树采用二叉链存储结构,其每个节点一个左子节点一个右子节点,因此可以利用递归的方式来实现从每个叶子节点到根节点的逆路径输出。 具体实现步骤如下: 1. 定义一个递归函数,接收一个二叉树节点参数,首先判断该节点是否为空,若为空则返回。 2. 判断当前节点是否为叶子节点,若是,则输出该节点的值,并依次递归输出该节点的父节点,直至根节点。 3. 若当前节点不是叶子节点,则先递归输出它的右子节点的逆路径,再递归输出它的左子节点的逆路径。 4. 在递归结束,需要判断当前节点是否是根节点,若是,则输出根节点的值。 5. 在主程序中,先判断二叉树是否为空,若为空则直接返回,否则依次遍历二叉树的每个叶子节点,对每个叶子节点都调用递归函数进行逆路径输出。 递归函数的Python实现如下: def reverse_path(node): if node is None: return if node.left is None and node.right is None: print(node.value) while node.parent is not None: print(node.parent.value) node = node.parent return reverse_path(node.right) reverse_path(node.left) if node.parent is None: print(node.value) 在主程序中,假设二叉树的根节点为root,则可以如下调用reverse_path函数: def traverse_leaf_to_root(root): if root is None: return if root.left is None and root.right is None: print(root.value) return traverse_leaf_to_root(root.left) traverse_leaf_to_root(root.right) # 遍历完所有叶子节点,开始逆路径输出 reverse_path(root) 如此,就可实现从每个叶子节点到根节点的逆路径输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值