堆、二叉堆的实现和特性

196 阅读4分钟

堆(HEAP) 介绍

en.wikipedia.org/wiki/Heap_(…

堆(HEAP)定义: 可以迅速的找到一堆数中的 最大数和最小数

分类:

  • 大顶堆(Max Heap)
      • 最大的元素在堆顶 元素越大优先级越高
    • 父节点要比左右孩子大
  • 小顶堆 (Mix Heap):
    • 最小的元素在堆顶 元素越小优先级越高
    • 父节点要比左右孩子小
  • 常见的有
    • 二叉堆
    • 斐波那契堆

堆的时间复杂度

  • find max O(1) --大顶堆
  • delete max O(logN)
  • insert O(logN)/O(1)-斐波那契堆

各种堆的时间复杂度对比

二叉堆 介绍

  • 通过完全二叉树实现的(属性 叶子是满的 除了最下面的一层)
  • 树中 任意根节点的值 >= 子节点的值(所 以根节点是最大的)
  • (数组下标)索引为i的左孩子的索引为 (2*i + 1)
  • (数组下标)索引为i的右孩子的索引为 (2*i + 2)
  • (数组下标)索引为i的父节点的索引是 floor((i-1)/2) 取整

image.png

二叉堆的插入操作

  • step1 新元素一律插入堆的尾部
  • step2 重新维护堆的元素排序 HeapifyUp
    • 依次向上调整整个堆的结构(heapifyup)
    • 如果 当前元素 > 父节点 则进行交换, 直到 <= 父节点

最坏的时间复杂度 O(logN)

第一步 将插入的元素放入堆的尾部

image.png

image.png 这个时候 堆的属性被破坏了

第二步 依次和父节点进行比较

直到<= 父节点

大顶堆的插入代码示例

    def swap(self, fpos, spos):
        """
        交换两个下标的值
        :param fpos: 
        :param spos: 
        :return: 
        """
        self.Heap[fpos], self.Heap[spos] = self.Heap[spos], self.Heap[fpos]
            
    def insert_maxheap(self, element):
        """
        大顶堆的插入操作
        思路
            - 新元素插入堆的尾部
            - 依次和父节点进行比较
                -   如果新元素 > 父节点 则进行交换;否则结束比较 完成插入操作
        :param element:
        :return:
        """
        # 判断是否存在溢出
        if self.size >= self.capacity:
            return

        # 堆的尾部插入新元素
        self.size += 1
        self.Heap[self.size] = element

        # 新元素的下标
        current = self.size

        # 新元素 > 父节点 则进行交换
        while self.Heap[current] > self.Heap[self.parent(current)]:
            self.swap(current, self.parent(current))
            # 插入元素 获取新的下标(即父节点的下标)
            current = self.parent(current)

小顶堆的插入代码示例

    def insert(self, element):
        """
        小顶堆的插入操作 
        思路
            - 新元素插入堆的尾部
            - 依次和父节点进行比较
                -   如果新元素 < 父节点 则进行交换;否则结束比较 完成插入操作
        :param element:
        :return:
        """
        # 判断是否存在溢出
        if self.size >= self.capacity:
            return

        # 堆的尾部插入新元素
        self.size += 1
        self.Heap[self.size] = element

        # 新元素的下标
        current = self.size

        # 新元素 < 父节点 则进行交换
        while self.Heap[current] < self.Heap[self.parent(current)]:
            self.swap(current, self.parent(current))
            # 插入元素 获取新的下标(即父节点的下标)
            current = self.parent(current)

2.删除max操作

  • 将堆尾的元素 去替代 堆顶的元素
  • 依次从根部 向下调整整个堆的结构(一直调整到堆尾部)
    • 如果当前结点 < 子节点 则进行交换,
  • heapifydown
  • 最坏的时间复杂度 O(logN)

删除max示例

最开始的结构

将堆顶元素删除掉

用尾部元素替换 被删除的堆顶元素

和子结点进行比较 和最强的子节点 进行替换 40<-->85

和子结点进行比较 和最强的子节点 进行替换 40<-->8540<-->60

大顶堆删除代码示例


    def delete(self):
        # 删除第一个下标(即删除最小的元素)
        popped = self.Heap[self.FRONT]

        # 将堆尾的元素 放入堆顶
        self.Heap[self.FRONT] = self.Heap[self.size]
        self.size -= 1

        # 从下标1 也就是堆顶依次进行对比操作
        self.heapifyDown(self.FRONT)
        return popped
        
    def heapifyDown_maxheap(self, pos):
        """
        大顶堆的操作
        
        - 依次和自己的儿子节点进行比较
            - 如果父节点 < 最大的儿子 则将最差的儿子节点和自己交换
            - 直到父节点 >= 最大的儿子
        :param pos:
        :return:
        """

        if not self.isLeaf(pos):

            if (self.Heap[pos] < self.Heap[self.leftChild(pos)] or
                    self.Heap[pos] < self.Heap[self.rightChild(pos)]):

                # 左节点最大 则左节点和父节点进行交换
                if self.Heap[self.leftChild(pos)] > self.Heap[self.rightChild(pos)]:
                    self.swap(pos, self.leftChild(pos))
                    self.heapifyDown(self.leftChild(pos))

                # 右节点最大 则右节点和父节点进行交换
                else:
                    self.swap(pos, self.rightChild(pos))
                    self.heapifyDown(self.rightChild(pos))

小顶堆删除代码示例

    def heapifyDown(self, pos):
        """
        小顶堆的删除操作

        思路:
            - 将堆顶元素(min values)删除
            - 将堆尾元素 替换到堆顶
            - 依次和自己的儿子节点进行比较
                - 如果父节点 > 最小的儿子 则将最差的儿子节点和自己交换
                - 直到父节点 <= 最小的儿子
        :param pos:
        :return:
        """

        if not self.isLeaf(pos):

            if (self.Heap[pos] > self.Heap[self.leftChild(pos)] or
                    self.Heap[pos] > self.Heap[self.rightChild(pos)]):

                # 左节点最小 则左节点和父节点进行交换
                if self.Heap[self.leftChild(pos)] < self.Heap[self.rightChild(pos)]:
                    self.swap(pos, self.leftChild(pos))
                    self.heapifyDown(self.leftChild(pos))

                # 右节点最小 则右节点和父节点进行交换
                else:
                    self.swap(pos, self.rightChild(pos))
                    self.heapifyDown(self.rightChild(pos))

注意

  • 二叉堆只是堆的一种实现方式 但不是最优的
  • 工程使用的时候 直接调用优先队列(priority queue)