优先队列(堆)
优先队列的应用主要在操作系统调度时决定若干进程中运行的优先级,一般算法会使用一个队列,开始时将作业放到队尾,然后从队头开始进行处理任务。这种方式并不适合处理。所以这里我们需要使用到优先队列。
1.模型
首先,我们需要先了解优先队列的基础逻辑。优先队列主要包含两个操作
a. 插入(Insert)
b. 删除最小值(DeleteMin)
2.二叉堆
我们实现优先队列的数据结构称为堆,我们使用的为二叉堆(Binary Heap),它的名字让我很容易联想到二叉树的实现,但根据堆的性质,构成堆之一般为一颗完全二叉树,我们可以使用数组来进行实现而不是使用树结构,因为数组的操作对于堆来说可以计算较快,并且可以节省一部分指针空间。
3.通过数组实现的结构性质
- 我们通过数组实现的时候,也需要给堆添加一些逻辑限制,例如在访问节点的时候通过左右的位置去进行访问,而不是通过普通数组索引顺序访问。
- 我们访问孩子节点的规律为:
- 对于任意位置i上的元素,其左儿子在i * 2 的位置上,右儿子在 i * 2 + 1的位置上,注意:以上条件为数组起始位置为1时符合。
- 对于任意位置i上的元素,其左儿子在i * 2 的位置上,右儿子在 i * 2 + 1的位置上,注意:以上条件为数组起始位置为1时符合。
4.堆序性质
- 使操作快速执行的性质为堆序性。我们需要快速找到最小元,因此,最小元应该再根上。如果我们将任意子树视为一个堆,那么任意节点就应该小于它的所有后裔。
像这里,这两颗完全二叉树中,只有左边符合堆序,而右边并不符合,因为23的左孩子为一个小于他自身的数。
5.堆实现
- 我们已经对堆又了一些基本的了解。结下来为堆的实现
首先是堆结构的定义,我们使用python定义一个类,里面包含两个数据
size : 记录堆的大小
element: list类型,来存放堆数据
min: int类型,用来做标记,放在element的头部
class BinaryHeap:
def __init__(self):
min: int = -999
self.element: list = [min]
self.__size: int = len(self.element) - 1
然后堆判空的方法:
def IsEmpty(self) -> bool:
"""
check heap is empty
:return: bool
"""
return self.__size == 0
上滤(percolate up)
我们进行插入元素时,会将一个新的元素插入堆中(也可以创建一个空单元),然后通过上滤去寻找该数据在堆中的正确位置。
因为我们插入的数据破坏了堆序,所以需要对新插入的数据进行调整调整到他合适的位置,所以我们将与它的父亲(parent)进行比较,然后将它们的位置进行交换,然后17再向上进行比较发现已经到了正确的位置,然后上滤操作停止。
实现代码:
- 普通循环方法:
def percolateUp(self, i: int) -> None:
"""
normal edition of percolate up
:param i: position of heap
:return: None
"""
while self.element[i // 2] > self.element[i]:
self.element[i // 2], self.element[i] = self.element[i], self.element[i // 2]
i = i // 2
- 递归方法
def percolateUp2(self, i: int) -> None:
"""
recursion edition of percolate up
:param i: position of heap
:return: None
"""
if self.element[i // 2] > self.element[i]:
self.element[i // 2], self. element[i] = self. element[i], self.element[i // 2]
self.percolateUp2(i // 2)
通过上滤方法,我们可以实现堆的插入方法。明白上滤方法之后,我们进行插入只需要对堆进行一些数据的改变就可以了
- insert方法
def insert(self, val: int) -> None:
"""
insert the value to heap
:param val: element
:return: None
"""
self.element.append(val)
self.__size += 1
self.percolateUp(self.__size)
下滤(percolate down)
在说完上滤操作之后。我们再进行下滤操作。顾名思义,下滤就是将待调整节点向下进行调整为正确位置。
我们在进行DeleteMin方法时,只讲根节点删除是不符合要求的,这时产生了一个空单元,我们需要将堆中最后一个元素移入合适的位置。此时我们需要将最后一个节点放入根节点,然后进行下滤操作,就完成了我们的删除最小值的操作,并且不会破坏堆序。
- 循环下滤方法
def percolateDown(self, i: int) -> None:
"""
normal edition of percolate down
:param i: position of heap
:return: None
"""
child: int = i * 2
while child <= self.__size:
if child != self.__size and self.element[child + 1] < self.element[child]:
child += 1
if self.element[i] > self.element[child]:
self.element[i], self.element[child] = self.element[child], self.element[i]
else:
break
i = child
- 递归下滤方法
def percolateDown2(self, i: int) -> None:
"""
recursion edition of percolate down
:param i: position of heap
:return:None
"""
child: int = i * 2
if child != self.__size and self.element[child + 1] < self.element[child]:
child += 1
if self.element[i] > self.element[child]:
self.element[i], self.element[child] = self.element[child], self.element[i]
self.percolateDown2(child)
我们了解下滤方法后,删除最小值方法(DeleteMin)也就基本实现了。
代码如下:
def deleteMin(self) -> int:
"""
this function method to delete the heap peek and rebuild the heap conform to the rule
:return: root
"""
MinElement: int = self.element[0]
LastElement: int = self.element[self.__size]
self.element[1] = LastElement
self.__size -= 1
self.percolateDown(1)
return MinElement
其他堆操作
1. Decrease Key(降低关键字的值)
Decrease Key因为降低了关键字的值,所以使当前堆不在符合堆序,所以需要进行上滤来使堆重新符合条件
DecreaseKey方法的所含有参数及定义为:
def decreaseKey(self, i: int, decVal: int):
DecreaseKey中的I为堆中的位置,decVal为降值的幅度即element[I] = element[I] - decVal
- 该操作主要堆系统管理程序是有用的,可以使程序以更高的优先级运行
实现代码:
def decreaseKey(self, i: int, decVal: int):
"""
:param i: Position of need decrease key
:param decVal: decrease value of element
:return: None That's need to
use percolate up to adjust the heap, normally, it used to adjust management program of
Operating System.
"""
self.element[i] -= decVal
self.percolateUp(i)
Increase Key(增加关键字的值)
同理,与上面DecreaseKey的原理是一样的,就不再过多赘述。
- IncreaseKey方法定义
def increaseKey(self, i: int, incVal: int):
实现代码:
def increaseKey(self, i: int, incVal: int):
"""
:param i: Position of need increse key
:param incVal: increase value of element
:return: None
use percolate down to adjust the heap, normally, it used to adjust management program of
Operating System about some program using lot of CPU source.
"""
self.element[i] += incVal
self.percolateDown(i)
删除操作(Delete)
删除操作可以将堆中任意位置的节点删除。其主要的实现方法为:首先使用DecreaseKey将改位置关键字降为最小,然后通过DeleteMin进行删除。这样删除之后,不会改变堆序。
Delete方法定义:
def Delete(self, i: int) -> int:
实现代码:
def Delete(self, i: int) -> int:
"""
this function is mean to delete the element of binary heap, through the decrease key and deleteMin function to
make the value to the heap peek and delete it.
:param i: want to delete element position
:return: Delete Value
"""
decVal = self.element[i]
self.decreaseKey(i, decVal)
self.deleteMin()
return decVal
构建堆(Build Heap)
最后一种方法为构建堆,我们可以通过自己输入一串元素组成的列表传入方法中,使其按照堆序进行排列。
因为我们能够保证堆的构建能够以线性平均时间实施。一般的算法是通过将N个关键字以仁义顺序放入树中,保持结构特性,然后使用percolate down对节点进行下滤,创建一颗有堆序的树
i: int = self.__size // 2
while i > 0:
self.percolateDown(i)
i -= 1
因为在这里我使用了标记来标记列表的第一个元素为最小值,这为我们在前面进行上滤时的判断条件节省了循环中的一次判断,但如果我们数据的不符合我们当前的类型,就需要对他进行一些处理。将第一个元素标记为最小值,来符合上滤和下滤时的操作。
实现代码:
def addMinelement(self, arr: list) -> list:
"""
this function is mean to add an Element of Min element to the input list
:param arr: input element
:return: list
"""
MinVal: int = -999
arr.reverse()
arr.append(MinVal)
arr.reverse()
return arr
然后让我们来实现构建堆的方法:
def BuildHeap(self, arr: list) -> None:
"""
this function resolve input N element to build a heap
:param arr: input data
:return: None
"""
if arr[0] >= 0: # arr have sentinal won't to add one
arr = self.addMinelement(arr)
self.__size = len(arr) - 1
self.element = arr
i: int = self.__size // 2
while i > 0:
self.percolateDown(i)
i -= 1
全部代码实现:
class BinaryHeap:
def __init__(self):
min: int = -999
self.element: list = [min]
self.__size: int = len(self.element) - 1
def __len__(self):
return self.__size
def __str__(self):
return self.element[1:].__str__()
def IsEmpty(self) -> bool:
"""
check heap is empty
:return: bool
"""
return self.__size == 0
'''percolate up'''
def percolateUp(self, i: int) -> None:
"""
normal edition of percolate up
:param i: position of heap
:return: None
"""
while self.element[i // 2] > self.element[i]:
self.element[i // 2], self.element[i] = self.element[i], self.element[i // 2]
i = i // 2
def percolateUp2(self, i: int) -> None:
"""
recursion edition of percolate up
:param i: position of heap
:return: None
"""
if self.element[i // 2] > self.element[i]:
self.element[i // 2], self. element[i] = self. element[i], self.element[i // 2]
self.percolateUp2(i // 2)
'''percolate down'''
def percolateDown(self, i: int) -> None:
"""
normal edition of percolate down
:param i: position of heap
:return: None
"""
child: int = i * 2
while child <= self.__size:
if child != self.__size and self.element[child + 1] < self.element[child]:
child += 1
if self.element[i] > self.element[child]:
self.element[i], self.element[child] = self.element[child], self.element[i]
else:
break
i = child
def percolateDown2(self, i: int) -> None:
"""
recursion edition of percolate down
:param i: position of heap
:return:None
"""
child: int = i * 2
if child != self.__size and self.element[child + 1] < self.element[child]:
child += 1
if self.element[i] > self.element[child]:
self.element[i], self.element[child] = self.element[child], self.element[i]
self.percolateDown2(child)
'''insert function'''
def insert(self, val: int) -> None:
"""
insert the value to heap
:param val: element
:return: None
"""
self.element.append(val)
self.__size += 1
self.percolateUp(self.__size)
'''deleteMin function'''
def deleteMin(self) -> int:
"""
this function method to delete the heap peek and rebuild the heap conform to the rule
:return: root
"""
MinElement: int = self.element[0]
LastElement: int = self.element[self.__size]
self.element[1] = LastElement
self.__size -= 1
self.percolateDown(1)
return MinElement
'''Decrease Key'''
def decreaseKey(self, i: int, decVal: int):
"""
:param i: Position of need decrease key
:param decVal: decrease value of element
:return: None That's need to
use percolate up to adjust the heap, normally, it used to adjust management program of
Operating System.
"""
self.element[i] -= decVal
self.percolateUp(i)
'''Increase Key'''
def increaseKey(self, i: int, incVal: int):
"""
:param i: Position of need increse key
:param incVal: increase value of element
:return: None
use percolate down to adjust the heap, normally, it used to adjust management program of
Operating System about some program using lot of CPU source.
"""
self.element[i] += incVal
self.percolateDown(i)
'''Delete function'''
def Delete(self, i: int) -> int:
"""
this function is mean to delete the element of binary heap, through the decrease key and deleteMin function to
make the value to the heap peek and delete it.
:param i: want to delete element position
:return: Delete Value
"""
decVal = self.element[i]
self.decreaseKey(i, decVal)
self.deleteMin()
return decVal
'''Build Heap'''
def addMinelement(self, arr: list) -> list:
"""
this function is mean to add an Element of Min element to the input list
:param arr: input element
:return: list
"""
MinVal: int = -999
arr.reverse()
arr.append(MinVal)
arr.reverse()
return arr
def BuildHeap(self, arr: list) -> None:
"""
this function resolve input N element to build a heap
:param arr: input data
:return: None
"""
if arr[0] >= 0: # arr have sentinal won't to add one
arr = self.addMinelement(arr)
self.__size = len(arr) - 1
self.element = arr
i: int = self.__size // 2
while i > 0:
self.percolateDown(i)
i -= 1
if __name__ == '__main__':
"""
test code
"""
bh = BinaryHeap()
bh.BuildHeap([10, 20, 30, 53, 35, 25, 13, 63, 24])
print(bh)