完全二叉树:除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点。

完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。

完全二叉树是效率很高的数据结构,是一种完全二叉树或者近似完全二叉树,所以效率极高,像十分常用的排序算法、Dijkstra算法、Prim算法等都要用堆才能优化,几乎每次都要考到的二叉排序树的效率也要借助平衡性来提高,而平衡性基于完全二叉树。


一、堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆的定义如下:

n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。

(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)

若将和此次序列对应的一堆数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,k3,k4,,,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。

二、堆支持的基本操作:
如斐波那契堆支持检查一个堆中是否存在某个元素。

三、算法思想:
不必将值一个个地插入堆中,通过交换形成堆。假设根的左、右子树都已是堆,并且根的元素名为R。这种情况下,有两种可能:
(1)R的值小于或等于其两个子女,此时堆已完成;
(2)R的值大于其某一个或全部两个子女的值,此时R应与两个子女中值较小的一个交换,结果得到一个堆,除非R仍然大于其新子女的一个或全部的两个。这种情况下,我们只需简单地继续这种将R“拉下来”的过程,直至到达某一个层使它小于它的子女,或者它成了叶子结点。

四、筛选法:
首先将要排序的所有关键码放到一棵完全二叉树的各个结点中(这时的完全二叉树并不具备堆的特性)。显然,所有的结点ki都没有子女结点,因此以这样的ki为根的子树已经是堆,然后从的结点ki开始,逐步把以为根的子树排成堆,直到以k0为根的子树排成堆,就完成了建堆过程。
在考虑将以ki为根的子树排成堆时,以ki+1,ki+2,...,kn-1为根的子树已经是堆,所以这时如果有ki<=k2i+1和ki<=k2i+2,则不必改变任何结点的位置,以ki为根的子树就已经是堆;否则就要适当调整子树中结点的位置以满足堆的定义。由于ki的左、右子树都已经是堆,根结点是堆中最小的结点,所以调整后ki的值必定是原来k2i+1和k2i+2中较小的一个。不妨假定k2i+1较小,将ki与k2i+1交换位置,这样调整后ki<=k2i,ki<=k2i+1,并且以k2i+2为根的子树原来已经是堆,不必再做任何调整,只有以k2i+1为根的子树由于k2i+1的值已经发生变化(与ki交换了),所以有可能不满足堆的定义(当k2i+1的左、右子树已经是堆)。这时可重复上述过程,考虑将k2i+1以为根的子树排成堆。如此一层一层递推下去,最多可以一直进行到树叶。由于每一步都保证将子树中最小的结点交换到子树的根部,所以这个过程是不会反馈的。它就像过筛一样,把最小的关键码一层一层选择出来。

五、建堆效率

六、堆操作的具体实现
在程序中,堆用于动态分配和释放程序所使用的对象。在以下情况中调用堆操作:
1、事先不知道程序所需对象的数量和大小。
2、对象太大,不适合使用堆栈分配器
堆使用运行期间分配给代码和堆栈以外的部分内存。

传统上,操作系统和运行时库随附了堆实现。当进程开始时,操作系统创建称为进程堆的默认堆。如果没有使用其他堆,则使用进程堆分配块,语言运行时库也可在一个进程内创建单独的堆。(例如,C运行时库创建自己的堆。)除这些专用堆外,应用程序或许多加载的动态链接库(DLL)之一也可以创建并使用单独的堆。win32提供了一组丰富的api用于创建和使用专用堆。有关堆函数的优秀教程,请参阅MSDN平台SDK节点。
当应用程序或DLL创建专用堆时,这些堆驻留于进程空间中并且在进程范围内是可访问的。某一给定堆分配的任何数据应为同一堆所释放。(从一个堆分配并释放给另一个堆没有意义。)
在所有虚拟内存系统中,堆位于操作系统的虚拟内存管理器之上。语言运行时堆也驻留在虚拟内存之上。某些情况下,这些堆在操作系统堆的上层,但语言运行时堆通过分配大的块来执行自己的内存管理。绕开操作系统堆来使用虚拟内存函数可使堆更好地分配和使用块。
典型的堆实现由前端分配器和后端分配器组成。前端分配器维护固定大小块的自由列表。当堆收到分配调用后,它尝试从前端列表中查找自由块。如果此操作失败,则堆将被迫从后端(保留和提交虚拟内存)分配一个大块来满足请求。通常的实现具有每个块分配的开销,这花费了执行周期,也减少了可用存储区。

windows nt 的实现(windows nt 4.0版及更高版本)使用127个从8到1,024字节不等的8字节对齐块的自由列表和1个混合列表。混合列表(自由列表【0】)包含大小超过1,024字节的块。自由列表包含在双向链接表中链接在一起的对象。默认情况下,进程堆执行合并操作。(合并操作是组合相邻的自由块以生成更大的块的操作。)合并操作花费了额外的周期,但减少了堆块的内部碎片。

单个全局锁可防止多线程同时使用堆。此锁主要用于保护堆数据结构不受多线程的任意访问。当堆操作过于频繁时,此锁会对性能造成负面影响。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值