线性表--优先队列

Posted Abro.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性表--优先队列相关的知识,希望对你有一定的参考价值。

 

目录

前言

一、优先队列简介

二、优先队列的适用场景

三、优先队列的实现方式

四、二叉堆实现的优先队列

4.1 二叉堆的定义

4.2 二叉堆的基本操作

4.3 优先队列的基本操作

4.4 手写二叉堆实现优先队列

4.5 使用heapq模块实现优先队列

总结


前言

优先队列(Priority Queue):一种特殊的队列。在优先队列中,元素被赋予优先级,当访问队列元素时,具有最高优先级的元素最先删除。


一、优先队列简介

优先队列与普通队列最大的不同之处在于出队顺序

普通队列的出队顺序跟入队顺序相关,符合先进先出的规则。

而优先队列的出队顺序跟入队顺序无关,优先队列是按照元素的优先级来决定出队顺序的。优先级高的元素优先出队,优先级低的元素后出队。优先队列符合 「最高级先出(First in, Largest out)」 的规则。

如下图所示:

    

二、优先队列的适用场景

优先队列的应用场景非常多,比如:

  • 数据压缩:赫夫曼编码算法;
  • 最短路径算法:Dijkstra 算法;
  • 最小生成树算法:Prim 算法;
  • 任务调度器:根据优先级执行系统任务;
  • 事件驱动仿真:顾客排队算法;
  • 选择问题:查找第 k 个最小元素。

许多编程语言都提供了优先队列的实现,例如:Java的priorityQueue,C++的priority_queue等。python中也可以通过heapq来实现优先队列。

三、优先队列的实现方式

优先队列所涉及的基本操作跟普通队列差不多,主要是入队操作出队操作

而优先队列的实现方式也有很多种,除了使用数组(顺序存储)实现与链表(链式存储)实现之外,最常使用的是二叉堆结构实现优先队列。以下是三种方案的介绍和总结:

  • 数组(顺序存储)实现优先队列:入队操作直接插入到数组队尾,时间复杂度为 O(1)。出队操作需要遍历整个数组,找到优先级最高的元素,返回并删除该元素,时间复杂度为 O(n)。
  • 链表(链式存储)实现优先队列:链表中的元素按照优先级排序,入队操作需要为待插入元素创建节点,并在链表中找到合适的插入位置,时间复杂度为 O(n)。出队操作直接返回链表队头元素,并删除队头元素,时间复杂度为 O(1)。
  • 二叉堆结构实现优先队列:构建一个二叉堆结构,二叉堆按照优先级进行排序。入队操作就是将元素插入到二叉堆中合适位置,时间复杂度为 O(log2n)。吹对操作则返回二叉堆中优先级最大节点并删除,时间复杂度也是 O(log2n)。

下面是三种结构实现的优先队列入队操作和出队操作的时间复杂度总结。

入队操作时间复杂度出队操作(取出优先级最高的元素)时间复杂度
数组O(n)O(1)
链表O(1)O(n)
O(log2n)O(log2n)

从上述表格可以看出,使用二叉堆来实现优先队列是比较高效的。 

四、二叉堆实现的优先队列

4.1 二叉堆的定义

二叉堆:符合以下两个条件之一的完全二叉树:

  • 大顶堆:根节点值 ≥ 子节点值。
  • 小顶堆:根节点值 ≤ 子节点值。

4.2 二叉堆的基本操作

二叉树主要涉及两个基本操作:「堆调整方法」和「将数组构建为二叉堆方法」。

  • 堆调整方法 heapAdjust:把移走了最大值元素以后的剩余元素组成的序列再构造为一个新的堆积。具体步骤如下:

    • 从根节点开始,自上而下地调整节点的位置,使其成为堆积。即把序号为 i 的节点与其左子树节点(序号为 2 * i)、右子树节点(序号为 2 * i + 1)中值最大的节点交换位置。

    • 因为交换了位置,使得当前节点的左右子树原有的堆积特性被破坏。于是,从当前节点的左右子树节点开始,自上而下继续进行类似的调整。

    • 如此下去直到整棵完全二叉树成为一个大顶堆。

  • 将数组构建为二叉堆方法(初始堆建立方法) heapify

    • 如果原始序列对应的完全二叉树(不一定是堆)的深度为 d,则从 d - 1 层最右侧分支节点(序号为 ⌊n/2⌋)开始,初始时令 i = ⌊n/2⌋,调用堆调整算法。

    • 每调用一次堆调整算法,执行一次 i = i - 1,直到 i == 1 时,再调用一次,就把原始数组构建为了一个二叉堆。

4.3 优先队列的基本操作

入队出队操作:

  • 入队操作 heappush
    • 先将待插入元素 value 插入到数组 nums 末尾。
    • 如果完全二叉树的深度为 d,则从 d - 1 层开始最右侧分支节点(序号为 ⌊n/2⌋)开始,初始时令 i = ⌊n/2⌋,从下向上依次查找插入位置。
    • 遇到 value 小于当前根节点时,将其插入到当前位置。否则继续向上寻找插入位置。
    • 如果找到插入位置或者到达根位置,将 value 插入该位置。
  • 出队操作 heappop
    • 交换数组 nums 首尾元素,此时 nums 尾部就是值最大(优先级最高)的元素,将其从 nums 中弹出,并保存起来。
    • 弹出后,对 nums 剩余元素调用堆调整算法,将其调整为大顶堆。

4.4 手写二叉堆实现优先队列

主要实现以下五种方法:

  • heapAdjust:将完全二叉树调整为二叉堆。
  • heapify: 将数组构建为二叉堆方法(初始堆建立方法)。
  • heappush:向堆中添加元素,也是优先队列的入队操作。
  • heappop:删除堆顶元素,也是优先队列的出队操作,弹出优先队列中优先级最高的元素。
  • heapSort:堆排序。

python代码:

class Heapq:
	# 堆调整方法:调整为大顶堆
	def heapAdjust(self, nums: [int], index: int, end: int):
		left = index * 2 + 1
		right = left + 1
		while left <= end:
			# 当前节点为非叶子结点
			max_index = index
			if nums[left] > nums[max_index]:
				max_index = left
			if right <= end and nums[right] > nums[max_index]:
				max_index = right
			if index == max_index:
				# 如果不用交换,则说明已经交换结束
				break
			nums[index], nums[max_index] = nums[max_index], nums[index]
			# 继续调整子树
			index = max_index
			left = index * 2 + 1
			right = left + 1
	
	# 将数组构建为二叉堆
	def heapify(self, nums: [int]):
		size = len(nums)
		# (size - 2) // 2 是最后一个非叶节点,叶节点不用调整
		for i in range((size - 2) // 2, -1, -1):
			# 调用调整堆函数
			self.heapAdjust(nums, i, size - 1)
	
	# 入队操作
	def heappush(self, nums: list, value):
		nums.append(value)
		size = len(nums)
		i = size - 1
		# 寻找插入位置
		while (i - 1) // 2 >= 0:
			cur_root = (i - 1) // 2
			# value 小于当前根节点,则插入到当前位置
			if nums[cur_root] > value:
				break
			# 继续向上查找
			nums[i] = nums[cur_root]
			i = cur_root
		# 找到插入位置或者到达根位置,将其插入
		nums[i] = value
				
	# 出队操作
	def heappop(self, nums: list) -> int:
		size = len(nums)
		nums[0], nums[-1] = nums[-1], nums[0]
		# 得到最大值(堆顶元素)然后调整堆
		top = nums.pop()
		if size > 0:
			self.heapAdjust(nums, 0, size - 2)
			
		return top
	
	# 升序堆排序
	def heapSort(self, nums: [int]):
		self.heapify(nums)
		size = len(nums)
		for i in range(size):
			nums[0], nums[size - i - 1] = nums[size - i - 1], nums[0]
			self.heapAdjust(nums, 0, size - i - 2)
		return nums

4.5 使用heapq模块实现优先队列

Python 中的 heapq 模块提供了优先队列算法。函数 heapq.heappush() 用于在队列 queue 上插入一个元素。heapq.heappop() 用于在队列 queue 上删除一个元素。

注意heapq.heappop() 函数总是返回「最小的」的元素。所以我们在使 heapq.heappush() 时,将优先级设置为负数,这样就使得元素可以按照优先级从高到低排序, 这个跟普通的按优先级从低到高排序的堆排序恰巧相反。这样做的目的是为了 heapq.heappop() 每次弹出的元素都是优先级最高的元素。

python代码:

import heapq

class PriorityQueue:
    def __init__(self):
        self.queue = []
        self.index = 0

    def push(self, item, priority):
        heapq.heappush(self.queue, (-priority, self.index, item))
        self.index += 1

    def pop(self):
        return heapq.heappop(self.queue)[-1]


总结

可以应用优先队列来刷一下力扣的几道题!

347.前K个高频元素

239.滑动窗口最大值

以上是关于线性表--优先队列的主要内容,如果未能解决你的问题,请参考以下文章

线性表--优先队列

线性表栈队列和优先队列

数据结构,如何根据邻接表画深度,广度优先生成树?

salesforce零基础学习(七十八)线性表链形结构简单实现

Python数据结构-队列与广度优先搜索(Queue)

线性表