左式堆
左式堆存在的意义是什么?
我们来看下面场景:
如何将堆A和堆B合并为一个堆H呢?
相信你在思考过后可能会想到我们之前学过的完全二叉堆(如果不了解,可以参考这篇博客)的操作方法,不错的确可以使用完全二叉堆的操作接口进行两个堆的合并,但是效率如何?我们接下来分析一下
-
方法一:
将B堆依次取出插入到A堆中
-
方法二:
将A、B堆混合并且使用建堆
我们是否有更优的方法?
这就是左式堆存在的意义了,能够使效率达到O(logn)
一、左式堆的基本概念与结构
首先我们需要引入一个概念:空结点路径长度(Null Path Length)
空结点路径长度计算方法
- npl(null) = 0
- npl(x) = 1 + min(npl(lc(x)), npl(rc(x)))
说明:
- npl(x) = x到外部结点的最近距离
- npl(x) = 以x为根的最大满子树的高度
左倾性:
- 对任何内结点,都有npl(lc(x)) >= npl(rc(x))
- 对任何内结点, 都有npl(x) = 1 + npl(rc(x))
满足左倾性的堆就是左式堆
接下来我们来看看左式堆的右侧链条
从根结点x出发,一直沿右分支前进,最终它一定会抵达全堆中最浅的外部结点
右侧链长度为d的左式堆至少有2^(d+1) + 1(这里用等比数列的求和性质),反之,当包含n个结点时,右侧链的长度最多为log(n + 1) - 1 = O(logn)
二、合并
算法
合并操作的主要步骤:
- 判断堆a是否有左孩子(根据左式堆特征也一定没有右孩子),如果没有则直接将b作为a的左孩子
- 如果有孩子,则将a和b中较大的那个堆的右孩子和另一个堆继续递归合并,直到当前堆a没有了右孩子,把堆b当成它的右孩子
- 递归返回后,调整它的左倾性,并且更新它的npl
- 返回堆a的根结点
以下图为例:
代码实现
#include <iostream>
using namespace std;
typedef struct LeftHeap
{
int val;
Left lc;
Left rc;
int npl;
}*Left;
Left Merge_node(Left h1, Left h2)
{
if (!h1)
{
return h2;
}
if (!h2)
{
return h1;
}
if (h1->val < h2->val)
{
return Merge(h2, h1);
}
else
{
return Merge(h1, h2);
}
}
Left Merge(Left h1, Left h2)
{
if (!h1->lc)
{
h1->lc = h2;
}
else
{
h1->rc = Merge_node(h1->rc, h2);
if (h1->lc->npl < h1->rc->npl)
{
Left tmp = h1->lc;
h1->lc = h1->rc;
h1->rc = tmp;
}
h1->npl = 1 + h1->rc->npl;
}
return h1;
}
时间复杂度: O(logn)
温馨提醒:这里加了一个辅助函数Merge_node可能不好理解,其实就是调整为了使h1总为最大的那个,也就是递归调用本身,多看几遍一定可以理解
三、插入
算法
插入操作的主要步骤:
- 为插入的元素开辟一块空间
- 将它的左右孩子设为空
- 将它与原堆合并
如下图所示:
代码实现
Left Insert(int x, Left heap)
{
Left h = new LeftHeap;
h->val = x;
h->npl = 1;
h->lc = nullptr;
h->rc = nullptr;
heap = Merge(h, heap);
return heap;
}
时间复杂度: O(logn)
四、删除
算法
删除操作的主要步骤:
- 先判断堆是否只有左孩子,如果有直接将其孩子设置为根结点返回
- 如果既有左孩子又有右孩子,将左孩子和右孩子合并成新的堆
- 释放原有空间,返回根结点
如下图所示:
代码实现
Left Erase(Left heap)
{
Left h = heap;
if (heap && !(heap->rc))
{
heap = heap->lc;
}
else
{
heap = Merge(heap->lc, heap->rc);
}
delete h;
return heap;
}
时间复杂度: O(logn)
五、完整代码
#include <iostream>
using namespace std;
typedef struct LeftHeap* Left;
Left Merge(Left h1, Left h2);
struct LeftHeap
{
int val;
Left lc;
Left rc;
int npl;
};
Left Merge_node(Left h1, Left h2)
{
if (!h1)
{
return h2;
}
if (!h2)
{
return h1;
}
if (h1->val < h2->val)
{
return Merge(h2, h1);
}
else
{
return Merge(h1, h2);
}
}
Left Merge(Left h1, Left h2)
{
if (!h1->lc)
{
if (h1 && h2 && h1->val < h2->val)
{
swap(h1, h2);
}
if (h1 && !h1->lc)
{
h1->lc = h2;
}
else
{
Merge(h1, h2);
}
}
else
{
h1->rc = Merge_node(h1->rc, h2);
if (h1->lc->npl < h1->rc->npl)
{
Left tmp = h1->lc;
h1->lc = h1->rc;
h1->rc = tmp;
}
h1->npl = 1 + h1->rc->npl;
}
return h1;
}
Left Insert(int x, Left heap)
{
Left h = new LeftHeap;
h->val = x;
h->npl = 1;
h->lc = nullptr;
h->rc = nullptr;
heap = Merge(h, heap);
return heap;
}
Left Erase(Left heap)
{
Left h = heap;
if (!(heap->rc))
{
heap = heap->lc;
}
else
{
heap = Merge(heap->lc, heap->rc);
}
delete h;
return heap;
}
int main()
{
Left php = nullptr;
Left phpx = nullptr;
//插入
Left ret1 = Insert(1, php);
Left ret2 = Insert(3, ret1);
Left ret3 = Insert(2, ret2);
cout << ret3->val << endl;
cout << ret3->lc->val << endl;
cout << ret3->rc->val << endl;
cout << ret3->npl << endl;
//删除
Left ret4 = Erase(ret3);
cout << ret4->val << endl;
cout << ret4->lc->val << endl;
//合并
ret1 = Insert(3, phpx);
Left ret5 = Merge(ret1, ret4);
cout << ret5->val << endl;
cout << ret5->lc->val << endl;
cout << ret5->lc->lc->val << endl;
system("pause");
return 0;
}