完全二叉树、完美二叉树、完满二叉树、计算完全二叉树的结点

news/2024/11/14 13:49:12/

文章目录

  • 完美二叉树、完全二叉树、完满二叉树
    • 一、完美二叉树(Perfect Binary Tree)
      • 完美二叉树的定义:
    • 二、完全二叉树**(Complete Binary Tree)**
      • 完全二叉树的定义:
    • 完美二叉树和完全二叉树:
    • 三、完满二叉树**(Full Binary Tree)**
    • 四、如何计算完全二叉树的结点数?
      • [222. 完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes/)
        • 复杂度分析:

完美二叉树、完全二叉树、完满二叉树

一、完美二叉树(Perfect Binary Tree)

对于完美二叉树,我们常用的是另一个名称:满二叉树

完美二叉树的定义:

完美二叉树是一种特殊的完全二叉树,每层都是满的,像一个稳定的三角形

在这里插入图片描述

二、完全二叉树**(Complete Binary Tree)**

完全二叉树的定义:

全二叉树从根结点到倒数第二层满足完美二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐。

在这里插入图片描述

其实理解完全二叉树可以借助于**栈(stack)**的思想。

具体就是我们可以将一棵完美二叉树的所有节点按照编号1…15的顺序依次入栈。然后对栈每一次出栈,栈里保存的结点集构造一棵二叉树都是一棵完全二叉树

完美二叉树和完全二叉树:

下图是一棵完美二叉树:

现在将编号为15, 14, …, 9的叶子结点从右到左依次拿掉或者拿掉部分,则是一棵完全(Complete)二叉树
在这里插入图片描述

例如将上图中的编号为15, 14, 13, 12, 11叶子结点都拿掉(从右到左的顺序):

在这里插入图片描述

但是如果不是依次拿掉就不满足完全二叉树

例如将上图编号15,14,13,11叶子结点都拿掉(从右到左的顺序):

在这里插入图片描述

那可以把它变成完全二叉树吗?

要不就是在拿掉编号11的时候也拿掉编号12,要不就是让编号11座位编号5的右孩子,则变成一棵完全二叉树

三、完满二叉树**(Full Binary Tree)**

所有非叶子结点的度都是2。(只要你有孩子,你就必然是有两个孩子。)

四、如何计算完全二叉树的结点数?

222. 完全二叉树的节点个数

这题其实很简单,但是为了达到较高效的时间复杂度,我们可以用到上面介绍到的完满二叉树

  • 如果是求一棵普通二叉树的结点数,只需要这样遍历就能得到结果,时间复杂度为 O(N):
public int countNodes(TreeNode root) {if (root == null) return 0;return 1 + countNodes(root.left) + countNodes(root.right);
}
  • 如果是求一棵满二叉树,节点总数和树的高度呈指数关系:
public int countNodes(TreeNode root) {int h = 0;// 计算树的高度while (root != null) {root = root.left;h++;}// 节点总数就是 2^h - 1return (int)Math.pow(2, h) - 1;
}
  • 完全二叉树比普通二叉树特殊,但又没有满二叉树那么特殊,计算它的节点总数,可以说是普通二叉树和满二叉树的结合版
public int countNodes(TreeNode root) {TreeNode l = root, r = root;// 沿最左侧和最右侧分别计算高度int hl = 0, hr = 0;while (l != null) {l = l.left;hl++;}while (r != null) {r = r.right;hr++;}// 如果左右侧计算的高度相同,则是一棵满二叉树if (hl == hr) {return (int)Math.pow(2, hl) - 1;}// 如果左右侧的高度不同,则按照普通二叉树的逻辑计算return 1 + countNodes(root.left) + countNodes(root.right);
}

结合刚才针对满二叉树和普通二叉树的算法,上面这段代码应该不难理解,就是一个结合版,但是其中降低时间复杂度的技巧是非常微妙的

复杂度分析:

这个算法的时间复杂度是 O(logN*logN),这是怎么得到的呢?

直觉感觉好像最坏情况下是 O(N*logN) 吧,因为之前的 while 需要 logN 的时间,最后要 O(N) 的时间向左右子树递归:

return 1 + countNodes(root.left) + countNodes(root.right);

但是关键点在于,这两个递归只有一个会真的递归下去,另一个一定会触发 hl == hr 而立即返回,不会递归下去

因为一棵完全二叉树的两棵子树,至少有一棵是满二叉树,如图所示:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

很明显,由于完全二叉树的性质,其子树一定有一棵是满的,所以一定会触发 hl == hr,只消耗 O(logN) 的复杂度而不会继续递归。

综上,算法的递归深度就是树的高度 O(logN),每次递归所花费的时间就是 while 循环,需要 O(logN),所以总体的时间复杂度是 O(logN*logN)。

所以说,「完全二叉树」这个概念还是有它存在的原因的,不仅适用于数组实现二叉堆,而且连计算节点总数这种看起来简单的操作都有高效的算法实现。


http://www.ppmy.cn/news/38237.html

相关文章

动态库 - 对话

1 程序a调用c.so,同时程序b也调用c.so,内存中有几份c.so? 如果程序a和程序b都是独立的进程,则内存中会有两份c.so,每个进程都会加载自己的c.so库。 即使是同一个程序的不同进程,也会加载各自的c.so库,因为每个进程都有自己独立的虚拟地址空间,不会相互干扰。 2 同一进…

倒计时时钟

时间 很多情况下,需要一种倒计时的工具。厨房像煮螃蟹等。还有各种纪念日,高考倒计时等。这些东西可以时刻提醒自己时间的宝贵。 100年的时间,不算润年,初步估算 3,153,600,000 秒。一个64位整数就可以搞定。 其实从出生开始&a…

PCB生产工艺流程四:PCB工艺流程第2步层压

PCB生产工艺流程四:PCB工艺流程第2步层压 上一期给大家介绍了生产工艺流程的第1步——内层线路。 《生产PCB的内层线路有哪7步》 这一期给大家介绍生产工艺流程的第2步——层压,那么它的流程又有哪些步骤呢?那么我们就以层压的流程为主题&…

【C语言深度解刨】指针与数组(全)

文章目录前言基本目标一.指针1.指针的认识2.指针与指针变量3.指针的强转4.void指针5.空指针6.多级指针7.数组指针8.函数指针9.函数指针数组的指针10.野指针11.指针的运算二.数组1.数组传参2.多维数组前言 基本目标 为什么要有指针? 指针与指针变量的区别&#xff1…

【数据库管理】⑩数据字典

1. 数据字典的概述 数据字典(Data Dictionary)是数据库管理系统中的一个重要组成部分,它是一个存储数据库元数据的集合,包含了数据库中所有对象的定义和描述信息。数据字典可以帮助用户了解数据库中的各种对象和数据结构&#xff…

多线程+线程池(知识分享)

一、多线程 1、什么是多线程 1.1 多线程的概念 多线程是指在一个程序中同时执行多个线程,每个线程都可以独立执行,各自完成自己的任务。 多线程的实现可以提高程序的性能和响应速度,尤其是在需要同时执行多个耗时的任务时。在多线程中&…

ASP.NET Core MVC+Quartz实现定时任务可视化管理页面

在前一篇文章,我们了解了如何通过.NET6Quartz开发基于控制台应用程序的定时任务,今天继续在之前的基础上,进一步讲解基于ASP.NET Core MVCQuartz实现定时任务的可视化管理页面,仅供学习分享使用,如有不足之处&#xff…

Docker基础操作

关于Docker(https://hub.docker.com/)微服务虽然具备各种各样的优势,但服务的拆分通用给部署带来了很大的麻烦。分布式系统中,依赖的组件非常多,不同组件之间部署时往往会产生一些冲突。在数百上千台服务中重复部署&am…