堆——优先队列

1.数组实现堆操作

例1:实现大堆操作

'''
0629 实现大堆的操作
堆用数组表示,一颗完全二叉树
i的父节点是par=(i-1)/2
i的左子节点left=2*i+1
i的右子节点right=2*i+2
数组下标从0开始

大堆的特点是父节点是最大的
下沉操作:新元素比子节点都要小,下沉帮助找到正确位置(贪心原则,子节点大就要上移)
上浮操作:新元素比父节点大,其他都满足大堆
'''


class Heap(object):
    a = []
    n = 0

    def _sink(self, i):
        # 把新元素拿出来
        t = self.a[i]
        # 有左子节点
        while i + i + 1 < self.n:
            # j指向左子节点
            j = i + i + 1
            # 判断是否有右子节点,如果有且大,那么j指向右子节点
            if j < self.n - 1 and self.a[j] < self.a[j + 1]:
                # 左边小于右边,j设为右子节点下标
                j += 1
            # 如果子节点比t大,t的位置还要下移
            if self.a[j] > t:
                # 大于当前,节点上移
                self.a[i] = self.a[j]
                i = j  # j成为新的空位,再查看新空位的左右子节点
            else:
                # 找到了t的位置,t大于所有子节点
                break
        # 把新元素放入正确的位置
        self.a[i] = t

    def _swim(self, i):
        t = self.a[i]
        # 父节点
        while i > 0:
            par = (i - 1) / 2
            # 如果父节点比新元素小,父节点下沉
            if self.a[par] < t:
                self.a[i] = self.a[par]
                i = par
            else:
                break
        self.a[i] = t

    def push(self, val):
        # 在堆的尾巴上添加新元素
        self.a.append(val)
        # 新元素进行上浮
        self._swim(self.n)
        self.n += 1

    def pop(self):
        # 取出a[0]作为返回值
        ret = self.a[0]
        # 将a[n-1]放到a[0]中
        self.a[0] = self.a[self.n - 1]

        self.a.pop()
        self.n -= 1
        # a[0]进行下沉
        self._sink(0)
        return ret

    def size(self):
        return self.n

2.优先队列实现前k个例题

例2:数组最小的k个元素

'''
0620 数组最小的k个元素
用自己实现的堆
o(nlogk)
o(k)
'''


class Solution:
    a = []
    n = 0

    # 下沉操作
    def _sink(self, i):
        # 把新元素拿出来
        t = self.a[i]
        # 有左子节点
        while i + i + 1 < self.n:
            # j指向左子节点
            j = i + i + 1
            # 判断是否有右子节点,如果有且大,那么j指向右子节点
            if j < self.n - 1 and self.a[j] < self.a[j + 1]:
                # 左边小于右边,j设为右子节点下标
                j += 1
            # 如果子节点比t大,t的位置还要下移
            if self.a[j] > t:
                # 大于当前,节点上移
                self.a[i] = self.a[j]
                i = j  # j成为新的空位,再查看新空位的左右子节点
            else:
                # 找到了t的位置,t大于所有子节点
                break
        # 把新元素放入正确的位置
        self.a[i] = t

    # 上浮操作
    def _swim(self, i):
        t = self.a[i]
        # 父节点
        while i > 0:
            par = int((i - 1) / 2)
            # 如果父节点比新元素小,父节点下沉
            if self.a[par] < t:
                self.a[i] = self.a[par]
                i = par
            else:
                break
        self.a[i] = t

    def push(self, val):
        # 在堆的尾巴上添加新元素
        self.a.append(val)
        # 新元素进行上浮
        self._swim(self.n)
        self.n += 1

    def pop(self):
        # 取出a[0]作为返回值
        ret = self.a[0]
        # 将a[n-1]放到a[0]中
        self.a[0] = self.a[self.n - 1]

        self.a.pop()
        self.n -= 1
        # a[0]进行下沉
        self._sink(0)
        return ret

    def size(self):
        return self.n

    def getLeastNumbers(self, arr, k):
        if k <= 0 or (not arr) or len(arr) == 0:
            return []

        self.a = []
        self.n = 0

        for i in arr:
            # 加入集合
            self.push(i)
            while self.size() > k:
                # 有k+1个元素时,弹出最大元素
                self.pop()
        return self.a


if __name__ == '__main__':
    arr = [4, 5, 1, 6, 2, 7, 3, 8]
    k = 4
    s = Solution()
    print(s.getLeastNumbers(arr, k))

例2-1:内置优先队列实现

'''
0620 数组最小的k个元素
内置的优先级队列

单调队列和优先级队列
单调队列:进队的时候可能会将队列中的元素清空,始终保持单调性,出队的时候,对比,相等就出队,不等时候早就被出队了
push时始终保持单调性,pop时和队首比较
优先级队列:进队的时候找一个合适的位置坐下,等着,出队的时候,始终都是最大的先走
支持push功能,不需要有序。pop时,总是把最大的元素扔出去
'''
import queue
import heapq


class Solution(object):

    def getLeastNumbers(self, arr, k):
        if k <= 0 or (not arr) or len(arr) == 0:
            return []
        # 内置优先队列实现
        Q = queue.PriorityQueue()  # 优先级队列,小堆,需要将元素取反放入
        for i in arr:
            Q.put(-i)
            while Q.qsize() > k:
                Q.get()
        res = []
        while not Q.empty():
            res.append(-Q.get())

        return res


'''
1.使用内置堆进行操作
        heapq.heapify(arr)
        return [heapq.heappop(arr) for _ in range(k)]
'''

if __name__ == '__main__':
    arr = [4, 5, 1, 6, 2, 7, 3, 8]
    k = 4
    s = Solution()
    print(s.getLeastNumbers(arr, k))

例2-2:前K个高频元素

'''
0620 前K个高频元素
任意顺序返回,如果一样取较小的
k大小的堆
高频元素就是把小的删掉,小堆
堆中存放元素和频率,以频率进行优先级设置
返回顺序任意
'''

import queue
import collections

import heapq


class Solution:
    def topKFrequent(self, nums, k):

        # 直接获取频率统计字典
        dic = collections.Counter(nums)
        # 内置优先队列实现
        Q = queue.PriorityQueue()

        res = []
        for cnt, val in dic.items():
            Q.put((val, cnt))
            while Q.qsize() > k:
                Q.get()
        while not Q.empty():
            res.append(Q.get()[1])
        return res


'''
1.内置堆实现  
        dic = collections.Counter(nums)
        heap = [(-freq, value) for value, freq in dic.items()] # 内置的堆是大堆
        heapq.heapify(heap)
        return [heapq.heappop(heap)[1] for _ in range(k)]
'''

if __name__ == '__main__':
    nums = [1, 2, 1, 2, 1, 3]
    k = 2
    s = Solution()
    print(s.topKFrequent(nums, k))

例2-3:前k个高频单词

'''
0621 前K个高频单词
返回顺序是频率由高到低,相同频率按照字典顺序排序
'''
import heapq


class Solution:
    def topKFrequent(self, words, k):
        # 字典统计数组中元素出现的个数
        dic = {}
        for x in words:
            old = dic.get(x, 0)
            dic[x] = old + 1

        heap = [(-v, k) for k, v in dic.items()]
        heapq.heapify(heap)

        ans = sorted(heap)
        return [heapq.heappop(ans)[1] for _ in range(k)]


'''
1.直接对字典进行排序
        ans = sorted(dic.items(), key=lambda x: (-x[1], x[0]))
        return [i for i, j in ans[:k]]
'''

if __name__ == '__main__':
    words = ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"]
    k = 4
    s = Solution()
    print(s.topKFrequent(words, k))

3.优先队列困难题目

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值