堆排序与选择排序相似,它们都是找到最大元素并将其放在末尾的排序算法。堆排序是一种基于二叉堆数据结构的比较排序算法,是高效排序算法的最佳示例。

什么是堆排序?

堆排序是一种高效且流行的排序算法。堆排序的概念是逐个从列表的堆部分“消除”元素并将它们插入到已排序的部分。在深入了解堆排序算法之前,让我们讨论一下堆数据结构。

它是一种原地算法,这意味着用于存储排序后的列表的内存量是固定的,或者内存大小不依赖于初始列表的大小。

例如 - 我们不需要额外的内存堆栈来存储已排序的数组,也不需要递归调用堆栈。堆排序算法通常使用第二个数组来对固定值进行排序。这个过程快速、简单、自然且易于实现。

另一方面,堆排序是不稳定的,这意味着它不会保持具有相等值的元素的比较顺序。它可以快速排序原始类型,如整数和字符,但在处理复杂类型和对象时存在问题。

让我们通过以下示例来理解 -

我们有一个自定义类Student,具有agename属性,以及数组中该类的多个对象,包括一个名为"Thomas"、年龄为20的学生,还有"Peter",也是20岁,并按照相同的顺序出现。

如果我们按年龄对人的数组进行排序,那么不能保证"Thomas"会在排序后的数组中出现在"Peter"之前。它可以被定义的顺序,但没有保证。

堆数据结构

堆数据结构是满足堆属性的完全二叉树。它也被称为二叉堆。

完全二叉树满足以下属性。

  • 每个级别都应该填满。
  • 所有节点尽量在最左边。

131.png

如上图所示,堆的图像中,但它没有排序。在本文中,我们不会深入研究堆,因为我们的重点是解释堆排序算法,而不是堆。在堆排序中,下一个最小的元素总是第一个元素。

堆树可以是两种类型 - 最小堆和最大堆。最小堆记录最大元素。最大堆记录最大元素。堆主要支持以下操作 - delete_minimum()、get_minimum() 和 add()。

堆的第一个元素在恢复后可以删除。它需要O(log N)的时间,这非常有效。

实现

Python提供了使用堆排序来排序元素的内置函数。以下是这些函数。

  • heappush(list, item) - 用于添加堆元素并重新排序它。
  • heappop(list) - 用于删除元素并返回元素。
  • heapfy() - 用于将给定的列表转化为堆。

考虑以下堆排序的示例。

示例 -

from heapq import heappop, heappush  
   
 def heapsort(list1):  
     heap = []  
     for ele in list1:  
         heappush(heap, ele)  
   
     sort = []  
   
     # the elements are lift in the heap  
     while heap:  
         sort.append(heappop(heap))  
   
     return sort  
   
 list1 = [27, 21, 55, 15, 60, 4, 11, 17, 2, 87]  
 print(heapsort(list1))  

输出:

[2, 4, 11, 15, 17, 21, 27, 55, 60, 87]

解释:

在上面的代码中,我们导入了heapq模块,其中包含heappop()heappush()方法。我们创建了heapsort()方法,它以list1作为参数。使用for循环迭代list1,并将元素推入空堆中。我们使用while循环,将排序后的元素添加到空排序中。

我们调用了heapsort()函数并传入一个列表。它返回了已排序的列表。

对自定义对象进行排序

堆排序对于预定义的数据类型非常有用,但处理用户定义的数据类型(例如类对象)更复杂。我们将在本节中对自定义对象进行排序。

正如我们所看到的,我们的实现依赖于内置方法。Python提供了以下方法。

  • heapq.nlargest(n, iterable, key = None) - 该方法用于从可迭代的数据集中获取包含前n个最大元素的列表。
  • heapq.nsmallest(n, iterable, key = None) - 该方法用于从可迭代的数据集中获取包含前n个最小元素的列表。

让我们了解自定义对象的以下实现。

示例 -

from heapq import heappop, heappush  
   
 class Car:  
     def __init__(self, model, year):  
         self.model = model  
         self.year = year  
   
     def __str__(self):  
         return str.format("Model Name: {}, Year: {}", self.model, self.year)  
   
     def __lt__(self, other):  
         return self.year < other.year  
   
     def __gt__(self, other):  
         return other.__lt__(self)  
   
     def __eq__(self, other):  
         return self.year == other.year  
   
     def __ne__(self, other):  
         return not self.__eq__(other)  
   
   
 def heapsort(list1):  
     heap = []  
     for element in list1:  
         heappush(heap, element)  
   
     ordered = []  
   
     while heap:  
         ordered.append(heappop(heap))  
   
     return ordered  
   
   
 car1 = Car("Renault", 2001)  
 car2 = Car("Bentley", 2005)  
 car3 = Car("Kia", 2014)  
 car4 = Car("Maruti Suzuki", 1999);  
 car5 = Car("Nano", 2012)  
   
 list1 = [car1, car2, car3, car4, car5]  
   
 for c in Heapsort Heapsort (list1):  
     print(c)  

输出:

Model Name: Maruti Suzuki, Year: 1999
Model Name: Renault, Year: 2001
Model Name: Bentley, Year: 2005
Model Name: Nano, Year: 2012
Model Name: Kia, Year: 2014

我们已根据年份对对象进行排序。

堆排序与其他算法的比较

另一种流行的快速排序算法也非常高效,但堆排序因其可靠性而被广泛使用。堆排序的关键优势是时间复杂度上限为O(nlogn),无论是在平均情况还是在最坏情况下。

堆排序算法在平均和最坏情况下都需要O(nlogn)的时间,而快速排序在平均情况下比较快,快20%。

快速排序算法在可预测的情况下变得较慢。在快速排序中,存在触发糟糕O(n2)的机会,从而导致安全漏洞。

现在,我们将其与归并排序进行比较,归并排序所需的时间与堆排序相同。

归并排序更稳定,更容易并行化,而堆排序没有这些优势。

此外,在大多数情况下,归并排序比堆排序更快,因为它们具有相同的时间复杂性。

相反,堆排序可以在原地更快地实现,而归并排序不行。

结论

堆排序并不如其他排序算法那么流行和快速,但它比其他排序算法更可预测。在需要关注内存和安全性的情况下,可以使用此算法。

在Python中可以很快地实现它。我们只需将元素插入堆中,然后取出它们。

标签: Tkinter教程, Tkinter安装, Tkinter库, Tkinter入门, Tkinter学习, Tkinter入门教程, Tkinter, Tkinter进阶, Tkinter指南, Tkinter学习指南, Tkinter进阶教程, Tkinter编程