数据结构学习记录——树习题-Complete Binary Search Tree(题目描述、输入输出示例、数据结构的选择、核心算法、计算左子树的规模)

简介: 数据结构学习记录——树习题-Complete Binary Search Tree(题目描述、输入输出示例、数据结构的选择、核心算法、计算左子树的规模)

题目描述

现给定一系列不同的非负整数键,如果要求构造出一颗完全二叉树,则可以构造唯一的二叉搜索树。输出此二叉搜索树的层序遍历序列。

完全二叉树

有n个节点的二叉树,对树中节点按从上至下、从左到右顺序进行编号,编号为i(1<= i <= n)节点与满二叉树中编号为i节点在二叉树中位置相同。

二叉搜索树

一颗二叉树,可以为空;如果不为空,满足一下性质:

1.非空左子树的所有键值小于其根节点的键值。

2.非空右子树的所有键值大于其根节点的键值。

3.左、右子树都是二叉搜索树

输入示例

每个输入文件包含一个测试用例。

对于每种情况,第一行包含正整数N(≤ 1 0 0 0)。

然后下一行给出了 N个不同的非负整数键。一行中的所有数字都用空格分隔,不大于2000。

Sample Input:

10

1 2 3 4 5 6 7 8 9 0

输出示例

对于每个测试用例,在一行中打印相应的完全二叉搜索树的层序遍历序列。

一行中的所有数字必须用空格分隔,并且行的末尾不能有额外的空格。

Sample Output:

6 3 8 1 5 7 9 0 2 4

数据结构的选择

二叉树可以通过数组和链表来实现,我们现在要先确定一下用哪种数据结构来解决这道题目。


在之前的学习中,我们使用链表来实现二叉树的存储。因为在一些较为极端的情况下,二叉树可能向左或向右倾斜的程度比较大,这个时候使用数组来实现的话就会造成很大的空间浪费。这是从空间的一个角度来考虑的,而在遍历上,数组和链表两种实现方式都是差不多的。


现在看回这道题目,它需要进行的操作:

  • 填写数字(也属于某种遍历)
  • 层序遍历

在遍历的角度上来看,两种方式都差不多,所以不考虑了。

从空间的角度上来看,完全二叉树的情况下,数组是不会浪费空间的,故而还不能下定论。


最后看到遍历的方式,发现是层序遍历,这就意味着,如果使用数组实现,那么直接顺序输出就是其层序遍历的遍历序列;而链表的层序遍历需要借助队列来实现,显然更为复杂一点。


所以,我们选择数组来实现。


核心算法

void solve(int ALeft, int ARight, int TRoot)
{  
   /*  初始调用为 solve(0, N - 1, 0) */
  n = ARight - ALeft + 1;
  if (n == 0)  
    return;
  L = GetLeftLength(n);  /*  计算出n个结点的树其左子树有多少个结点  */
  T[TRoot] = A[ALeft + L];
  LeftTRoot = TRoot * 2 + 1;
  RightTRoot = LeftTRoot + 1;
  solve(ALeft, ALeft + L - 1, LeftTRoot);
  solve(ALeft + L + 1, ARight, RightTRoot);
}

其中A为排序后的序列,我们需要对输入的序列进行排序,才能得到它, ALeft表示序列最左边的位置的下标,ARight则表示序列最右边的位置的下标。


T是我们求的结果树,TRoot就是结果树的根结点。


除了排序之外,还有一步重要的操作:计算左子树的结点个数,只有确定了左子树的结点个数,我们才能确定根结点以及右子树。


计算左子树的规模

思路

如图所示,完全二叉树的完美二叉树的部分可以用 来表示其结点数,H为其中完美二叉树部分的层数。

接下来把剩余的几个结点个数设为X,总结点数设为N,就可以得到一个关系式:

将公式整理成log形式,得:

对于大规模的计算,X对整体的结果影响很小很小,我们把X忽略,故而有:

N是已知的,所以可以根据这个公式求出H;

继而代入 求出X;

现在得到一整棵完全二叉树的结点数了,要怎么求其左子树的结点呢?

一棵完全二叉树的左子树也一定是一棵完全二叉树,所以可以知道其左子树的结点数应为:

你以为,这样就完了吗?

看一下另外一种情况,我们就会发现目前的X还不是最正确的X:

很显然,这里的X已经不在左子树的范畴内了。

正确应该为:

所以我们应该要对X的取值有一定的限制,或者说,X应该要有一定的范围。

可以考虑,当左子树为一棵完美二叉树时,X可以为0,也可以为

X为0的情况:

X为 的情况:

总结

最后,总结一下计算左子树的规模:

第一步是通过公式 求出H;

第二步是通过公式 求出X;

第三步是选出正确的X,

最后一步就是代入 求出我们最终的结果。


end



目录
相关文章
|
11天前
|
存储 算法
数据结构与算法学习二二:图的学习、图的概念、图的深度和广度优先遍历
这篇文章详细介绍了图的概念、表示方式以及深度优先遍历和广度优先遍历的算法实现。
26 1
数据结构与算法学习二二:图的学习、图的概念、图的深度和广度优先遍历
|
8天前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
29 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
9天前
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
32 2
动态规划算法学习三:0-1背包问题
|
8天前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
37 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
9天前
|
算法
动态规划算法学习二:最长公共子序列
这篇文章介绍了如何使用动态规划算法解决最长公共子序列(LCS)问题,包括问题描述、最优子结构性质、状态表示、状态递归方程、计算最优值的方法,以及具体的代码实现。
40 0
动态规划算法学习二:最长公共子序列
|
9天前
|
缓存 负载均衡 算法
nginx学习:配置文件详解,负载均衡三种算法学习,上接nginx实操篇
Nginx 是一款高性能的 HTTP 和反向代理服务器,也是一个通用的 TCP/UDP 代理服务器,以及一个邮件代理服务器和通用的 HTTP 缓存服务器。
17 0
nginx学习:配置文件详解,负载均衡三种算法学习,上接nginx实操篇
|
5天前
|
移动开发 算法 前端开发
前端常用算法全解:特征梳理、复杂度比较、分类解读与示例展示
前端常用算法全解:特征梳理、复杂度比较、分类解读与示例展示
9 0
|
8天前
|
Java C++
【数据结构】探索红黑树的奥秘:自平衡原理图解及与二叉查找树的比较
本文深入解析红黑树的自平衡原理,介绍其五大原则,并通过图解和代码示例展示其内部机制。同时,对比红黑树与二叉查找树的性能差异,帮助读者更好地理解这两种数据结构的特点和应用场景。
15 0
|
9天前
|
存储 算法
动态规划算法学习一:DP的重要知识点、矩阵连乘算法
这篇文章是关于动态规划算法中矩阵连乘问题的详解,包括问题描述、最优子结构、重叠子问题、递归方法、备忘录方法和动态规划算法设计的步骤。
42 0
|
11天前
|
存储 C语言
栈和队列题目练习
栈和队列题目练习
12 0