左式堆(以大堆为例)

左式堆

 

左式堆存在的意义是什么?

我们来看下面场景:

如何将堆A和堆B合并为一个堆H呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qkbBUIiw-1667874431350)(左式堆.assets/image-20221107133625111.png)]

相信你在思考过后可能会想到我们之前学过的完全二叉堆(如果不了解,可以参考这篇博客)的操作方法,不错的确可以使用完全二叉堆的操作接口进行两个堆的合并,但是效率如何?我们接下来分析一下

  • 方法一:

    将B堆依次取出插入到A堆中

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jrvsbnqk-1667874431351)(左式堆.assets/image-20221107193530299.png)]

  • 方法二:

    将A、B堆混合并且使用建堆

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PfvDUApt-1667874431352)(左式堆.assets/image-20221107193637776.png)]

我们是否有更优的方法?

这就是左式堆存在的意义了,能够使效率达到O(logn)

 

一、左式堆的基本概念与结构

首先我们需要引入一个概念:空结点路径长度(Null Path Length)

空结点路径长度计算方法

  • npl(null) = 0
  • npl(x) = 1 + min(npl(lc(x)), npl(rc(x)))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-efnyD6ps-1667874431353)(左式堆.assets/image-20221107194617959.png)]
说明:

  1. npl(x) = x到外部结点的最近距离
  2. npl(x) = 以x为根的最大满子树的高度

 

左倾性:

  • 对任何内结点,都有npl(lc(x)) >= npl(rc(x))
  • 对任何内结点, 都有npl(x) = 1 + npl(rc(x))

满足左倾性的堆就是左式堆

 

接下来我们来看看左式堆的右侧链条

从根结点x出发,一直沿右分支前进,最终它一定会抵达全堆中最浅的外部结点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyeXGwUK-1667874431353)(左式堆.assets/image-20221107201135900.png)]

右侧链长度为d的左式堆至少有2^(d+1) + 1(这里用等比数列的求和性质),反之,当包含n个结点时,右侧链的长度最多为log(n + 1) - 1 = O(logn)

 

二、合并

算法

合并操作的主要步骤:

  1. 判断堆a是否有左孩子(根据左式堆特征也一定没有右孩子),如果没有则直接将b作为a的左孩子
  2. 如果有孩子,则将a和b中较大的那个堆的右孩子和另一个堆继续递归合并,直到当前堆a没有了右孩子,把堆b当成它的右孩子
  3. 递归返回后,调整它的左倾性,并且更新它的npl
  4. 返回堆a的根结点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f74TwP2r-1667874431353)(左式堆.assets/image-20221107205000820.png)]

以下图为例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iJ0bGqUO-1667874431354)(左式堆.assets/image-20221107205816754.png)]

 

代码实现

#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总为最大的那个,也就是递归调用本身,多看几遍一定可以理解

 

三、插入

算法

插入操作的主要步骤:

  1. 为插入的元素开辟一块空间
  2. 将它的左右孩子设为空
  3. 将它与原堆合并

如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-59Dc54ur-1667874431354)(左式堆.assets/2.gif)]

 

代码实现

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)

 

四、删除

算法

删除操作的主要步骤:

  1. 先判断堆是否只有左孩子,如果有直接将其孩子设置为根结点返回
  2. 如果既有左孩子又有右孩子,将左孩子和右孩子合并成新的堆
  3. 释放原有空间,返回根结点

如下图所示:

在这里插入图片描述

 

代码实现

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值