SortedPriorityQueue
和UnsortedPriorityQueue
;HeapPriorityQueue
。本文您将看到,有趣的是:如果使用优先级队列实现排序,则根据具体实现类是UnsortedPriorityQueue
、SortedPriorityQueue
、还是HeapPriorityQueue
,则该排序实现将分别天然地是选择排序、插入排序以及堆排序。
对于包含的对象元素均可比较(即对象实现了__lt__()
、__gt__()
等方法中的至少一个,用于重载<
、>
等运算符)的集合collection2sort
,下面提供可将集合collection2sort
中的元素按非降序(非严格单调递增)排列后得到的序列。
下面给出的排序实现十分简单:
remove_min()
方法以非降序的方式获取每个元素。from positional_list import PositionalList
from priority_queue import UnsortedPriorityQueue, SortedPriorityQueue
from heap_priority_queue import HeapPriorityQueue
def priority_queue_sort(collection2sort: PositionalList, sort_algorithm):
"""
使用优先级队列实现选择排序或插入排序
:param collection2sort: 待排序集合
:param sort_algorithm: 指定使用选择排序或插入排序,仅可以字符串形式指定为'selection'或'insertion'
:return: None
"""
length = len(collection2sort)
if sort_algorithm == 'selection':
queue = UnsortedPriorityQueue()
elif sort_algorithm == 'insertion':
queue = SortedPriorityQueue()
elif sort_algorithm == 'heapsort':
queue = HeapPriorityQueue()
else:
raise ValueError('请指定正确的排序算法!')
for _ in range(length): # 排序第一阶段:添加待排序集合的元素至优先级队列
element = collection2sort.delete(collection2sort.first())
queue.add(element, element) # 将待排序集合collection2sort中的每个元素既当做键也当做值添加至优先级队列
for _ in range(length): # 排序第二阶段:利用优先级队列性质对集合进行排序
key, value = queue.remove_min() # 依次获得优先级队列中剩下的最小元素
collection2sort.add_last(value)
上述实现中,需要注意的是:
collection2sort
是一个位置列表,其典型特征是对任意给定位置进行增(如:add_last()
)、删(delete()
)操作的最坏时间复杂度都是 O ( 1 ) O(1) O(1);add(k, v)
方法时将待排序集合的元素既当作键也当做值。对于上述实现的priority_queue_sort()
,如果使用时指定sort_algorithm
参数为'selection'
,则queue
引用UnsortedPriorityQueue
的实例对象,由【数据结构Python描述】优先级队列简介及Python手工实现中的分析可知:
由上述分析可知,此时priority_queue_sort()
的效率瓶颈在于重复选择优先级队列中键最小的记录。因此,这时priority_queue_sort()
所实现的算法通常被称为选择排序。
对于选择排序的最坏时间复杂度,瓶颈在于第二阶段重复调用remove_min()
方法,由于优先级队列的长度在每次调用remove_min()
后减1直到为0,因此循环的第一次迭代的最坏时间复杂度为 ( n ) (n) (n),第二次为 O ( n − 1 ) O(n-1) O(n−1)等等,因此循环的最坏总时间复杂度为:
O ( n + ( n − 1 ) + ⋅ ⋅ ⋅ + 2 + 1 ) = O ( ∑ i = 1 n i ) = O ( n ( n + 1 ) / 2 ) O(n+(n-1)+\cdot\cdot\cdot+2+1)=O(\sum\nolimits_{i=1}^{n}{i})=O(n(n+1)/2) O(n+(n−1)+⋅⋅⋅+2+1)=O(∑i=1ni)=O(n(n+1)/2)
因此,第二阶段的最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2),则算法整体的最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
关于选择排序的应用,下表显示了当待排序集合为(7, 4, 8, 2, 5, 3)
时,完成排序的每一个步骤及结果:
待排序集合 | 优先级队列 | ||
输入 | (7, 4, 8, 2, 5, 3) | ( ) | |
第一阶段 | (a) | (4, 8, 2, 5, 3) | (7) |
(b) | (8, 2, 5, 3) | (7, 4) | |
(c) | (2, 5, 3) | (7, 4, 8) | |
(d) | (5, 3) | (7, 4, 8, 2) | |
(e) | (3) | (7, 4, 8, 2, 5) | |
(f) | ( ) | (7, 4, 8, 2, 5, 3) | |
第二阶段 | (a) | (2) | (7, 4, 8, 5, 3) |
(b) | (2, 3) | (7, 4, 8, 5) | |
(c) | (2, 3, 4) | (7, 8, 5) | |
(d) | (2, 3, 4, 5) | (7, 8) | |
(e) | (2, 3, 4, 5, 7) | (8) | |
(f) | (2, 3, 4, 5, 7, 8) | ( ) |
对于上述实现的priority_queue_sort()
,如果使用时指定sort_algorithm
参数为'insertion'
,则queue
引用SortedPriorityQueue
的实例对象,由【数据结构Python描述】优先级队列简介及Python手工实现中的分析可知:
add(k, v)
方法前优先级队列的长度呈正比;实际上,因为SortedPriorityQueue
中add(k, v)
方法的实现与插入排序的实现基本一致,因此此时priority_queue_sort()
通常被称为插入排序。
由上述分析可知,当使用的优先级队列为SortedPriorityQueue
,排序算法priority_queue_sort()
的效率瓶颈在第一阶段每次调用add(k, v)
。因此,类似地,第一阶段总最坏时间复杂度为:
O ( 1 + 2 + ⋅ ⋅ ⋅ + ( n − 1 ) + n ) = O ( ∑ i = 1 n i ) = O ( n ( n + 1 ) / 2 ) O(1+2+\cdot\cdot\cdot+(n-1)+n)=O(\sum\nolimits_{i=1}^{n}{i})=O(n(n+1)/2) O(1+2+⋅⋅⋅+(n−1)+n)=O(∑i=1ni)=O(n(n+1)/2)
因此,算法整体的最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2),然而需要注意的是,不同于选择排序,插入排序最好时间复杂度为 O ( n ) O(n) O(n)(此时待排序集合基本为非降序排列),但是选择排序因为总是要遍历所有剩下的未排序元素,因此其时间复杂度总是 O ( n 2 ) O(n^2) O(n2)。
关于插入排序的应用,下表显示了当待排序集合为(7, 4, 8, 2, 5, 3)
时,完成排序的每一个步骤及结果:
待排序集合 | 优先级队列 | ||
输入 | (7, 4, 8, 2, 5, 3) | ( ) | |
第一阶段 | (a) | (4, 8, 2, 5, 3) | (7) |
(b) | (8, 2, 5, 3) | (4, 7) | |
(c) | (2, 5, 3) | (4, 7, 8) | |
(d) | (5, 3) | (2, 4, 7, 8) | |
(e) | (3) | (2, 4, 5, 7, 8) | |
(f) | ( ) | (2, 3, 4, 5, 7, 8) | |
第二阶段 | (a) | (2) | (3, 4, 5, 7, 8) |
(b) | (2, 3) | (4, 5, 7, 8) | |
(c) | (2, 3, 4) | (5, 7, 8) | |
(d) | (2, 3, 4, 5) | (7, 8) | |
(e) | (2, 3, 4, 5, 7) | (8) | |
(f) | (2, 3, 4, 5, 7, 8) | ( ) |
如针对基于堆实现的优先级队列各方法的复杂度分析,使用二叉堆来实现优先级队列,最大优点是其ADT方法的最坏时间复杂度均为 l o g ( n ) log(n) log(n)或更高效。
因此基于二叉堆实现的优先级队列如HeapPriorityQueue
非常适合对优先级队列所有方法效率要求较高的场合,如上述分两个阶段(第一阶段依赖优先级队列的add(k, v)
方法,第二阶段依赖于remove_min()
方法)的priority_queue_sort
算法。
若使用HeapPriorityQueue
来实现priority_queue_sort
,则:
add(k, v)
后优先级队列中已有 i i i条记录,所以第 i i i次调用add(k, v)
操作的最坏时间复杂度为 l o g ( i ) log(i) log(i),因此该阶段总的最坏时间复杂度为:remove_min()
后优先级队列中还有 n − j + 1 n-j+1 n−j+1条记录,所以第 j j j次调用remove_mn()
操作时的最坏时间复杂度为 l o g ( n − j + 1 ) log(n-j+1) log(n−j+1),因此该阶段总的最坏时间复杂度为:下面是给定相同的待排序集合后,分别测试选择排序、插入排序和堆排序的测试代码和结果:
from positional_list import PositionalList
from priority_queue import UnsortedPriorityQueue, SortedPriorityQueue
from heap_priority_queue import HeapPriorityQueue
from random import randint
from copy import deepcopy
from timeit import timeit
def priority_queue_sort(collection2sort: PositionalList, sort_algorithm):
"""
使用优先级队列实现选择排序或插入排序
:param collection2sort: 待排序集合
:param sort_algorithm: 指定使用选择排序或插入排序,仅可以字符串形式指定为'selection'或'insertion'
:return: None
"""
length = len(collection2sort)
if sort_algorithm == 'selection':
queue = UnsortedPriorityQueue()
elif sort_algorithm == 'insertion':
queue = SortedPriorityQueue()
elif sort_algorithm == 'heapsort':
queue = HeapPriorityQueue()
else:
raise ValueError('请指定正确的排序算法!')
for _ in range(length): # 排序第一阶段:添加待排序集合的元素至优先级队列
element = collection2sort.delete(collection2sort.first())
queue.add(element, element) # 将待排序集合collection2sort中的每个元素既当做键也当做值添加至优先级队列
for _ in range(length): # 排序第二阶段:利用优先级队列性质对集合进行排序
key, value = queue.remove_min() # 依次获得优先级队列中剩下的最小元素
collection2sort.add_last(value)
collection2sort1 = PositionalList()
for i in range(10):
collection2sort1.add_last(randint(0, 10))
print('待排序集合collection2sort1 = ', collection2sort1) # 待排序集合collection2sort1 = [2, 10, 2, 3, 8, 0, 9, 6, 2, 9]
# 将待排序集合进行深拷贝,确保执行三种排序的输入是一致的
collection2sort2 = deepcopy(collection2sort1)
collection2sort3 = deepcopy(collection2sort1)
sort_algorithm1 = 'selection'
sort_algorithm2 = 'insertion'
sort_algorithm3 = 'heapsort'
def main():
# 测试选择排序执行
print('排序前collection2sort1 = ', collection2sort1) # 排序前collection2sort1 = [2, 10, 2, 3, 8, 0, 9, 6, 2, 9]
print('选择排序所用时间为:', timeit(stmt='priority_queue_sort(collection2sort1, sort_algorithm1)',
setup='from __main__ import priority_queue_sort,'
'collection2sort1,'
'sort_algorithm1',
number=10000)) # 选择排序所用时间为: 1.2219319
print('排序后collection2sort1 = ', collection2sort1) # 排序后collection2sort1 = [0, 2, 2, 2, 3, 6, 8, 9, 9, 10]
# 测试插入排序执行
print('排序前collection2sort2 = ', collection2sort2) # 排序前collection2sort2 = [2, 10, 2, 3, 8, 0, 9, 6, 2, 9]
print('插入排序所用时间为:', timeit(stmt='priority_queue_sort(collection2sort2, sort_algorithm2)',
setup='from __main__ import priority_queue_sort,'
'collection2sort2,'
'sort_algorithm2',
number=10000)) # 插入排序所用时间为: 0.7265602999999998
print('排序后collection2sort2 = ', collection2sort2) # 排序后collection2sort2 = [0, 2, 2, 2, 3, 6, 8, 9, 9, 10]
# 测试堆排序执行
print('排序前collection2sort3 = ', collection2sort3) # 排序前collection2sort3 = [2, 10, 2, 3, 8, 0, 9, 6, 2, 9]
print('堆排序所用时间为:', timeit(stmt='priority_queue_sort(collection2sort3, sort_algorithm3)',
setup='from __main__ import priority_queue_sort,'
'collection2sort3,'
'sort_algorithm3',
number=10000)) # 堆排序所用时间为: 0.6814350999999998
print('排序后collection2sort3 = ', collection2sort3) # 排序后collection2sort3 = [0, 2, 2, 2, 3, 6, 8, 9, 9, 10]
if __name__ == '__main__':
main()