堆与堆排序

堆与堆排序


堆的定义

  • 堆是一个完全二叉树:除了最后一层外,其他层几点必须是满的,且最后一层节点从左向右逐步填充
  • 堆中每一个父节点的值必须大于等于(或小于等于)其两子树节点的值:大于等于是大顶堆,小于等于是小顶堆

堆的实现

    完全二叉树使用使用数组来存储,所以堆也是基于数组来实现的,堆最重要的是下面两个基础操作:

  • 新增(插入)新元素
  • 删除堆顶元素

    下面详细说明下其操作方法:

新增元素

    新增元素的算法如下:

  • 1.将新增的元素放到堆的最后,也就是数组的最后,但此时可能导致不符合堆的特性了,所以需要第二步的自下向上堆化
  • 2.堆化,这里采用自下向上的堆化,即沿着节点的路径,与其父节点进行比较,如果不满足父子节点关系则交换,再继续比较;无法交换则堆化完成

    下图是一个大顶堆的堆化过程,首先将新元素22插入尾部,然后进行堆化

删除堆顶元素

    删除堆顶元素的算法如下:

  • 1.交换堆顶元素和堆尾元素的值,这样堆就减小,堆顶元素也就删除了,但此时可能导致不符合堆的特性,所有需要第二步的从上向下的堆化
  • 2.堆化,这里采用自上向下的堆化,沿着节点的路径,与其子节点进行比较,如果不满足父子节点关系则交换,重复这个过程,知道满足父子节点关系为止

    下图是一个大顶堆的删除过程过程

在这里插入图片描述

大顶堆的实现

    这里使用Python3简单模拟实现了一个大顶堆,小顶堆类似,也就是比较不同,这里就不重复实现了

class MaxHeap:
    def __init__(self, size: int):
        """
        堆的初始化,设置堆的大小
        :param size: 堆的大小
        """
        # 这里有个小技巧,数组下标不从0开始,而从1开始,这样父子节点的下标获取和计算方便,所有这里数组空间为size+1
        self.data = [None] * (size + 1)
        self.size = size
        self.used = 0
        print("init heap:", self.data)

    def push(self, value: int) -> None:
        """
        堆的插入操作
        :return:
        """
        if self.used == self.size:
            self.pop()

        self.used += 1
        print("insert:", self.used, self.data, end="==>")
        self.data[self.used] = value
        self._shitUp()
        print(self.data)

    def pop(self) -> None:
        """
        堆的删除操作
        :return:
        """
        if self.used == 0:
            return

        self.data[1] = self.data[self.used]
        print("pop:", self.data, end="==>")
        self.used -= 1
        self._shitDown()
        print(self.data)

    def _shitUp(self):
        """
        自下向上的堆化
        与父节点进行比较,大于则交换
        :return:
        """
        child = self.used
        while child // 2 > 0 and self.data[child] > self.data[child//2]:
            self.data[child], self.data[child//2] = self.data[child//2], self.data[child]
            child = child // 2

    def _shitDown(self):
        """
        自上向下的堆化
        这里是与最大值的子节点进行交换,则用了一个maxPos保存最大值的位置
        如果是当前父节点的位置,则堆化结束
        不是则交换父子节点,继续循环
        :return:
        """
        parent = 1
        while True:
            maxPos = parent
            if parent * 2 <= self.used and self.data[parent*2] > self.data[parent]:
                maxPos = parent * 2
            if parent * 2 + 1 <= self.used and self.data[parent*2+1] > self.data[maxPos]:
                maxPos = parent*2+1
            if maxPos == parent:
                break
            self.data[maxPos], self.data[parent] = self.data[parent], self.data[maxPos]
            parent = maxPos

    def toList(self):
        return self.data



if __name__ == "__main__":
    maxHeap = MaxHeap(2)
    maxHeap.push(1)
    maxHeap.push(2)
    maxHeap.push(3)
    print(maxHeap.toList())


init heap: [None, None, None]
insert: 1 [None, 1, None]
insert: 2 [None, 2, 1]
pop: [None, 1, 1]
insert: 2 [None, 3, 1]
[None, 3, 1]

LeetCode 347:前 K 个高频元素的使用自写堆

如何需要去跑测试的话,需要把代码里面的print打印的去掉,不然会超出输出限制。

    LeetCode里面有一个是使用堆来解的,答案里面有是使用语言实现,这里就自己实现一个小顶堆来尝试。小顶堆做的唯一修改就是加入一个字典,进行比较时进行转换再比较。大致的代码和思路在下面的代码中。

"""
347. 前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。



示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:

输入: nums = [1], k = 1
输出: [1]


提示:

你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。



解题思路:
使用小顶堆实现,因为要返回前最大K个数,小顶堆就可以保存着K个数,而堆顶是最小数,剩下的都是大于它的

1.先使用hashmap统计保存数字的出现次数
2.使用k个数据初始化小顶堆
3.比堆顶大的就插入,小于就说明前K大的数没它的份

统计N,遍历N,大顶堆操作logK,则最大时间复杂度O(N)

自己实现个堆来尝试尝试
内置的跑了60ms,自写的56,感觉差不多
"""
import collections
from typing import List
import heapq


class SolutionP:
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        count = collections.Counter(nums)
        return heapq.nlargest(k, count.keys(), key=count.get)


class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        count = {}
        for num in nums:
            count[num] = count.get(num, 0) + 1
        print(count)

        # 使用K个数初始化堆
        minHeap = MinHeap(k, count)
        keys = list(count.keys())
        print(keys)
        for i in range(0, k):
            minHeap.push(keys[i])

        # 大于堆顶才插入,小于的就说明前K个数没有它的份
        for i in range(k, len(keys)):
            if count[keys[i]] > count[minHeap.getMinest()]:
                minHeap.push(keys[i])
                
        return minHeap.toList()


class MinHeap:
    def __init__(self, size: int, myDict: dict):
        """
        堆的初始化,设置堆的大小
        :param size: 堆的大小
        """
        # 这里有个小技巧,数组下标不从0开始,而从1开始,这样父子节点的下标获取和计算方便,所有这里数组空间为size+1
        self.data = [None] * (size + 1)
        self.size = size
        self.used = 0
        self.myDict = myDict
        print("init heap:", self.data)

    def push(self, value: int) -> None:
        """
        堆的插入操作
        :return:
        """
        print("insert value:", value, end=" ")
        if self.used == self.size:
            self.pop()

        self.used += 1
        print("insert:", self.used, value, self.data, end="==>")
        self.data[self.used] = value
        self._shitUp()
        print(self.data)

    def pop(self) -> None:
        """
        堆的删除操作
        :return:
        """
        if self.used == 0:
            return

        self.data[1] = self.data[self.used]
        print("pop:", self.data, end="==>")
        self.used -= 1
        self._shitDown()
        print(self.data)

    def _shitUp(self):
        """
        自下向上的堆化
        与父节点进行比较,大于则交换
        :return:
        """
        child = self.used
        while child // 2 > 0 and self.myDict[self.data[child]] < self.myDict[self.data[child // 2]]:
            self.data[child], self.data[child // 2] = self.data[child // 2], self.data[child]
            child = child // 2

    def _shitDown(self):
        """
        自上向下的堆化
        这里是与最大值的子节点进行交换,则用了一个maxPos保存最大值的位置
        如果是当前父节点的位置,则堆化结束
        不是则交换父子节点,继续循环
        :return:
        """
        parent = 1
        while True:
            minPos = parent
            if parent * 2 <= self.used and self.myDict[self.data[parent * 2]] < self.myDict[self.data[parent]]:
                minPos = parent * 2
            if parent * 2 + 1 <= self.used and self.myDict[self.data[parent * 2 + 1]] < self.myDict[self.data[minPos]]:
                minPos = parent * 2 + 1
            if minPos == parent:
                break
            self.data[minPos], self.data[parent] = self.data[parent], self.data[minPos]
            parent = minPos

    def getMinest(self) -> int:
        """
        返回堆顶元素值
        :return:
        """
        return self.data[1]

    def toList(self):
        return self.data[1:]


if __name__ == "__main__":
    s = Solution()
    print(s.topKFrequent(nums=[1, 1, 1, 2, 2, 3], k=2))
    # [-3,-4,0,1,4,9]
    print(s.topKFrequent(nums=[6, 0, 1, 4, 9, 7, -3, 1, -4, -8, 4, -7, -3, 3, 2, -3, 9, 5, -4, 0], k=6))

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值