堆排序不稳定的例子_【译】Python中的堆排序
作者:Olivera Popovi?
翻譯:老齊
介紹
堆排序是高效排序算法的另一個(gè)例子,它的主要優(yōu)點(diǎn)是,無論輸入數(shù)據(jù)如何,它的最壞情況運(yùn)行時(shí)間都是O(n*logn)。
顧名思義,堆排序在很大程度上依賴于堆數(shù)據(jù)結(jié)構(gòu)——優(yōu)先級(jí)隊(duì)列的常見實(shí)現(xiàn)。
毫無疑問,堆排序是一種簡單的排序算法,而且與其他簡單實(shí)現(xiàn)相比,堆排序是更有效,也很常見。
堆排序
堆排序的工作原理是從堆逐個(gè)“移除”元素并將它們添加到已排序的數(shù)組里,在進(jìn)一步解釋和重新訪問堆數(shù)據(jù)結(jié)構(gòu)之前,我們應(yīng)該了解堆排序本身的一些屬性。
它是一種原地算法(譯者注:in-place algorithm,多數(shù)翻譯為“原地算法”,少數(shù)也翻譯為“就地算法”。這種算法是使用小的、固定數(shù)量的額外內(nèi)存空間來轉(zhuǎn)換資料的算法。),意味著它需要恒定數(shù)量的內(nèi)存,即所需內(nèi)存不取決于初始數(shù)組本身的大小,而取決于存儲(chǔ)該數(shù)組所需的內(nèi)存。
例如,不需要原始數(shù)組的副本,也不需要遞歸和遞歸調(diào)用堆棧。最簡單的堆排序?qū)崿F(xiàn)通常使用第二個(gè)數(shù)組來存儲(chǔ)排序后的值。我們將使用這種方法,因?yàn)樗诖a中更直觀、更易于實(shí)現(xiàn),但它也是百分百的原地算法。
堆排序不穩(wěn)定,意思是相等的值,并不會(huì)在同樣的相對(duì)位次上。對(duì)于整數(shù)、字符串等這些基本類型,不會(huì)出現(xiàn)這類問題,但當(dāng)我們對(duì)復(fù)雜類型的對(duì)象排序時(shí),可能會(huì)遇到。
例如,假設(shè)我們有一個(gè)自定義類Person帶有age和name屬性,在一個(gè)數(shù)組中幾個(gè)此類的實(shí)例對(duì)象,比如按順序出現(xiàn)19歲的名叫“Mike”的人和一個(gè)19歲的名叫“David”的人。
如果我們決定按年齡對(duì)這些人進(jìn)行排序,就不能在排序數(shù)組中保證“Mike”會(huì)出現(xiàn)在“David”之前,即使他們?cè)诔跏紨?shù)組中是按這個(gè)順序出現(xiàn)的。“Mike”有可能出現(xiàn)在“David”之前,但不能保證百分之百如此。
堆數(shù)據(jù)結(jié)構(gòu)
堆是計(jì)算機(jī)科學(xué)中最流行和最常用的一種數(shù)據(jù)結(jié)構(gòu)——更不用說在軟件工程面試中非常流行了。
我們將討論跟蹤最小元素(最小堆)的堆,但它們也可以很容易地實(shí)現(xiàn)對(duì)最大元素(最大堆)的跟蹤。
簡單地說,最小堆是一種基于樹的數(shù)據(jù)結(jié)構(gòu),其中每個(gè)節(jié)點(diǎn)比其所有子節(jié)點(diǎn)都小。通常使用二叉樹。堆有三個(gè)基本操作——delete_minimum()、get_minimum()和add()。
每次,你只能刪除堆中的第一個(gè)元素,然后對(duì)其進(jìn)行“重新排序”。在添加或刪除元素后,堆對(duì)自己會(huì)“重新排序”,以便最小的元素始終處于第一個(gè)位置。
注意:這絕不意味著堆是排序的數(shù)組。每個(gè)節(jié)點(diǎn)都小于其子節(jié)點(diǎn)這一事實(shí)不足以保證整個(gè)堆是按升序排列的。
我們來看一個(gè)關(guān)于堆的例子:
正如我們看到的,上面的例子確實(shí)符合堆的描述,但是沒有排序。我們不會(huì)詳細(xì)討論堆實(shí)現(xiàn),因?yàn)檫@不是本文的重點(diǎn)。當(dāng)在堆排序中使用堆數(shù)據(jù)結(jié)構(gòu)時(shí),我們所利用的堆數(shù)據(jù)結(jié)構(gòu)的關(guān)鍵優(yōu)勢是:下一個(gè)最小的元素始終是堆中的第一個(gè)元素。
實(shí)現(xiàn)
數(shù)組排序
譯者注: 作者在本文中并沒有嚴(yán)格區(qū)分Python中的列表和數(shù)組,而是將列表看做了數(shù)組,這對(duì)于列表中的元素是同一種類型的元素而言,無可厚非。對(duì)于排序,只有是同一種類型的元素,才有意義。
Python提供了創(chuàng)建和使用堆的方法,所以我們不必自己單獨(dú)為了實(shí)現(xiàn)它們?nèi)懘a了:
- heappush(list, item):向堆中添加一個(gè)元素,然后對(duì)其重新排序,使其保持堆狀態(tài)。可用于空列表。
- heappop(list):刪除第一個(gè)(最小的)元素并返回該元素。此操作之后,堆仍然是一個(gè)堆,因此我們不必調(diào)用heapify()。
- heapify(list):將給定的列表變成一個(gè)堆。
現(xiàn)在我們知道了這些,堆排序的實(shí)現(xiàn)就相當(dāng)簡單了:
from heapq import heappop, heappushdef heap_sort(array):heap = []for element in array:heappush(heap, element)ordered = []# While we have elements left in the heapwhile heap:ordered.append(heappop(heap))return orderedarray = [13, 21, 15, 5, 26, 4, 17, 18, 24, 2] print(heap_sort(array))輸出
[2, 4, 5, 13, 15, 17, 18, 21, 24, 26]如我們所見,堆數(shù)據(jù)結(jié)構(gòu)的繁重工作已經(jīng)完成,我們所要做的只是添加所需的所有元素并逐個(gè)刪除它們。它就像一臺(tái)硬幣計(jì)數(shù)機(jī),根據(jù)輸入的硬幣的價(jià)值對(duì)它們進(jìn)行分類,然后我們可以取出它們。
自定義對(duì)象排序
當(dāng)使用自定義類時(shí),事情會(huì)變得更加復(fù)雜。通常,為了使用我們的排序算法,建議不要重寫類中的比較運(yùn)算符,而是建議重寫該算法,以便使用lambda函數(shù)比較。
但是,由于我們的實(shí)現(xiàn)依賴于內(nèi)置堆方法,因此不能在這里這樣做。
Python確實(shí)提供了以下方法:
- heapq.nlargest(*n*, *iterable*, *key=None*):返回一個(gè)列表,其中包含由iterable定義的數(shù)據(jù)集中的n個(gè)最大元素。
- heapq.nsmallest(*n*, *iterable*, *key=None*):返回一個(gè)列表,其中包含由iterable定義的數(shù)據(jù)集中的n個(gè)最小元素。
我們可以使用它來簡單地獲取n = len(array)最大/最小元素,但是方法本身不使用堆排序,本質(zhì)上等同于只調(diào)用sorted()方法。
我們留給自定義類的唯一解決方案是實(shí)際重寫比較運(yùn)算符。遺憾的是,這使我們局限于對(duì)每個(gè)類只能進(jìn)行一種比較。在我們的示例中,我們被局限于按年份對(duì)Movie對(duì)象進(jìn)行排序。
但是,它確實(shí)讓我們演示了在自定義類上使用堆排序。我們來定義Movie類:
from heapq import heappop, heappushclass Movie:def __init__(self, title, year):self.title = titleself.year = yeardef __str__(self):return str.format("Title: {}, Year: {}", self.title, self.year)def __lt__(self, other):return self.year < other.yeardef __gt__(self, other):return other.__lt__(self)def __eq__(self, other):return self.year == other.yeardef __ne__(self, other):return not self.__eq__(other)現(xiàn)在,讓我們稍微修改一下heap_sort()函數(shù):
def heap_sort(array):heap = []for element in array:heappush(heap, element)ordered = []while heap:ordered.append(heappop(heap))return ordered最后,讓我們實(shí)例化一些電影,將它們放入一個(gè)數(shù)組中,然后對(duì)它們進(jìn)行排序:
movie1 = Movie("Citizen Kane", 1941) movie2 = Movie("Back to the Future", 1985) movie3 = Movie("Forrest Gump", 1994) movie4 = Movie("The Silence of the Lambs", 1991); movie5 = Movie("Gia", 1998)array = [movie1, movie2, movie3, movie4, movie5]for movie in heap_sort(array):print(movie)輸出:
Title: Citizen Kane, Year: 1941 Title: Back to the Future, Year: 1985 Title: The Silence of the Lambs, Year: 1991 Title: Forrest Gump, Year: 1994 Title: Gia, Year: 1998與其他排序算法的比較
堆排序被廣泛使用,主要原因是它的可靠性,盡管它經(jīng)常被運(yùn)行良好的“快速排序”法所超越(譯者注: 本文的微信公眾號(hào)“老齊教室”以系列文章,介紹各種排序算法,并且用Python語言實(shí)現(xiàn),敬請(qǐng)關(guān)注)。
堆排序的主要優(yōu)點(diǎn)是時(shí)間復(fù)雜度上的O(n*logn)上限以及安全性。Linux內(nèi)核開發(fā)人員給出了使用堆排序而不是快速排序的以下理由:
堆排序的平均排序時(shí)間和最壞排序時(shí)間均為O(n*logn),雖然qsort的平均速度快了20%,但不得不容忍O(n*n)的最壞可能情形和額外的內(nèi)存支出,這使它不太適合在操作系統(tǒng)內(nèi)核中使用。
此外,快速排序算法法在可預(yù)測的情況下表現(xiàn)不佳。并且,如果對(duì)內(nèi)部實(shí)現(xiàn)有足夠的了解,你可能會(huì)意識(shí)到它造成的安全風(fēng)險(xiǎn)(主要是DDoS攻擊),因?yàn)椴涣嫉腛(n^2)行為很容易被觸發(fā)。
經(jīng)常被用來與堆排序比較的另一種算法是歸并排序算法(譯者注: 本微信公眾號(hào)也會(huì)刊發(fā)相關(guān)文章給予介紹,敬請(qǐng)關(guān)注),它們具有相同的時(shí)間復(fù)雜度。
歸并排序的優(yōu)點(diǎn)是穩(wěn)定、可并行運(yùn)算,而堆排序兩者都做不到。
另一個(gè)注意事項(xiàng)是:即使復(fù)雜度相同,堆排序在大多數(shù)情況下也比歸并排序慢,因?yàn)槎雅判蚓哂休^大的常數(shù)因子。
然而,堆排序比歸并排序更容易實(shí)現(xiàn),因此當(dāng)內(nèi)存比速度更重要時(shí),它是首選。
結(jié)論
正如我們所看到的,堆排序不像其他高效的通用算法那么流行,但是它的可預(yù)測行為(而不是不穩(wěn)定的行為)使它成為一個(gè)很好的算法,適用于內(nèi)存和安全性比稍快的運(yùn)行速度更重要的場合。
實(shí)現(xiàn)和利用Python提供的內(nèi)置功能是非常直觀的,我們實(shí)際上要做的就是將元素放在一個(gè)堆中并取出它們,就像對(duì)待硬幣計(jì)數(shù)器一樣。
原文鏈接:https://stackabuse.com/heap-sort-in-python/
★ 關(guān)注微信公眾號(hào):老齊教室。讀深度文章,得精湛技藝,享絢麗人生。”
總結(jié)
以上是生活随笔為你收集整理的堆排序不稳定的例子_【译】Python中的堆排序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用python画长方形_Python+o
- 下一篇: python 找质数的个数_用Pytho