二叉堆详解.docx
- 文档编号:9110679
- 上传时间:2023-02-03
- 格式:DOCX
- 页数:10
- 大小:74.45KB
二叉堆详解.docx
《二叉堆详解.docx》由会员分享,可在线阅读,更多相关《二叉堆详解.docx(10页珍藏版)》请在冰豆网上搜索。
二叉堆详解
5.3应用举例
5.3.1二叉堆(BinaryHeap)——树的应用举例
5.3.1.1应用堆(Heap)的意义
在计算机科学应用中,我们经常会碰见这样一种情况,即需要在一组给定数据中找到满足某种条件下的最优数据,比如从一个整型数组中找到其最大值、最小值的问题。
这样问题的解决方法虽然并不困难,但是由于这种最基本的操作会在其它众多算法中被多次调用,所以如果能找到一种足够有效的算法,使得这种操作的时间复杂度尽可能的降低,那么必然会对提高一些算法的效率有所帮助。
对于解决这种问题,我们通常的做法是从这组数据的第一个元素开始进行扫描,逐个比较这组数据中的每一个元素,当我们完成对数据的扫描后,即可获得我们所要求得的结果。
但这样做的效率无疑是极其低下的,我们必须比较所有数据后才能得到我们想要的数据,时间复杂度为O(n)。
显然这种解决方案在很多情况下是很不现实的。
如果我们再进一步思考,可能会得到这样一种办法,我们可以先对所有数据进行排序,然后我们就可以在这组有序数据集中一下子找到我们所需要的数据。
但是在以后我们会学到,对一组数据进行排序,通常情况下最优时间复杂度也只能达到O(nlogn)的程度,所以实际上并没有起到多大的优化效果。
但是这种方法确可以给我们一些启发,在计算机应用中,我们实际上很少需要对一堆固定的数据进行查找。
多数情况下是这样的,我们所需要查找的数据集是在不断变化的,或者向其中添加一新些数据,或者从其中删掉一些已经不需要的数据,甚至只是改变其中某个元素的值。
如果我们在而每次数据发生变化时,花费较少的代价去维护这组数据,使其有序,而每次查找都只需从有序数据集中直接获取数据,那么我们的优化目的就达到了。
现在我们将要介绍的数据结构堆就是这样一种帮我们实现这种操作的利器。
在取得最优值时只需O
(1)的时间复杂度,而在堆中进行插入、删除、修改操作时,也只需要O(logn)的时间复杂度。
5.3.1.2二叉堆的定义
堆是一种特殊的树形结构,其特点是根结点的值最小(最大),且根结点的子树也为一个堆,称为最小堆(最大堆),有时也称小根堆(大根堆)。
这里我们只介绍堆结构中最常用的二叉堆,即用二叉树结构来构造的堆。
通常情况下,为了计算机存储和运算的方便,一般会将二叉堆作为一棵完全二叉树来存储。
下面是就一种最小二叉堆的例子,后面将假设我们所提到的堆均指最小二叉堆:
5-3-1
5.3.1.3二叉堆的操作
假设现在我们已经建立好了一个如图5-3-1所示的具有7个数据结点的二叉堆:
(1)寻找堆中的最小值
在任意一个最小二叉堆中,由“根结点的值最小”这一性质可知,其最小值就是这颗二叉树中根结点的值,因为堆中的根结点永远是所有数据中最小的一个。
如图5-3-1中堆的最小值为3。
伪代码:
HEAP_GET_MIN(HEAP)
returnHEAP[1]
(2)向堆中插入一个数据
由于堆的每一颗子树仍是一个堆,所以当我们向堆中插入一个新的数据时,必需仍然保持堆的这一性质不变。
正确的做法是,我们先将待插入的数据放在这棵二叉树的最后一个位置上,然后将其不断和父亲结点进行比较、上移,找到其最后的位置。
如图5-3-2。
5-3-2
插入前的堆中共有7个数据结点,我们将待插入的数据放在第8个位置上
5-3-3
为了使这棵二叉树仍然为一个堆,必需继续使它保持“根结点的值最小”和“根结点的子树也为一个堆”这两条性质。
所以以4号结点为根结点的这棵子二叉树中,值最小的结点应该放在根结点处,显然在这棵子树中8号结点的值最小,应该将它放置在4号位置,同时让原4号结点下移至8号位置。
为了节省时间,减少移动次数,我们只是先将8号结点用一个临时变量存储起来,然后将4号结点挪到8号结点的位置上,以后再对4号结点进行比较时,改为和我们设置的临时结点变量比较。
这步操作保证了以4号结点为根结点的子树为一个堆。
5-3-4
同样,临时变量中保存的待插入结点的值小于2号结点的值,由于2号结点为原来以2号结点为根结点的子二叉树中的最小值,所以在新的子树中,临时变量保存的结点即为现子二叉树的最小结点。
那么我们将2号结点挪到4号结点的位置上去,2号位置应该存放的是待插入结点,仍然将其保存在临时变量中不变。
以2为根结点的子二叉树仍然是一个二叉堆。
5-3-5
待插入结点的值大于1号结点的值,1号结点即为整棵树的最小结点,不再将待插入结点上移,2号即为待插入结点的最终位置,将临时变量中保存的待插入结点放入2号位置,即可最终保证整棵树继续满足堆的所有性质。
显然,经过这几步操作后,插入新数据后的二叉树仍然满足“根结点的值最小”和“根结点的子树也为一个堆”这两条性质。
且由二叉树的性质可知,当原来堆中的数据元素个数为n时,向堆中插入一个新的结点,最坏情况下也只需要各进行[logn]次比较和移动操作,所以堆结构的插入操作最坏时间复杂度仅为O(logn),同向一个线性数组中插入元素相比,效率已经有了极大的提高。
伪代码:
HEAP_MOVE_UP(HEAP,i)
ni//n指向当前结点
pPARENT[n]//p指向父亲结点
nodeHEAP[i]//node为临时变量
whilep<>1andnode HEAP[n]HEAP[p] np pPARENT[p] HEAP[n]=node HEAP_INSERT(HAEP,data) heap_sizeheap_size+1//将data插入堆中的最后一个位置 HEAP[heap_size]data HEAP_MOVE_UP(HEAP,heap_size)//对新加入的元素进行上移调整 从堆中移除最小数据 5-3-6 如图5-3-6所示,要将堆中最小数据1号结点删除,删除后堆中的数据个数从8个减少到7个,我们在前面提到过,为了计算和存储方便,我们要保证堆始终为一棵完全二叉树,所以进行删除操作后,只有8号位置是多余的。 这样我们便可以将8号结点挪到1号位置,这样1号结点被删除,堆的数据总数减一。 之后我们再对1号结点进行下移操作,找到其正确位置即可。 5-3-7 1、2、3号结点中,2号结点的值最小,易知2号结点为当前堆中的最小值,所以将其和1号结点交换,这样便能保证堆中“根结点的值最小”,且1号结点的右子树为堆不变。 我们只需保证以2号结点为根结点的子二叉树为堆,即可做到删除最小结点后使堆的性质保持不变。 5-3-8 同样,将2号结点和4号结点交换,保证以2号结点为根结点的子二叉树“根结点的值最小”。 这时以4号结点和5号结点为根结点的子二叉树均为堆,所以以2号结点的子二叉树也为堆,最终保证整棵二叉树仍为一个堆。 删除结点后的二叉树如图5-3-9所示,我们还可以像插入操作时一样,用一个临时变量来保存值为6的那个结点,起到减少移动次数的作用。 伪代码: HEAP_MOVE_DOWN(HEAP,i) ni//n指向当前结点 lLEFTCHILD(n)//l指向左孩子 rRIGHTCHILD(n)//r指向右孩子 whiletrue //选择当前结点及其左孩子中最小的 ifl<=heap_sizeandHEAP[l] elseminn //选择当前结点及其左右孩子中最大的 ifr<=heap_sizeandHEAP[r] //如果最小结点为当前结点则退出循环 ifn=minbreak exchangeHEAP[n]<->HEAP[min] nmin lLEFTCHILD(n) rRIGHTCHILD(n) HEAP_DEL_MIN(HEAP) tempHEAP[1] HEAP[1]HEAP[heap_size] heap_sizeheap_size-1 HEAP_MOVE_DOWN(HEAP,1) returntemp 5-3-9 建堆及堆的存储结构 之前的三种操作都是在假定已经存在一个已经构建好了的堆的情况下进行的,现在让我们来考虑一下如何用给定的一组数据来构建一个新堆。 由于通常将堆构造成一棵完全二叉树,所以我们可以仅用一个数组来表示一个二叉堆,对于数组中的某一个元素,只需要根据其下标,通过对其除以2去整、乘2和乘2加1来分别找到它的父亲结点、左孩子和右孩子。 初始状态下这些数据存储在这样的一个数组中,我们就假设这些数据已经构成了一棵完全二叉树,通过对这棵二叉树的调整来构建出一个堆。 构建过程是这样的: 从这棵二叉树的第一个非叶子结点开始遍历,按照数组标号依次递减至二叉树的根结点,每次将该结点做一遍下移操作,这样就会使得以该结点为根的子二叉树为一个堆,当最后遍历完根结点,整棵树也随之成为一个最小二叉堆。 伪代码: HEAP_BUILD(A) HEAPA heap_sizelength(A) foriheap_size/2downto2 doHEAP_MOVE_DOWN(HEAP,i)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 二叉 详解