最小索引优先队列(Min index priority queue)
在之前实现的最大优先队列和最小优先队列,他们可以分别快速访问到队列中最大元索和最小元素,但是他们有一 个缺点,就是没有办法通过索引访问已存在于优先队列中的对象,并更新它们。
为了实现这个目的,在优先队列的基础上,学习一种新的数据结构,索引优先队列。
接下来我们以最小索引优先队列举列,最大优先索引队列,有兴趣可以自行实现。
实现思路
实现功能的三个重要数组
- 数组items,储存无序插入的元素的数组
- 数组pq,假设将items储存的元素进行从小到大排序,并且items对应的原索引依旧保持不变,而pq储存的元素就是items排序过后元素的原索引(参照下图可更好地理解)
- 数组qp,qp的元素对应pq中的索引, qp的索引对应pq中的元素(也就对应着未排序的items中的元素,与items元素的索引保持一致)
这样删除时只需要根据传入的索引在qp中寻找对应的元素就可以跟快地找到了
实现的操作方法
- size()获取队列的大小
- is_empty()判断队列是否为空
- less(x, y)对传入的两个索引对应当前队列的元素进行大小比较
- swap(i, j)对传入的两个索引对应当前队列中的元素进行值交换
- min_elem_index()获取最小元素的索引
- is_index_exist()判断索引在当前队列中是否对应一个元素
- insert(index, item)指定索引index处插入一个元素item
- delete_min_elem()删除最小元素,并返回最小元素插入队列时的索引
- change_item(idx, itm)指定索引idx处,将该处元素替换为itm
- swim()上浮排序操作,同之前堆的排序中介绍
- sink()下沉排序操作,同之前堆的排序中介绍
Python代码实现
import operator
class IndexMinPriorityQueue:
def __init__(self, length):
self.items = [None for _ in range(length)]
# Ascendingly sort items and memorize the item's relative index in items
self.pq = [None] + [i if self.items[i] else None for i in range(len(self.items))]
# Its index is associate with elements in pq, and also syncs with indices of list items
self.qp = [i if self.pq[i] else None for i in range(len(self.pq))]
self.N = 0
def size(self):
return self.N
def is_empty(self):
return self.N == 0
def less(self, i, j):
"""Compare the given two items in self.items"""
return operator.lt(self.items[self.pq[i]], self.items[self.pq[j]])
def swap(self, i, j):
"""But change the position of the two items in the subsidiary pq and qp lists"""
self.pq[i], self.pq[j] = self.pq[j], self.pq[i]
self.qp[self.pq[i]], self.qp[self.pq[j]] = i, j
def min_elem_index(self):
"""Find the minimum element's index"""
return self.pq[1]
def is_index_exist(self, index):
"""Judge if the given index is exist in this queue"""
return self.qp[index] is not None
def insert(self, index, item):
"""Insert an element associated with the element's index in this queue"""
if self.is_index_exist(index):
return
self.items[index] = item
self.N += 1
# Now it isn't a orderly queue
self.pq[self.N] = index
self.qp[index] = self.N
# swim the last element to make list pq ordered
self.swim(self.N)
def delete_min_elem(self):
"""Delete the minimum element, and return its index"""
min_index = self.pq[1]
# print(f"min_ele: {self.items[min_index]}")
self.swap(1, self.N)
self.pq[self.N] = None
self.qp[min_index] = None
self.items[min_index] = None
self.N -= 1
self.sink(1)
return min_index
def change_item(self, idx, itm):
"""Substitute a item which index=idx with a new item which value=itm"""
self.items[idx] = itm
k = self.qp[idx]
self.sink(k)
self.swim(k)
def swim(self, index):
"""Move the smaller element up; We should only change order in pq and qp"""
while index > 1:
# Compare the current node with its parent node, if smaller, swim up
if self.less(index, int(index/2)): # Compare values in items
self.swap(index, int(index/2)) # But swap the mapping position in pq and qp
index = int(index/2)
def sink(self, index):
"""Move the bigger element down; We should only change order in pq and qp"""
# print(f"SINK: idx:{index} N:{self.N}")
while 2*index <= self.N:
index_smaller = 2*index if 2*index+1 > self.N else \
(2*index if self.less(2*index, 2*index+1) else 2*index+1)
# print(f"index_smaller: {index_smaller}")
# print(f"index: {index}")
if self.less(index, index_smaller):
break
self.swap(index, index_smaller)
index = index_smaller
测试代码
if __name__ == '__main__':
IMPQ = IndexMinPriorityQueue(10)
IMPQ.insert(0, 'C')
IMPQ.insert(1, 'E')
IMPQ.insert(2, 'A')
print(f"After three insert list items now is: {IMPQ.items}")
# print(f"pq: {IMPQ.pq}")
# print(f"qp: {IMPQ.qp}")
# print(f"min_elem_index: {IMPQ.min_elem_index()}")
index, item = 0, 'B'
IMPQ.change_item(0, 'B')
print(f"Changed the item in index[{index}] to {item}, items now is {IMPQ.items}")
index, item = 0, 'V'
IMPQ.change_item(0, 'V')
print(f"Changed the item in index[{index}] to {item}, items now is {IMPQ.items}")
while not IMPQ.is_empty():
res = IMPQ.delete_min_elem()
print(f"Pop the minimum element: {res}")
print(f"After delete all elements , its size: {IMPQ.size()}")
print(IMPQ.is_index_exist(0))
测试结果
After three insert list items now is: ['C', 'E', 'A', None, None, None, None, None, None, None]
Changed the item in index[0] to B, items now is ['B', 'E', 'A', None, None, None, None, None, None, None]
Changed the item in index[0] to V, items now is ['V', 'E', 'A', None, None, None, None, None, None, None]
Pop the minimum element: 2
Pop the minimum element: 1
Pop the minimum element: 0
After delete all elements , its size: 0
False
这里为了测试结果直观,直接将数组items拿出来了,注意items中的元素是不会改变位置的,实际改变的是用来定位items中元素的pq和qp数组