最大堆(一)

背景知识:

  1. 堆是一棵完全二叉树,什么是完全二叉树?就是除了最后一层节点之外,其他层的节点个数必须是最大值,并且最后一层的节点必须都集中在左侧。
  2. 堆分为最大堆和最小堆。最大堆就是父节点大于等于子节点,从而导致根节点是最大值。最小堆就是父节点小于等于子节点,从而导致根节点是最小值。本文基于最大堆讲解,并且提供python代码实现。

1.背景知识中提到,堆是一棵完全二叉树,并且最大堆的特点是父节点大于等于子节点,为了更直观的理解什么是最大堆,请看下图:

图A是一个最大堆,首先它满足完全二叉树的性质,即除了最后一层节点数不是最大以外,其他层节点数达到最大,同时最后一层节点都集中在左侧。其次它满足最大的性质,即每一个父节点的值都是大于等于子节点的。

图B不是一个最大堆,因为它不是一棵完全二叉树,因为第二层的节点个数并没有达到最大

图C不是一个最大堆,虽然它除了最后一层其他层的节点个数达到了最大,但是最后一层的节点没有集中在左侧

图D不是一个最大堆,虽然在定义上它是一棵完全二叉树,但是它不满足最大堆每一个父元素的值都大于等于子元素,因为我们可以很明显的看到62是50的右孩子。

2.经过上述的分析,相信读者对于最大堆已经有了很深刻的认识,接着如果我们对最大堆,至上而下从左往右依次从1开始标序,例如:

我们可以发现:左孩子的下标是父亲下标的两倍,右孩子的下标是父亲下标的两倍加一,而父亲下标则是孩子下标除以二取整。基于这个性质我们可以使用数组这种数据结构来存储最大堆。

3.在利用数组这个数据结构构建最大堆的过程称之为Shift Up,什么是Shift Up呢?在数组中添加一个元素之后,根据新添加孩子的下标找到父亲的下标,比较这两个元素的大小,如果父亲的值小于孩子的值,则交换对应的值,接着再向前比较,依次类推,直到父亲的值小于孩子的值时,则停止。具体代码实现:

import random
class maxStack:
    def __init__(self):
        # 将下标为0的元素赋值为0,显示约束下标为0不可用,因为本案例实现的最大堆是从下标为1开始
        self.data = [0]
        self.count = 0


    # 第一个元素是从下标为1开始的
    def insert(self, item):
        self.data.append(item)
        self.count += 1
        self.__shiftUp(self.count)

    # 判断新增的元素是否比父元素大,大的话就交换位置,依次类推
    def __shiftUp(self, count):
        while count > 1 and self.data[count // 2] < self.data[count]:
            self.data[count // 2], self.data[count] = self.data[count], self.data[count // 2]
            count = count // 2

if __name__ == '__main__':
    stack = maxStack()
    for i in range(0, 10):
        stack.insert(random.randint(10, 100))

#经过上述操作之后,stack.data这个列表存储的值就是一个最大堆。


4.我们都知道树结构取值都是取根元素的,同样最大堆也是如此,即下标为1的元素就是根元素。这时我们不得不提一个与Shift Up相对应的操作,Shift Down 操作。什么是Shift Down操作?当我们取出数组的第一个元素即下标为1的元素时,交换第一个元素和最后一个元素的值,删除掉最后的元素之后,这时数组可能不满足最大堆的性质了,怎么解决?从根元素开始,比较两个孩子的大小,较大的孩子再和父元素比较,如果比父元素小,则停止,否则交换位置,依次向下追溯,直至节点为叶子节点(即没有孩子)或者父元素的值大于子元素时停止,具体的代码实现:

import random
class maxStack:
    def __init__(self):
        # 将下标为0的元素赋值为0,显示约束下标为0不可用,因为本案例实现的最大堆是从下标为1开始
        self.data = [0]
        self.count = 0

    # 获取元素:堆的元素获取只能获取根节点也就是下标为1的元素,然后将最后的元素挪到根节点,然后count减1相当于移除最后的元素
    def getMax(self):
        if self.count <= 0:
            return
        root = self.data[1]
        self.data[1], self.data[self.count] = self.data[self.count], self.data[1]
        self.count -= 1
        self.__shiftDown(1)
        return root

    # 首先比较当前节点的两个子节点的大小,如果大的那个子节点比父元素大就交换位置
    def __shiftDown(self, key):
        # 在完全二叉树中,只要保证有左节点那么这个节点就是有孩子的,因为完全二叉树并不存在有右节点而没有左节点
        while 2 * key <= self.count:
            # 默认change指向左节点
            change = 2 * key
            # 如果存在右节点并且右节点比左节点大,那么将change加1,此时change指向了右节点
            if change + 1 <= self.count and self.data[change + 1] > self.data[change]:
                change += 1
            # 经过上面两步,此时change指向的值是key父节点最大的孩子节点
            if self.data[change] <= self.data[key]:
                break
            self.data[change], self.data[key] = self.data[key], self.data[change]
            key = change


5.完整的python测试用例:

import random

# 最大堆(完全二叉树,并且父元素大于等于子元素)的实现,使用数组的方法实现(python中使用列表)
class maxStack:
    def __init__(self):
        # 将下标为0的元素赋值为0,显示约束下标为0不可用,因为本案例实现的最大堆是从下标为1开始
        self.data = [0]
        self.count = 0

    def is_empty(self):
        return 0 != self.count

    def get_size(self):
        return self.count

    # 第一个元素是从下标为1开始的
    def insert(self, item):
        self.data.append(item)
        self.count += 1
        self.__shiftUp(self.count)

    # 判断新增的元素是否比父元素大,大的话就交换位置,依次类推
    def __shiftUp(self, count):
        while count > 1 and self.data[count // 2] < self.data[count]:
            self.data[count // 2], self.data[count] = self.data[count], self.data[count // 2]
            count = count // 2

    # 获取元素:堆的元素获取只能获取根节点也就是下标为1的元素,然后将最后的元素挪到根节点,然后count减1相当于移除最后的元素
    def getMax(self):
        if self.count <= 0:
            return
        root = self.data[1]
        self.data[1], self.data[self.count] = self.data[self.count], self.data[1]
        self.count -= 1
        self.__shiftDown(1)
        return root

    # 首先比较当前节点的两个子节点的大小,如果大的那个子节点比父元素大就交换位置
    def __shiftDown(self, key):
        # 在完全二叉树中,只要保证有左节点那么这个节点就是有孩子的,因为完全二叉树并不存在有右节点而没有左节点
        while 2 * key <= self.count:
            # 默认change指向左节点
            change = 2 * key
            # 如果存在右节点并且右节点比左节点大,那么将change加1,此时change指向了右节点
            if change + 1 <= self.count and self.data[change + 1] > self.data[change]:
                change += 1
            # 经过上面两步,此时change指向的值是key父节点最大的孩子节点
            if self.data[change] <= self.data[key]:
                break
            self.data[change], self.data[key] = self.data[key], self.data[change]
            key = change


if __name__ == '__main__':
    stack = maxStack()
    list = [random.randint(10,100) for i in range(100)]
    print('原数组:',end=' ')
    print(list)
    for i in range(0, 100):
        stack.insert(list[i])
    print('最大堆数组:',end=' ')
    print(stack.data)
    # 循环取出堆的元素,可以发现最大堆循环取出的元素是从大到小排序的
    while stack.is_empty():
        print(stack.getMax(), end=' ')




猜你喜欢

转载自blog.csdn.net/wx145/article/details/83004793