数据结构——树(2)——二叉堆的基本操作原理

来源:互联网 发布:起名软件生辰八字 编辑:程序博客网 时间:2024/04/28 00:05

二叉堆的存储

上次我们讨论了什么是二叉堆,以及完全二叉树的定义,这次我们来看看,我们应该如何来存储我们的二叉堆。先来看看下面的这个二叉堆:
这里写图片描述
我们看到这个,也许我们第一反应是用一个基于节点的方式储存:
这里写图片描述
是的,这就是一个很正常也是最容易想到的方式。然而,科学家却发现使用基于数组的方式能更好的存储我们的二叉堆。我们把根(root),放在下标为1的位置(而不是0),这是为了我们能从数学的角度更好的理解这个原理。 如下图所示:
这里写图片描述
为什么是这样存放我们的数组呢?因为用数组表示二叉堆,使得我们确定父亲与孩子之间的问题就变成了简单的算术问题:
对于每一个位于下标为 i 的元素来说
- 它的左孩子一定在 2i 的位置
- 右孩子一定在 2i + 1 的位置
- 父亲一定在[i / 2]的位置

我们举例子,上面的元素12,下标为4,所以它的左孩子的下标就是 2 x 4 = 8.也就是上图的22元素。它的右孩子同理。它的父亲为 4 / 2 = 2的位置,也就是元素10。

二叉堆的基本操作

还记得我们的之前提到过的优先级队列问题吗?数据结构——树(1)——二叉堆
基本功能如下:

操作 功能 enqueue(k,e): 将优先级为k的元素e插入到P中。 dequeue(): 从P中删除具有最高优先级的元素。 peek(): 返回具有最高优先级键的P的元素(但是不从队列中删除)

现在,我们可以从堆的角度去解决这个问题:

peek();

我们只需要返回堆的首元素就可以了,也就是

return heap[1];

复杂度??毫无疑问是O(1)!

enqueue(k);

在堆中插入元素。在这里假设我们要在堆中插入一个元素为9.我们应该要经过下面的一些步骤:
1. 我们先把元素插入到旧数组元素的尾部,也就是heap[heap.size() + 1]的位置。
2. 执行“冒泡”(bubble up)或“上堆”(up-heap)操作:比较添加的元素与其父亲, 如果按正确的顺序,停止操作,如果不是,交换并重复步骤2。

文字总是抽象难懂的,那么我们就图文解释走一波:
1. 一开始我们的初始堆假设就是我们开始的那个图:
这里写图片描述
2. 接着我们把新元素插入到旧数组的尾部,也就是堆中的空白位置(并不是随意的,以为可以通过下标计算父亲是谁),也就是heap[heap.size() + 1]的位置,在这个例子中就是我们的下标10:
这里写图片描述
3. 由于这个时候我们是新插入的元素,根据堆的属性,我们找到下标为10的元素的父亲位于哪个位置。也就是计算10 / 2 = 5,也就是位于5的元素就是新插入元素的父亲,那么它符不符合小堆的要求?11 > 9 显然是不符合,所以我们把他们进行交换。如下面两幅图所示:
4. 这里写图片描述
这里写图片描述
5. 好,这个时候我们再对比5号位置的值与它的父亲位置的值,这个时候我们计算 5 / 2 = 2.5,可是这里并没有下标为2.5的元素,我们采取的是向下取整(其实设置为int型就会自动转换的),所以下标为2的元素10,仍然大于9,于是我们再交换:
这里写图片描述
6. 重复以上的操作,发现5已经小于9了,因此操作结束,插入元素操作完成!

那么,这个操作的算法复杂度是多少呢?O(log n)!

dequeue();

dequeue操作,也就是我们要移除堆顶的元素。如果按照上述的例子,很显然我们要把5移除出去,但是这就有问题了,把5移除后,就破坏了堆的结构,那么谁来做这个堆顶呢?我们一般有下面的一些操作:
1. 当我们删除根的时候,我们仍然需要保留一个完全树:于是我们用最后一个元素替换根。
2. 使用下堆(down-heap)操作,寻找新符合条件的新的根:将根与它的孩子们进行比较,如果他们的顺序正确,操作停止。否则将根与两个孩子中最小的那个进行交换,然后继续重复步骤2.
3. 要时刻注意该节点是否存在孩子,如果没有孩子或者只有一个,那么就不用执行比较操作。那么如何判断呢?可以这样想,如果有该节点有右孩子,那么它的左孩子也必定存在!!是不是很有道理?
那么 我们同样图文解释一波:
1. 我们先假设我们的初始堆为下图:
这里写图片描述
2. 接下来我们移除我们的堆顶
这里写图片描述
3. 将最后一个元素(此时它的位置当然是在 heap[heap.size()] 处)移动到堆顶处,准备我们的down-heap操作。(注意,移动了堆顶以后,不要忘记了堆的大小是要减一的)
这里写图片描述
4. 将根与它的较小的还在进行比较,并交换(这里指的是13和8):
这里写图片描述
5. 如果有必要,那么我们继续保持交换,直到它再也没有更多的孩子跟它比较了,我们也就完成工作了!
这里写图片描述

至此,简单的二叉堆的基本操作就完成了,但是这有个问题。这些操作都是建立在已有堆的操作上的。但是如果给的是一堆无关的数字呢?显然我们要学会怎么建立一个二叉堆!下次再说,如何建立二叉堆与如何完成堆排序。

阅读全文
1 0
原创粉丝点击