数据结构算法——优先队列

一、概述

该博客结合leetcode原题介绍了“堆”的建立和操作,以及其常见题目。

主要要了解的堆的实现有两种:二叉树堆和Fibonacci堆(前者效率最低,后者效率最高)
在这里插入图片描述

1.1 二叉树堆

1.1.1 堆的定义

(1)首先,堆一定是一个完全二叉树
(2)最大堆:每个节点都比其子树所有节点大
(3)最小堆:每个节点都比其子树所有节点小

1.1.2 堆的插入

如下图,当要对堆插入元素时,将从完全二叉树的最后一个位置进行插入,即6号位置。
在这里插入图片描述
如下图,当插入58这个元素后,破坏了之前堆的有序性。
在这里插入图片描述
则将该元素与父节点进行替换,还不平衡的话再重复此操作,直至该元素小于父节点,此时,也一定恢复了堆的有序性。
在这里插入图片描述在这里插入图片描述在这里插入图片描述
由于堆的深度是logN,N是元素个数,所以插入的最坏情况时间复杂度是O(logN)。

1.1.3 堆的删除

如下图,对堆的删除操作只能在堆顶进行。可以想想是为什么,因为堆只满足自上而下的顺序,自下而上是没有顺序规律的。因此对下图的堆,我们的删除操作只能删除值为58的元素。
在这里插入图片描述
删除元素之后,将堆中最后一个元素,31放到堆顶(根)。此时发现堆顶元素不满足比所有子节点大的有序性。
在这里插入图片描述
因此我们将其和子节点交换位置,交换后如果依然比子节点大,则重复前一步,如下图所示。
在这里插入图片描述在这里插入图片描述在这里插入图片描述

1.1.4 堆的建立

堆的建立时间复杂度O(N)(具体证明方法参考【1】【2】)。方法是按照下图中的数字顺序,依次将对应圆圈中的二叉树通过和堆的插入一样的方法变成一个个小堆,最后整个二叉树都变成堆。
在这里插入图片描述

1.2 Fibonacci堆

二、例题

2.1 第K大的元素

#leetcode 703 第K大的元素
(1)暴力解法
*时间复杂度O(NlogN)
*空间复杂度O(K)

class KthLargest(object):
    def __init__(self, k, nums):
        """
        :type k: int
        :type nums: List[int]
        """
        self.nums = sorted(nums, reverse=True)
        self.sort_k = self.nums[:k]
        self.k = k

    def add(self, val):
        """
        :type val: int
        :rtype: int
        """
        self.sort_k = sorted(self.sort_k + [val], reverse=True)[:self.k]
        return self.sort_k[-1]

(2)初步优化
*时间复杂度O(NKlogK)
*空间复杂度O(K)

class KthLargest(object):
    def __init__(self, k, nums):
        """
        :type k: int
        :type nums: List[int]
        """
        self.k = k
        self.sort_k = []
        for val in nums:
            self.add(val)

    def add(self, val):
        """
        :type val: int
        :rtype: int
        """
        self.sort_k = sorted(self.sort_k + [val], reverse=True)[:self.k]
        return self.sort_k[-1]

(3)使用优先队列
*时间复杂度O(NlogK)
*空间复杂度O(K)

import heapq

class KthLargest(object):
    def __init__(self, k, nums):
        """
        :type k: int
        :type nums: List[int]
        """
        self.heap = []  # 创建小顶堆(根是最小值)
        self.k = k
        self.nums = nums
        for num in nums:
            self.add(num)

    def add(self, val):
        """
        :type val: int
        :rtype: int
        """
        if len(self.heap) < self.k:
            heapq.heappush(self.heap, val)
        else:
            if val > self.heap[0]:
                heapq.heapreplace(self.heap, val)  # 替换堆顶元素,并重新排序
        return self.heap[0]

2.2 前K个高频元素

#Leetcode 347 前K个高频元素

(1)暴力解法
*时间复杂度O(NlogN)
*空间复杂度O(N)

class Solution:
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        freq_dict = {}
        for v in nums:
            freq_dict[v] = freq_dict.get(v, 0) + 1
        res = freq_dict.items()
        res.sort(key=lambda x:x[1], reverse=True)
        return [res[i][0] for i in range(k)]

(2)使用优先队列
*时间复杂度O(N)
*空间复杂度O(N)

class Solution:
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        freq_dict = {}  # 创建计数的存储空间
        for v in nums:
            freq_dict[v] = freq_dict.get(v, 0) + 1
        heap = []  # 创建堆
        for key, value in freq_dict.items():
            if len(heap)<k:  # 当堆还没满时,插入元素
                heapq.heappush(heap, (value, key))
                continue
            if value>heap[0][0]:  # 堆满时,若当前元素大于堆顶,则删除堆顶并插入
                heapq.heapreplace(heap, (value, key))

        res = []
        while heap:
            res.insert(0, heapq.heappop(heap)[1])
        return res

2.3 返回滑动窗口中的最大值

#leetcode 239 返回滑动窗口中的最大值
1、利用优先队列
*时间复杂度:O(NlogN)
简述:维护一个大顶堆,滑动时做两个操作:顶是否过时(若是则删除)、插入新元素

    def maxSlidingWindow(self, nums, k):
        ret = []
        # 初始化heap
        heap = []
        for idx, v in enumerate(nums[:k]):
            heapq.heappush(heap, (-v, idx))
        ret.append(-heapq.nsmallest(1, heap)[0][0])
        # 开始滑动窗口
        for idx, v in enumerate(nums[k:]):
            idx_pop = idx
            idx_insert = idx + k
            while len(heap)>0 and heapq.nsmallest(1, heap)[0][1] <= idx_pop:  # 先保证堆顶元素的坐标在滑动窗口内
                heapq.heappop(heap)
            heapq.heappush(heap, (-v, idx_insert))
            ret.append(-heapq.nsmallest(1, heap)[0][0])
        return ret

2、仅利用队列
*时间复杂度:O(N)

    def maxSlidingWindow(self, nums, k):
        ret, window = [], []
        for i, v in enumerate(nums):
            if i>=k and window[0]<=i-k:  # 需要尾部删除
                window.pop(0)
            while window and nums[window[-1]]<=v:  # 是否需要删除前朝老臣保证递减
                window.pop()
            window.append(i)
            if i>=k-1 and window:  # 需要输出结果
                ret.append(nums[window[0]])
        return ret

参考:
【1】堆和二叉查找树的建立的时间复杂度
【2】构建二叉堆时间复杂度的证明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值