maxHBLT的合并&初始化&时间复杂度分析
1. 定义
[extened binary tree] 扩充二叉树是有 external node (用来代替空子树, 也就是 nullptr) 的 binary tree. 对应地, 其他 nodes 叫 internal node.
\(s(x)\) 是从 node x 到其 子树的 external node 的左右路径中 最短 的一条.
- If x is an external node, \(s(x)=0\) .
- If x is an internal node, \(s(x)=\min\{s(LeftChild),s(RightChild)\}+1\) .
更直观的说法是, \(s(x)\) 就是从以 x 为 root 的最高 complete binary tree 的层数.
[HBLT] 当且仅当一颗 binary tree 的任何一个 internal node 的 \(s(LeftChild)\) 都大于或等于 \(s(RightChild)\), 这颗 binary tree 被称为 height-biased lefist tree (HBLT).
[WBLT] 设 \(w(x)\) 表示 node x 所有 descendent node 的数量, 则当且仅当一颗 binary tree 的任何一个 internal node 的 \(w(LeftChild)\) 都大于或等于 \(w(RightChild)\), 这颗 binary tree 被称为 weight-biased lefist tree (HBLT).
[max(min) HBLT] HBLT + max(min) tree.
[max(min) WBLT] WBLT + max(min) tree.
2. 性质
令 x is an interal node of an HBLT:
以 x 为 root 的 (sub)tree 的节点数量 至少 为 \(2^{s(x)}-1\) .
证明:
根据 定义 1.2 , 由于到达 external node 的最短路径为 \(s(x)\) , 因此在自 x 向下一直到第 \(s(x)-1\) 层都无 external node;
也就是说, 自该层向上到 x 是一棵 complete binary tree.
根据 Binary_Tree 的 性质 2.2, 以 x 为 root 的 (sub)tree 至少包含上述 complete binary tree 的 \(2^{s(x)}-1\) 个节点.
而再往下的层就不能确定节点的数量了.若以 x 为 root 的 (sub)tree 有 m 个节点, 那么 \(s(x)\) 至多为 \(\log_{2}{(m+1)}\) .
从 x 到一个 external node 的最右路径 (即从 x 开始延 Right Child 移动的路径) 的长度为 \(s(x)\) .
3. max HBLT 的合并
3.1. 合并逻辑分析
合并两棵 max HBLT 最好用递归完成.
下图展示了 max HBLT 的合并过程.

- (a) 中 9 与 7 合并, 总是将右合并至左 (左的根理所应当更大);
递归进入 9 的 RightChild, 由于是 nullptr, 直接用 7 取代之.
(b) 展现了合并结果.
由于 9 的 \(s(LeftChild) = 0 < s(RightChild) = 1\), 需要交换其左右子树.
(c) 展现了交换完的最终结果. - (d) 中以 10 和 7 为根的左右 subtree 合并, 总是将右合并至左 (左的根理所应当更大);
递归进入 10 的 RightChild, 由于是由于是 nullptr, 直接用 7 取代之.
(e) 展现了合并结果.
由于 10 的 \(s(LeftChild) = 1 = s(RightChild) = 1\), 无需交换其左右子树.
因此 (e) 即为最终结果. - (f) 中以 18 和 10 为 root 的左右 subtree 合并, 总是将右合并至左 (左的根理所应当更大);
递归进入 18 的 RightChild = 7, 由于不是 nullptr, 比较 7 与 10 的大小, 发现 7 不能做 root;
交换18->RghtChild这根指针 与 10 的root这根指针 (也就是说交换完成后,18 -> RightChild应该是以 10 为根的 subtree, 而原本的 7 成为了右边的待合并树).
交换完成后, 在调用函数迭代, 实际上就是 2 中 (d) 和 (e) 的问题.
(g) 是内层函数合并完的结果.
再次比较 6 与 10 的 s 值, 进行适当的交换. - ....
3.2. 代码实现
将上面的逻辑具体实现为代码并不容易,
因此添加了大量注释帮助理解.
先写一个私有方法实现递归.
// Private method.
template<class T>
void maxHBLT<T>::m_meld(binaryTreeNode<std::pair<int, T>>*& x,
binaryTreeNode<std::pair<int, T>>*& y)
{
if (y == nullptr) { return; }
// When {x} is the rightest external node, replace {x=nullptr} with {y}.
if (x == nullptr) {
x = y; // meld
return;
}
// Make sure meld tree with smaller root to the one with a larger.
// Notice that {std::swap(ptr_1,ptr_2)} exchanges the content in two addresses.
// The result is that PARENT of x points to the same address with different content (of y), same to y.
if (x->element.second < y->element.second) { std::swap(x, y); }
// Suppose that "m_meld(x->rightChild, y)" can meld the subtree
// whose root is"x->rightChild" with whose is "y".
m_meld(x->rightChild, y);
// After right subtree of 'x' is melded with tree 'y',
// following code adjests the tree whose root is "x" to an HBLT.
if (x->leftChild == nullptr) {
/*******************
* 9 9 *
* \ -> / *
* 7 7 *
********************/
// "std::swap()" costs 3 steps, but here we only spends 2 steps.
x->leftChild = x->rightChild;
x->rightChild = nullptr;
// Reset the value of "s(x)".
x->element.first = 1;
} else {
/**********************
* 9 9 *
* / \ -> / \ *
* 7 8 8 7 *
* / \ / \ *
* 6 4 6 4 *
***********************/
// Make sure the "s(x->leftChild)" is larger than "s(x->rightChild)".
if (x->leftChild->element.first < x->rightChild->element.first) {
// If not smaller, exchange "x->leftChild" and "x->rightChild".
std::swap(x->leftChild, x->rightChild);
// Reset the value of "s(x)".
x->element.first = x->rightChild->element.first + 1;
}
}
}
用共有方法封装一下作为对外部的接口.
// Public method.
template<class T>
void maxHBLT<T>::meld(maxHBLT<T>& theHblt)
{
m_meld(root, theHblt.root);
treeSize += theHblt.treeSize;
theHblt.root = nullptr;
theHblt.size = 0;
}
3.3. 时间复杂度分析
假设合并两棵树 x 与 y, 它们的元素个数分别为 \(m\) 与 \(n\);
pravate 方法 m_meld 仅沿着 x 和 y 的左/右子树移动, 因此复杂度为:
\]
其中 \(s(x)\) 和 \(s(y)\) 的最大值分别为 \(\log_{2}{(m+1)}\) 和 \(\log_{2}{(n+1)}\)
因此时间复杂度进一步推导为:
\]
4. max HBLT 的初始化
4.1. 逻辑实现
将所有元素分别单独创建为只有一个元素的 max HBLT, 然后全部 push 入一个 FIFO 队列 内部.
然后利用循环, 每次从队首 pop 两个 max HBLT 出来 meld, 然后将结果 push 入队尾;
直到只剩一个合并完的 max HBLT.
4.2. 代码实现
template<class T>
void maxHBLT<T>::initialize(T* theElement, int theSize)
{
arrayQueue<binaryTreeNode<std::pair<int, T>>*> q(theSize);
erase();
for (int i = 1; i <= theSize; i++) {
q.push(new binaryTreeNode<std::pair<int, T>>(std::pair<int, T>(1, theElement[i])));
}
for (int i = 1; i <= theSize; i++) {
binaryTreeNode<std::pair<int, T>>* b = q.front();
q.pop();
binaryTreeNode<std::pair<int, T>>* c = q.front();
q.pop();
m_meld(b, c);
q.push(b);
}
if (theSize > 0) {
root = q.front;
}
treeSize = theSize;
}
4.3. 时间复杂度分析
假设用 \(n\) 个元素初始化一棵 max HBLT, 同时为了简单起见, 假设 \(n\) 是 2 的幂次方.
根据 4.1 的分析:
- 第一轮 pop 后, 合并了 \(n/2\) 对元素个数为 \(1\) 的 max HBLT.
- 第二轮 pop 后, 合并了 \(n/4\) 对元素个数为 \(2\) 的 max HBLT.
- 第三轮 pop 后, 合并了 \(n/8\) 对元素个数为 \(4\) 的 max HBLT.
... - 第 \(n/2\) 轮 pop 后, 合并了 \(1\) 对元素个数为 \(n/2\) 的 max HBLT.
若两颗棵 max HBLT 的元素个数都为 \(i\); 根据 3.3 的时间复杂度分析, private 方法 m_meld 合并这两棵树的最大步数 (完全遍历 + 左树 nullptr 的一次检测) 为:
\]
因此 initialize 的时间复杂度为:
\]
Reference & picture resource | Data Structures, Algoritms, and Applications in C++, Sartaj Sahni
maxHBLT的合并&初始化&时间复杂度分析的更多相关文章
- BZOJ 3277 串 & BZOJ 3473 字符串 (广义后缀自动机、时间复杂度分析、启发式合并、线段树合并、主席树)
标签那么长是因为做法太多了... 题目链接: (bzoj 3277) https://www.lydsy.com/JudgeOnline/problem.php?id=3277 (bzoj 3473) ...
- 斐波那契数列的三种C++实现及时间复杂度分析
本文介绍了斐波那契数列的三种C++实现并详细地分析了时间复杂度. 斐波那契数列定义:F(1)=1, F(2)=1, F(n)=F(n-1) + F(n-2) (n>2) 如何计算斐波那契数 F( ...
- STL堆排序&时间复杂度分析
1. 逻辑&时间复杂度分析 pop 和 initialize 的时间复杂度请参考: [DSAAinC++] 大根堆的pop&remove&initialize 将数组初始化为一 ...
- C语言数组实现约瑟夫环问题,以及对其进行时间复杂度分析
尝试表达 本人试着去表达约瑟夫环问题:一群人围成一个圈,作这样的一个游戏,选定一个人作起点以及数数的方向,这个人先数1,到下一个人数2,直到数到游戏规则约定那个数的人,比如是3,数到3的那个人就离开这 ...
- u-boot中nandflash初始化流程分析(转)
u-boot中nandflash初始化流程分析(转) 原文地址http://zhuairlunjj.blog.163.com/blog/static/80050945201092011249136/ ...
- 数据结构线性表的动态分配顺序存储结构算法c语言具体实现和算法时间复杂度分析
#include<stdio.h>#include<stdlib.h>//线性表的动态分配顺序存储结构#define LIST_INIT_SIZE 100//线性表存储空间的初 ...
- 轮廓问题/Outline Problem-->改进的算法及时间复杂度分析
前面写过一篇关于轮廓算法的文章,是把合并建筑和合并轮廓是分开对待的,并且为了使轮廓合并的时候算法简单,对x坐标使用了double类型,然后对整形的x坐标数据进行合并.这样做是为了使得需找拐点的算法容易 ...
- Android ListView初始化简单分析
下面是分析ListView初始化的源码流程分析,主要是ListVIew.onLayout过程与普通视图的layout过程完全不同,避免流程交代不清楚,以下是一个流程的思维导图. 思维导图是顺序是从左向 ...
- MVC之前-ASP.NET初始化流程分析1
Asp.net Mvc是当前使用比较多的web框架,也是比较先进的框架.我打算根据自己的实际项目经验以及相关的源码和一些使用Asp.net Mvc的优秀项目(主要是orchard)来说一说自己对于As ...
随机推荐
- linux中CentOS配置文件编辑错误撤回
未编辑状态下 U键 撤销 DD 快速删除
- windows如何结束某个端口的进程
netstat -ano | findstr 端口号 (查询端口号被哪个进程占用) tasklist | findstr 进程PID (根据PID找到进程名称) taskkill -PID 进程PID ...
- Centos8安装NextCloud记录
今天在网上学习了这个Nextcloud 网盘的搭建,被折磨的快崩溃了.始终是找不到答案,我在网上查了2天的资料 还是没有找到答案,今天这里总结一下安装的下面的总结: 原文出处在官网:CentOS 8 ...
- 10.5 详解Android Studio项目结构
Android项目的结构很复杂,并不像HTML项目,最简单的直接一个HTML文件就行了,相信学完上一节的同学就明白,哪怕是一个HelloWorld这样一个项目的文件可能都有几十个,所以我们需要搞清楚, ...
- Kotlin学习快速入门(7)——扩展的妙用
原文地址: Kotlin学习快速入门(7)--扩展的妙用 - Stars-One的杂货小窝 之前也模模糊糊地在用这个功能,也是十分方便,可以不用继承,快速给某个类增加新的方法,本篇便是来讲解下Kotl ...
- C语言求100以内的和的4种方式
C语言的一个很经典的例子,帮助熟练运行几个循环的写法 * 方法一(do---while语句) #include main () { int i,sum=0; do { sum=sum+i; i++; ...
- antd vue 折叠面板 v-for 循环点击无效
问题描述 实现一个折叠面板点击展开,但是必须点击两次才能展开,第一次无效 <a-collapse-panel v-for="(item, index) in dataMap" ...
- IO流思维导图
IO思维导图总结 总览: 1.文件 <目标:File类的创建和删除的方法 > public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时, 创建一个 ...
- NFS介绍与搭建
一.NFS的介绍 1.1.什么是NFS NFS 是Network File System的缩写,即网络文件系统.一种使用于分散式文件系统的协定,由Sun公司开发,于1984年向外公布. NFS在文件传 ...
- 互联网产品前后端分离测试(Eolink 分享)
在互联网产品质量保障精细化的大背景下,根据系统架构从底层通过技术手段发起测试,显得尤为重要,测试分层的思想正是基于此产生的,目前也是较为成熟的测试策略. 一般采用自下而上的测试方式,以最简单的单一前后 ...