1. 定义

  1. [extened binary tree] 扩充二叉树是有 external node (用来代替空子树, 也就是 nullptr) 的 binary tree. 对应地, 其他 nodes 叫 internal node.

  2. \(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 的层数.

  3. [HBLT] 当且仅当一颗 binary tree 的任何一个 internal node 的 \(s(LeftChild)\) 都大于或等于 \(s(RightChild)\), 这颗 binary tree 被称为 height-biased lefist tree (HBLT).

  4. [WBLT] 设 \(w(x)\) 表示 node x 所有 descendent node 的数量, 则当且仅当一颗 binary tree 的任何一个 internal node 的 \(w(LeftChild)\) 都大于或等于 \(w(RightChild)\), 这颗 binary tree 被称为 weight-biased lefist tree (HBLT).

  5. [max(min) HBLT] HBLT + max(min) tree.

  6. [max(min) WBLT] WBLT + max(min) tree.

2. 性质

x is an interal node of an HBLT:

  1. 以 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\) 个节点.

    而再往下的层就不能确定节点的数量了.

  2. 若以 xroot(sub)treem 个节点, 那么 \(s(x)\) 至多为 \(\log_{2}{(m+1)}\) .

  3. x 到一个 external node 的最右路径 (即从 x 开始延 Right Child 移动的路径) 的长度为 \(s(x)\) .

3. max HBLT 的合并

3.1. 合并逻辑分析

合并两棵 max HBLT 最好用递归完成.

下图展示了 max HBLT 的合并过程.

  1. (a) 中 9 与 7 合并, 总是将右合并至左 (左的根理所应当更大);

    递归进入 9 的 RightChild, 由于是 nullptr, 直接用 7 取代之.

    (b) 展现了合并结果.

    由于 9 的 \(s(LeftChild) = 0 < s(RightChild) = 1\), 需要交换其左右子树.

    (c) 展现了交换完的最终结果.
  2. (d) 中以 10 和 7 为根的左右 subtree 合并, 总是将右合并至左 (左的根理所应当更大);

    递归进入 10 的 RightChild, 由于是由于是 nullptr, 直接用 7 取代之.

    (e) 展现了合并结果.

    由于 10 的 \(s(LeftChild) = 1 = s(RightChild) = 1\), 无需交换其左右子树.

    因此 (e) 即为最终结果.
  3. (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 值, 进行适当的交换.
  4. ....

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 的左/右子树移动, 因此复杂度为:

\[O(s(x)+s(y))
\]

其中 \(s(x)\) 和 \(s(y)\) 的最大值分别为 \(\log_{2}{(m+1)}\) 和 \(\log_{2}{(n+1)}\)

因此时间复杂度进一步推导为:

\[O(\log{m}+\log{n})=O(\log{(mn)})
\]

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 的一次检测) 为:

\[i+i+1=2i+1
\]

因此 initialize 的时间复杂度为:

\[O((2*1+1) * n/2 + (2*2+1)* n/4 + (2*4+1)*n/8 + \cdots )=O(n\sum{\frac{2i+1}{2^i}})=O(n)
\]

Reference & picture resource | Data Structures, Algoritms, and Applications in C++, Sartaj Sahni

maxHBLT的合并&初始化&时间复杂度分析的更多相关文章

  1. BZOJ 3277 串 & BZOJ 3473 字符串 (广义后缀自动机、时间复杂度分析、启发式合并、线段树合并、主席树)

    标签那么长是因为做法太多了... 题目链接: (bzoj 3277) https://www.lydsy.com/JudgeOnline/problem.php?id=3277 (bzoj 3473) ...

  2. 斐波那契数列的三种C++实现及时间复杂度分析

    本文介绍了斐波那契数列的三种C++实现并详细地分析了时间复杂度. 斐波那契数列定义:F(1)=1, F(2)=1, F(n)=F(n-1) + F(n-2) (n>2) 如何计算斐波那契数 F( ...

  3. STL堆排序&时间复杂度分析

    1. 逻辑&时间复杂度分析 pop 和 initialize 的时间复杂度请参考: [DSAAinC++] 大根堆的pop&remove&initialize 将数组初始化为一 ...

  4. C语言数组实现约瑟夫环问题,以及对其进行时间复杂度分析

    尝试表达 本人试着去表达约瑟夫环问题:一群人围成一个圈,作这样的一个游戏,选定一个人作起点以及数数的方向,这个人先数1,到下一个人数2,直到数到游戏规则约定那个数的人,比如是3,数到3的那个人就离开这 ...

  5. u-boot中nandflash初始化流程分析(转)

    u-boot中nandflash初始化流程分析(转) 原文地址http://zhuairlunjj.blog.163.com/blog/static/80050945201092011249136/ ...

  6. 数据结构线性表的动态分配顺序存储结构算法c语言具体实现和算法时间复杂度分析

    #include<stdio.h>#include<stdlib.h>//线性表的动态分配顺序存储结构#define LIST_INIT_SIZE 100//线性表存储空间的初 ...

  7. 轮廓问题/Outline Problem-->改进的算法及时间复杂度分析

    前面写过一篇关于轮廓算法的文章,是把合并建筑和合并轮廓是分开对待的,并且为了使轮廓合并的时候算法简单,对x坐标使用了double类型,然后对整形的x坐标数据进行合并.这样做是为了使得需找拐点的算法容易 ...

  8. Android ListView初始化简单分析

    下面是分析ListView初始化的源码流程分析,主要是ListVIew.onLayout过程与普通视图的layout过程完全不同,避免流程交代不清楚,以下是一个流程的思维导图. 思维导图是顺序是从左向 ...

  9. MVC之前-ASP.NET初始化流程分析1

    Asp.net Mvc是当前使用比较多的web框架,也是比较先进的框架.我打算根据自己的实际项目经验以及相关的源码和一些使用Asp.net Mvc的优秀项目(主要是orchard)来说一说自己对于As ...

随机推荐

  1. mybatis转义反斜杠_MyBatis Plus like模糊查询特殊字符_、\、%

    在MyBatis Plus中,使用like查询特殊字符_,\,%时会出现以下情况: 1.查询下划线_,sql语句会变为"%_%",会导致返回所有结果.在MySQL中下划线" ...

  2. HelloWord程序代码的编写和HelloWord程序的编译运行

    1.新建文件夹,存放代码 2.新建一个Java文件 文件后缀名.java(Hello.java) 3.编写代码public class Hello{public static void main(St ...

  3. SpringCloudGateway微服务网关实战与源码分析 - 中

    实战 路由过滤器工厂 路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应.路由过滤器的作用域是特定的路由.SpringCloud Gateway包括许多内置的GatewayFilter ...

  4. Redis 内存优化神技,小内存保存大数据

    大家好,我是「码哥」,大家可以叫我靓仔. 这次码哥跟大家分享一些优化神技,当你面试或者工作中你遇到如下问题,那就使出今天学到的绝招,一招定乾坤! 如何用更少的内存保存更多的数据? 我们应该从 Redi ...

  5. 什么新东西值得学「GitHub 热点速览 v.22.29」

    上周 18k+ 的项目 bun 这周又获得 7k+ star,是时候了解下它背后的编程语言 zig 了,它并不是一门新的语言,伴随着 bun 的风靡,zig 本周也上了 GitHub 热榜.同样,可以 ...

  6. AtCoder Beginner Contest 260 E // 双指针 + 差分

    题目传送门:E - At Least One (atcoder.jp) 题意: 给定大小为N的两个数组A,B,求长度分别为1~M的满足以下条件的连续序列数量,条件为: 对于每个i(从1~N),Ai和B ...

  7. 2022-7-20 第七组 pan小堂 String

    字符串 String 字符串部分方法 字符串对象的特点: 1.Java程序中所有双引号引起来的内容,都是String类的对象 2.字符串内容不可变,它们的值在创建后不能被更改(在底层被final修饰, ...

  8. 2022-7-18 第五组 pan 面向对象

    面向过程 向过程就是:面向过程,其实就是面向着具体的每一个步骤和过程,把每一个步骤和过程完成,然后由这些功能方法相互调用,完成需求. 面向对象 什么是面向对象: 面向对象思想就是不断的创建对象,使用对 ...

  9. python 进程理解

    简介 线程理解中介绍过,再回顾一遍,一个应用程序由多个进程组成,一个进程由多个线程组成,由操作系统根据优先级.时间片来绝对线程的运行 进程 python的进程不同于线程,在主流的cpython解释器下 ...

  10. Bika LIMS 开源LIMS集—— SENAITE的使用(用户、角色、部门)

    设置 添加实验室人员,系统用户 因为创建实验室时必须选择实验室经理/主任/负责人,因此需要先创建实验室经理人员. 创建人员时输入人员姓名,可上传签名图片. 创建实验室部门 输入实验室名称.代码,选择实 ...