-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
二叉堆 #35
Comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
完全二叉树
简单来说,完全二叉树的每一层的节点都是满的,除了最后一层,最后一层缺少的节点只能出现在右边,不能出现在左边和中间。
举例:
完全二叉树的数组表示
因为完全二叉树自身的特点(各个节点从上往下,从左往右依次排列),可以使用数组来表示一颗完全二叉树。
二叉堆的定义
堆
二叉堆
当没有特殊说明时,都用二叉堆来指代堆。
二叉堆同样必须满足是一颗完全二叉树。
最大堆
任意节点大于等于它的所有后裔,最大节点是树的根节点:
最小堆
任意节点小于等于它的所有后裔,最小节点是树的根节点:
二叉堆的操作
下面介绍的所有操作,都基于最大堆进行。
上浮(swim)
理想情况下,一个二叉堆是有序的,任意父节点都大于等于其子节点以及子节点的子节点...。
上浮用于解决二叉堆在末尾添加节点的问题。
下面这是一颗有序的二叉堆:
现在想要添加一个节点
12
到堆的末尾,也就是作为7
的右子节点:添加以后,就破坏了二叉堆的有序状态,因为任意节点不再大于等于其后的子节点。
从肉眼可以判断,有序状态下
12
应该位于根节点的位置。从代码操作来看,就需要将
12
上浮到根节点的位置,先和7
交换位置,再和9
交换位置,最后和10
交换位置。这个过程中节点
12
一步一步向上移动,所以形象地称为上浮。上浮操作以后,二叉堆变为如下,又重新会到了有序状态:
代码实现
下沉(sink)
下沉用于解决二叉堆删除节点的问题,删除过程和插入过程相反,指删除堆的头节点也就是最大元素,删除以后将末尾的节点移动到头节点的位置,移动以后打破了堆的有序状态,需要将新的头节点下沉到合适的位置
删除头节点
12
,将末尾节点7
作为新的头节点:此时,堆不再有序,需要对新的头节点进行下沉操作,首先将
7
和它对子节点中较大的一个10
交换,然后再和9
交换,堆重新回到了有序状态:代码实现
构建堆
上浮和下沉操作都是对二叉堆的修复操作,将局部无序的二叉堆修复成整体有序。
而构建是将一颗无序的完全二叉树变成整体有序的二叉堆。
比如,有如下一颗完全二叉树,现在想要将其有序化成二叉堆:
想要让整体有序,可以先使其局部有序,每个局部都有序后,整体自然就有序了。
所以,可以将整棵树看作由
4
颗小树组成,分别是:我们可以从后往前,分别对这
4
颗树的根节点进行下沉操作,后面两棵树进行下沉操作后没有变化。接着对第二颗树
tree(2, 10, 6)
进行下沉操作之后,变为:注意,因为所有操作都是基于数组的索引,对第二颗树
tree(2, 10, 6)
的有序化操作,间接破坏了最后一棵树tree(10, 8, 3)
的有序化,使其变为:所以,在对一颗树进行有序化后,如果后面的树的有序状态被破坏,需要接着对后面再次进行有序化操作,这样直到进行到第一颗树为止。
代码实现
sink
函数有小的变化,前面的sink
默认从根节点开始执行,这里的sink
从指定的节点开始。复杂度
时间复杂度
添加、删除节点
还是以一个二叉堆为例:
添加节点:
当在末尾添加节点的时候,最多需要多少次比较和多少次交换才能使堆有序呢?
比如添加的节点是
20
,那么需要分别和7
、9
、10
比较并且交换。所以,求添加节点操作的时间复杂度相当求树的高度。
删除节点:
同样,删除根节点后,将末尾节点移动到根节点的位置。最坏情况下,需要将新的根节点下沉到树的底部。
所以,同样也是求树的高度。
那么,大小为
N
的完全二叉树的高度是多少呢?已知高度为
n
的完全二叉树最多有2^n - 1
个节点。所以,
N = 2^n - 1 => n = logN + 1
。大小为
N
的完全二叉树的高度为logN + 1
。最后
添加节点:
O(logN)
,N
为节点数量。最多需要logN
次比较和logN
次交换操作。删除节点:
O(logN)
。一个节点下沉一次,需要两次比较(两个子节点相互比较一次再和该节点比较一次)和一次交换。最多需要2logN
次比较和logN
次交换操作。构建堆
还是以一个二叉堆为例:
这里令
n
为节点数量。因为我们采用的是自底向上的使用
sink
来构建堆,所以:对于最底层的
n/2
个节点来说,需要下沉0
次对于次底层的
n/4
个节点来说,需要下沉1
次对于第二层的
n/8
个节点来说,需要下沉2
次...
所以,总的的执行次数
S = 0 * n/2 + 1 * n/4 + 2 * n/8 + ...
。2S = 0 * n + 1 * n/2 + 2 * n/4 + ...
2S - S = 1 * n/2 + 1 * n/4 + 1 * n/8 + ...
最后用等比数列的求和公式可以得出:
S = n
所以,构建一个二叉堆的时间复杂度是 O(n)。
空间复杂度
O(1)
,没有使用额外空间。参考资料
The text was updated successfully, but these errors were encountered: