<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python實(shí)現(xiàn)經(jīng)典排序算法,再也不怕面試官讓我手寫(xiě)快排了!

          共 4957字,需瀏覽 10分鐘

           ·

          2020-11-19 02:12


          大家好,歡迎來(lái)到Crossin的編程教室!

          算法作為程序員的必修課,是每位程序員必須掌握的基礎(chǔ)。本篇我們將通過(guò)Python來(lái)實(shí)現(xiàn)5大經(jīng)典排序算法,結(jié)合例圖剖析內(nèi)部實(shí)現(xiàn)邏輯,對(duì)比每種算法各自的優(yōu)缺點(diǎn)和應(yīng)用點(diǎn)。相信我,耐心看完絕對(duì)有收獲。

          準(zhǔn)備工作

          大家都知道從理論上講,我們一般會(huì)使用大O表示法測(cè)量算法的運(yùn)行時(shí)復(fù)雜度"大O表示法"表示程序的執(zhí)行時(shí)間或占用空間隨數(shù)據(jù)規(guī)模的增長(zhǎng)趨勢(shì)。

          但為了測(cè)算具體的時(shí)間,本篇將使用timeit模塊來(lái)衡量實(shí)現(xiàn)的運(yùn)行時(shí)間。下面自己寫(xiě)一個(gè)對(duì)算法測(cè)試時(shí)間的函數(shù)。

          from?random?import?randint
          from?timeit?import?repeat

          def?run_sorting_algorithm(algorithm,?array):
          ????#?調(diào)用特定的算法對(duì)提供的數(shù)組執(zhí)行。
          ????#?如果不是內(nèi)置sort()函數(shù),那就只引入算法函數(shù)。
          ????setup_code?=?f"from?__main__?import?{algorithm}"?\
          ????????if?algorithm?!=?"sorted"?else?""

          ????stmt?=?f"{algorithm}({array})"

          ????#?十次執(zhí)行代碼,并返回以秒為單位的時(shí)間
          ????times?=?repeat(setup=setup_code,?stmt=stmt,?repeat=3,?number=10)

          ????#?最后,顯示算法的名稱和運(yùn)行所需的最短時(shí)間
          ????print(f"Algorithm:?{algorithm}.?Minimum?execution?time:?{min(times)}")

          這里用到了一個(gè)騷操作,通過(guò)f-strings魔術(shù)方法導(dǎo)入了算法名稱,不懂的可以參考:Python 格式化字符串的最佳姿勢(shì)

          注意:應(yīng)該找到算法每次運(yùn)行的平均時(shí)間,而不是選擇單個(gè)最短時(shí)間。由于系統(tǒng)同時(shí)運(yùn)行其他進(jìn)程,因此時(shí)間測(cè)量是受影響的。最短的時(shí)間肯定是影響最小的,是這樣才使其成為算法時(shí)間最短的。

          Python中的冒泡排序算法

          冒泡排序是最直接的排序算法之一。它的名稱來(lái)自算法的工作方式:每經(jīng)過(guò)一次新的遍歷,列表中最大的元素就會(huì)“冒泡”至正確位置。

          在Python中實(shí)現(xiàn)冒泡排序

          這是Python中冒泡排序算法的實(shí)現(xiàn):

          def?bubble_sort(array):
          ?????n?=?len(array)
          ?
          ?????for?i?in?range(n):
          ?????????#?創(chuàng)建一個(gè)標(biāo)識(shí),當(dāng)沒(méi)有可以排序的時(shí)候就使函數(shù)終止。
          ?????????already_sorted?=?True
          ?
          ?????????#?從頭開(kāi)始逐個(gè)比較相鄰元素,每一次循環(huán)的總次數(shù)減1,
          ?????????#?因?yàn)槊看窝h(huán)一次,最后面元素的排序就確定一個(gè)。
          ?????????for?j?in?range(n?-?i?-?1):
          ?????????????if?array[j]?>?array[j?+?1]:
          ?????????????????#?如果此時(shí)的元素大于相鄰后一個(gè)元素,那么交換。
          ?????????????????array[j],?array[j?+?1]?=?array[j?+?1],?array[j]
          ?
          ?????????????????#?如果有了交換,設(shè)置already_sorted標(biāo)志為False算法不會(huì)提前停止
          ?????????????????already_sorted?=?False
          ?
          ?????????#?如果最后一輪沒(méi)有交換,數(shù)據(jù)已經(jīng)排序完畢,退出
          ?????????if?already_sorted:
          ?????????????break
          ?
          ?????return?array

          為了正確分析算法的工作原理,看下這個(gè)列表[8, 2, 6, 4, 5]。假設(shè)使用bubble_sort()排序,下圖說(shuō)明了算法每次迭代時(shí)數(shù)組的實(shí)際換件情況:

          冒泡排序過(guò)程

          測(cè)算冒泡算法的大O運(yùn)行復(fù)雜度

          冒泡排序的實(shí)現(xiàn)由兩個(gè)嵌套for循環(huán)組成,其中算法先執(zhí)行n-1個(gè)比較,然后進(jìn)行n-2個(gè)比較,依此類推,直到完成最終比較。因此,總的比較次數(shù)為(N - 1)+(N - 2)+(N - 3)+ ... + 2 + 1 = N(N-1)/ 2,也可以寫(xiě)成 ?n?2?- ?n

          去掉不會(huì)隨數(shù)據(jù)規(guī)模n而變化的常量可以將符號(hào)簡(jiǎn)化為 n2?-n。由于 n2的增長(zhǎng)速度快于n,因此也可以舍棄最后一項(xiàng),使冒泡排序的平均和最壞情況下的時(shí)間復(fù)雜度為 O(n?2

          在算法接收到已排序的數(shù)組的情況下,運(yùn)行時(shí)間復(fù)雜度將降低到更好的O(n),因?yàn)樗惴ㄑh(huán)一遍沒(méi)有任何交換,標(biāo)志是true,所以循環(huán)一遍比較了N次直接退出。因此,O(n)是冒泡排序的最佳情況運(yùn)行時(shí)間復(fù)雜度。但最好的情況是個(gè)例外,比較不同的算法時(shí),應(yīng)該關(guān)注平均情況。

          泡排序的時(shí)間運(yùn)行測(cè)試

          使用run_sorting_algorithm()測(cè)試冒泡排序處理具有一萬(wàn)個(gè)元素的數(shù)組所花費(fèi)的時(shí)間。

          ARRAY_LENGTH?=?10000if?__name__?==?"__main__":
          ?????#?生成包含“?ARRAY_LENGTH”個(gè)元素的數(shù)組,元素是介于0到999之間的隨機(jī)整數(shù)值
          ?????array?=?[randint(0,?1000)?for?i?in?range(ARRAY_LENGTH)]
          ?
          ?????#?使用排序算法的名稱和剛創(chuàng)建的數(shù)組調(diào)用該函數(shù)
          ?????run_sorting_algorithm(algorithm="bubble_sort",?array=array)

          現(xiàn)在運(yùn)行腳本來(lái)獲取bubble_sort的執(zhí)行時(shí)間:

          $?python?sorting.py
          Algorithm:?bubble_sort.?Minimum?execution?time:?73.21720498399998

          分析冒泡排序的優(yōu)缺點(diǎn)

          冒泡排序算法的主要優(yōu)點(diǎn)是它的簡(jiǎn)單性,理解起來(lái)非常簡(jiǎn)單。但也看到了冒泡排序的缺點(diǎn)是速度慢,運(yùn)行時(shí)間復(fù)雜度為O(n?2。因此,一般對(duì)大型數(shù)組進(jìn)行排序的時(shí)候,不會(huì)考慮使用冒泡排序。

          Python中的插入排序算法

          像冒泡排序一樣,插入排序算法也易于實(shí)現(xiàn)和理解。但是與冒泡排序不同,它通過(guò)將每個(gè)元素與列表的其余元素進(jìn)行比較并將其插入正確的位置,來(lái)一次構(gòu)建一個(gè)排序的列表元素。此“插入”過(guò)程為算法命名。

          一個(gè)例子,就是對(duì)一副紙牌進(jìn)行排序。將一張卡片與其余卡片進(jìn)行逐個(gè)比較,直到找到正確的位置為止,然后重復(fù)進(jìn)行直到您手中的所有卡都被排序。

          在Python中實(shí)現(xiàn)插入排序

          插入排序算法的工作原理與紙牌排序完全相同,Python中的實(shí)現(xiàn):

          ??def?insertion_sort(array):
          ????#?從數(shù)據(jù)第二個(gè)元素開(kāi)始循環(huán),直到最后一個(gè)元素
          ????for?i?in?range(1,?len(array)):
          ????????#?這個(gè)是我們想要放在正確位置的元素
          ????????key_item?=?array[i]

          ????????#?初始化變量,用于尋找元素正確位置
          ????????j?=?i?-?1

          ????????#?遍歷元素左邊的列表元素,一旦key_item比被比較元素小,那么找到正確位置插入。
          ????????while?j?>=?0?and?array[j]?>?key_item:
          ????????????#?把被檢測(cè)元素向左平移一個(gè)位置,并將j指向下一個(gè)元素(從右向左)
          ????????????array[j?+?1]?=?array[j]
          ????????????j?-=?1

          ????????#?當(dāng)完成元素位置的變換,把key_item放在正確的位置上
          ????????array[j?+?1]?=?key_item

          ????return?array

          下圖顯示了對(duì)數(shù)組進(jìn)行排序時(shí)算法的不同迭代[8, 2, 6, 4, 5]

          插入排序過(guò)程


          測(cè)量插入排序的大O時(shí)間復(fù)雜度

          與冒泡排序?qū)崿F(xiàn)類似,插入排序算法具有兩個(gè)嵌套循環(huán),遍歷整個(gè)列表。內(nèi)部循環(huán)非常有效,因?yàn)樗鼤?huì)遍歷列表,直到找到元素的正確位置為止。

          最壞的情況發(fā)生在所提供的數(shù)組以相反順序排序時(shí)。在這種情況下,內(nèi)部循環(huán)必須執(zhí)行每個(gè)比較,以將每個(gè)元素放置在正確的位置。這仍然給您帶來(lái)O(n2)運(yùn)行時(shí)復(fù)雜性。

          最好的情況是對(duì)提供的數(shù)組進(jìn)行了排序。這里,內(nèi)部循環(huán)永遠(yuǎn)不會(huì)執(zhí)行,導(dǎo)致運(yùn)行時(shí)復(fù)雜度為O(n),就像冒泡排序的最佳情況一樣。

          盡管冒泡排序和插入排序具有相同的大O時(shí)間復(fù)雜度,但實(shí)際上,插入排序比冒泡排序有效得多。如果查看兩種算法的實(shí)現(xiàn),就會(huì)看到插入排序是如何減少了對(duì)列表進(jìn)行排序的比較次數(shù)的。

          插入排序時(shí)間測(cè)算

          為了證明插入排序比冒泡排序更有效,可以對(duì)插入排序算法進(jìn)行計(jì)時(shí),并將其與冒泡排序的結(jié)果進(jìn)行比較。調(diào)用我們寫(xiě)好的測(cè)試函數(shù)。

          ??if?__name__?==?"__main__":
          ?????#?生成包含“?ARRAY_LENGTH”個(gè)元素的數(shù)組,元素是介于0到999之間的隨機(jī)整數(shù)值
          ?????array?=?[randint(0,?1000)?for?i?in?range(ARRAY_LENGTH)]
          ?
          ?????#?使用排序算法的名稱和剛創(chuàng)建的數(shù)組調(diào)用該函數(shù)
          ????run_sorting_algorithm(algorithm="insertion_sort",?array=array)

          執(zhí)行腳本:

          ??$?python?sorting.py
          Algorithm:?insertion_sort.?Minimum?execution?time:?56.71029764299999

          可以看到,插入排序比冒泡排序?qū)崿F(xiàn)少了17秒,即使它們都是O(n 2)算法,插入排序也更加有效。

          分析插入排序的優(yōu)點(diǎn)和缺點(diǎn)

          就像冒泡排序一樣,插入排序算法的實(shí)現(xiàn)也很簡(jiǎn)單。盡管插入排序是O(n 2)算法,但在實(shí)踐中它也比其他二次實(shí)現(xiàn)(例如冒泡排序)更有效。

          有更強(qiáng)大的算法,包括合并排序和快速排序,但是這些實(shí)現(xiàn)是遞歸的,在處理小型列表時(shí)通常無(wú)法擊敗插入排序。如果列表足夠小,可以提供更快的整體實(shí)現(xiàn),則某些快速排序?qū)崿F(xiàn)甚至在內(nèi)部使用插入排序。Timsort還在內(nèi)部使用插入排序?qū)斎霐?shù)組的一小部分進(jìn)行排序。

          也就是說(shuō),插入排序不適用于大型陣列,這為可以更有效地?cái)U(kuò)展規(guī)模的算法打開(kāi)了大門(mén)。

          Python中的合并排序算法

          合并排序是一種非常有效的排序算法。它基于分治法,這是一種用于解決復(fù)雜問(wèn)題的強(qiáng)大算法技術(shù)。

          要正確理解分而治之,應(yīng)該首先了解遞歸的概念。遞歸涉及將問(wèn)題分解成較小的子問(wèn)題,直到它們足夠小以至于無(wú)法解決。在編程中,遞歸通常由調(diào)用自身的函數(shù)表示。

          分而治之算法通常遵循相同的結(jié)構(gòu):

          原始輸入分為幾個(gè)部分,每個(gè)部分代表一個(gè)子問(wèn)題,該子問(wèn)題與原始輸入相似,但更為簡(jiǎn)單。每個(gè)子問(wèn)題都遞歸解決。所有子問(wèn)題的解決方案都組合成一個(gè)整體解決方案。在合并排序的情況下,分而治之方法將輸入值的集合劃分為兩個(gè)大小相等的部分,對(duì)每個(gè)一半進(jìn)行遞歸排序,最后將這兩個(gè)排序的部分合并為一個(gè)排序列表。

          在Python中實(shí)現(xiàn)合并排序

          合并排序算法的實(shí)現(xiàn)需要兩個(gè)不同的部分:

          遞歸地將輸入分成兩半的函數(shù) 合并兩個(gè)半部的函數(shù),產(chǎn)生一個(gè)排序數(shù)組 這是合并兩個(gè)不同數(shù)組的代碼:

          def?merge(left,?right):
          ??#?如果第一個(gè)數(shù)組為空,那么不需要合并,
          ??#?可以直接將第二個(gè)數(shù)組返回作為結(jié)果
          ??if?len(left)?==?0:
          ??????return?right

          ? #?如果第二個(gè)數(shù)組為空,那么不需要合并,
          ??#?可以直接將第一個(gè)數(shù)組返回作為結(jié)果
          ??if?len(right)?==?0:
          ??????return?left

          ??result?=?[]
          ??index_left?=?index_right?=?0

          ??#?查看兩個(gè)數(shù)組直到所有元素都裝進(jìn)結(jié)果數(shù)組中
          ??while?len(result)???????#?這些需要排序的元素要依次被裝入結(jié)果列表,因此需要決定將從
          ??????#?第一個(gè)還是第二個(gè)數(shù)組中取下一個(gè)元素
          ??????if?left[index_left]?<=?right[index_right]:
          ??????????result.append(left[index_left])
          ??????????index_left?+=?1
          ??????else:
          ??????????result.append(right[index_right])
          ??????????index_right?+=?1

          ??????#?如果哪個(gè)數(shù)組達(dá)到了最后一個(gè)元素,那么可以將另外一個(gè)數(shù)組的剩余元素
          ??????#?裝進(jìn)結(jié)果列表中,然后終止循環(huán)
          ??????if?index_right?==?len(right):
          ??????????result?+=?left[index_left:]
          ??????????break

          ??????if?index_left?==?len(left):
          ??????????result?+=?right[index_right:]
          ??????????break

          ??return?result

          現(xiàn)在還缺少的部分是將輸入數(shù)組遞歸拆分為兩個(gè)并用于merge()產(chǎn)生最終結(jié)果的函數(shù):

          def?merge_sort(array):
          ??#?如果輸入數(shù)組包含元素不超過(guò)兩個(gè),那么返回它作為函數(shù)結(jié)果
          ??if?len(array)?2:
          ??????return?array

          ??midpoint?=?len(array)?//?2

          ??#?對(duì)數(shù)組遞歸地劃分為兩部分,排序每部分然后合并裝進(jìn)最終結(jié)果列表
          ??return?merge(
          ??????left=merge_sort(array[:midpoint]),
          ??????right=merge_sort(array[midpoint:]))

          看一下合并排序?qū)?duì)數(shù)組進(jìn)行排序的步驟[8, 2, 6, 4, 5]

          合并排序過(guò)程

          該圖使用黃色箭頭表示在每個(gè)遞歸級(jí)別將數(shù)組減半。綠色箭頭表示將每個(gè)子陣列合并在一起。

          衡量合并排序的大O復(fù)雜度

          要分析合并排序的復(fù)雜性,可以分別查看其兩個(gè)步驟:

          merge()具有線性運(yùn)行時(shí)間。它接收兩個(gè)數(shù)組,它們的組合長(zhǎng)度最多為n(原始輸入數(shù)組的長(zhǎng)度),并且通過(guò)最多查看每個(gè)元素一次來(lái)組合兩個(gè)數(shù)組。這導(dǎo)致運(yùn)行時(shí)復(fù)雜度為O(n)。

          第二步以遞歸方式拆分輸入數(shù)組,并調(diào)用merge()每一部分。由于將數(shù)組減半直到剩下單個(gè)元素,因此此功能執(zhí)行的減半運(yùn)算總數(shù)為log 2 n。由于merge()每個(gè)部分都被調(diào)用,因此總運(yùn)行時(shí)間為O(n log 2 n)。

          對(duì)合并排序進(jìn)行測(cè)算時(shí)間

          同樣通過(guò)之前時(shí)間測(cè)試函數(shù):

          if?__name__?==?"__main__":
          ?????#?生成包含“?ARRAY_LENGTH”個(gè)元素的數(shù)組,元素是介于0到999之間的隨機(jī)整數(shù)值
          ?????array?=?[randint(0,?1000)?for?i?in?range(ARRAY_LENGTH)]
          ?
          ?????#?使用排序算法的名稱和剛創(chuàng)建的數(shù)組調(diào)用該函數(shù)
          ????run_sorting_algorithm(algorithm="merge_sort",?array=array)

          執(zhí)行時(shí)間:

          $?python?sorting.py
          Algorithm:?merge_sort.?Minimum?execution?time:?0.6195857160000173

          與冒泡排序和插入排序相比,合并排序?qū)崿F(xiàn)非常快,可以在一秒鐘內(nèi)對(duì)一萬(wàn)個(gè)數(shù)組進(jìn)行排序了!

          分析合并排序的優(yōu)點(diǎn)和缺點(diǎn)

          由于其運(yùn)行時(shí)復(fù)雜度為O(n log 2 n),因此合并排序是一種非常有效的算法,可以隨著輸入數(shù)組大小的增長(zhǎng)而很好地?cái)U(kuò)展。并行化也很簡(jiǎn)單,因?yàn)樗鼘⑤斎霐?shù)組分成多個(gè)塊,必要時(shí)可以并行分配和處理這些塊。

          缺點(diǎn)是對(duì)于較小的列表,遞歸的時(shí)間成本就較高了,冒泡排序和插入排序之類的算法更快。例如,運(yùn)行包含十個(gè)元素的列表的實(shí)驗(yàn)的時(shí)間:

          Algorithm:?bubble_sort.?Minimum?execution?time:?0.000018774999999998654
          Algorithm:?insertion_sort.?Minimum?execution?time:?0.000029786000000000395
          Algorithm:?merge_sort.?Minimum?execution?time:?0.00016983000000000276

          合并排序的另一個(gè)缺點(diǎn)是,它在遞歸調(diào)用自身時(shí)會(huì)創(chuàng)建數(shù)組。它還在內(nèi)部創(chuàng)建一個(gè)新列表,這使得合并排序比氣泡排序和插入排序使用更多的內(nèi)存。

          Python中的快速排序算法

          就像合并排序一樣,快速排序算法采用分而治之的原理將輸入數(shù)組分為兩個(gè)列表,第一個(gè)包含小項(xiàng)目,第二個(gè)包含大項(xiàng)目。然后,該算法將對(duì)兩個(gè)列表進(jìn)行遞歸排序,直到對(duì)結(jié)果列表進(jìn)行完全排序?yàn)橹埂?/p>

          劃分輸入列表稱為對(duì)列表進(jìn)行分區(qū)。快排首先選擇一個(gè)pivot元素,然后將列表劃分為pivot,然后將每個(gè)較小的元素放入low數(shù)組,將每個(gè)較大的元素放入high數(shù)組

          將low列表中的每個(gè)元素放在列表的左側(cè),列表中的pivot每個(gè)元素high放在右側(cè),將其pivot精確定位在最終排序列表中的確切位置。這意味著該函數(shù)現(xiàn)在可以遞歸地將相同的過(guò)程應(yīng)用于low,然后high對(duì)整個(gè)列表進(jìn)行排序。

          在Python中實(shí)現(xiàn)快排

          這是快排的一個(gè)相當(dāng)緊湊的實(shí)現(xiàn):

          from?random?import?randint

          def?quicksort(array):
          ?? #?如果第一個(gè)數(shù)組為空,那么不需要合并,
          ?? #?可以直接將第二個(gè)數(shù)組返回作為結(jié)果
          ????if?len(array)?2:
          ????????return?array

          ????low,?same,?high?=?[],?[],?[]

          ????#?隨機(jī)選擇 pivot 元素
          ????pivot?=?array[randint(0,?len(array)?-?1)]

          ????for?item?in?array:
          ????????#?元素小于pivot元素的裝進(jìn)low列表中,大于piviot元素值的裝進(jìn)high列表中
          ????????#?如果和pivot相等,則裝進(jìn)same列表中
          ????????if?item?????????????low.append(item)
          ????????elif?item?==?pivot:
          ????????????same.append(item)
          ????????elif?item?>?pivot:
          ????????????high.append(item)

          ????#?最后的結(jié)果列表包含排序的low列表、same列表、hight列表
          ????return?quicksort(low)?+?same?+?quicksort(high)

          以下是快排對(duì)數(shù)組進(jìn)行排序的步驟的說(shuō)明[8, 2, 6, 4, 5]

          快排排序過(guò)程

          快速排序流程 黃線表示陣列的劃分成三個(gè)列表:low,same,high。綠線表示排序并將這些列表放在一起。

          選擇pivot元素

          為什么上面的實(shí)現(xiàn)會(huì)pivot隨機(jī)選擇元素?選擇輸入列表的第一個(gè)或最后一個(gè)元素會(huì)不會(huì)一樣?

          由于快速排序算法的工作原理,遞歸級(jí)別的數(shù)量取決于pivot每個(gè)分區(qū)的結(jié)尾位置。在最佳情況下,算法始終選擇中值元素作為pivot。這將使每個(gè)生成的子問(wèn)題恰好是前一個(gè)問(wèn)題的一半,從而導(dǎo)致最多l(xiāng)og 2 n級(jí)。

          另一方面,如果算法始終選擇數(shù)組的最小或最大元素作為pivot,則生成的分區(qū)將盡可能不相等,從而導(dǎo)致n-1個(gè)遞歸級(jí)別。對(duì)于快速排序,那將是最壞的情況。

          如你所見(jiàn),快排的效率通常取決于pivot選擇。如果輸入數(shù)組未排序,則將第一個(gè)或最后一個(gè)元素用作,pivot將與隨機(jī)元素相同。但是,如果輸入數(shù)組已排序或幾乎已排序,則使用第一個(gè)或最后一個(gè)元素作為pivot可能導(dǎo)致最壞的情況。pivot隨機(jī)選擇使其更有可能使快排選擇一個(gè)接近中位數(shù)的值并更快地完成。

          另一個(gè)選擇是找到數(shù)組的中值,并強(qiáng)制算法將其用作pivot。這可以在O(n)時(shí)間內(nèi)完成。盡管該過(guò)程稍微復(fù)雜一些,但將中值用作pivot快速排序可以確保您擁有最折中的大O方案。

          衡量快排的大O復(fù)雜度

          使用快排,將輸入列表按線性時(shí)間O(n)進(jìn)行分區(qū),并且此過(guò)程將平均遞歸地重復(fù)log 2 n次。這導(dǎo)致最終復(fù)雜度為O(n log 2 n)

          當(dāng)所選擇的pivot是接近陣列的中位數(shù),最好的情況下會(huì)發(fā)生O(n)當(dāng)pivot是陣列的最小或最大的值,最差的時(shí)間復(fù)雜度為O(n 2

          從理論上講,如果算法首先專注于找到中值,然后將其用作pivot元素,那么最壞情況下的復(fù)雜度將降至O(n log 2 n)。數(shù)組的中位數(shù)可以在線性時(shí)間內(nèi)找到,并將其用作pivot保證代碼的快速排序部分將在O(n log 2 n)中執(zhí)行。

          通過(guò)使用中值作為pivot,最終運(yùn)行時(shí)間為O(n)+ O(n log 2 n)。你可以將其簡(jiǎn)化為O(n log 2 n),因?yàn)閷?duì)數(shù)部分的增長(zhǎng)快于線性部分。

          隨機(jī)選擇pivot使最壞情況發(fā)生的可能性很小。這使得隨機(jī)pivot選擇對(duì)于該算法的大多數(shù)實(shí)現(xiàn)都足夠好。

          對(duì)快排測(cè)量運(yùn)行時(shí)間

          調(diào)用測(cè)試函數(shù):

          if?__name__?==?"__main__":
          ?????#?生成包含“?ARRAY_LENGTH”個(gè)元素的數(shù)組,元素是介于0到999之間的隨機(jī)整數(shù)值
          ?????array?=?[randint(0,?1000)?for?i?in?range(ARRAY_LENGTH)]
          ?
          ?????#?使用排序算法的名稱和剛創(chuàng)建的數(shù)組調(diào)用該函數(shù)
          ????run_sorting_algorithm(algorithm="quicksort",?array=array)

          執(zhí)行腳本:

          $?python?sorting.py
          Algorithm:?quicksort.?Minimum?execution?time:?0.11675417600002902

          快速排序不僅可以在不到一秒鐘的時(shí)間內(nèi)完成,而且比合并排序(0.11幾秒鐘對(duì)0.61幾秒鐘)要快得多。如果增加ARRAY_LENGTH10,000到數(shù)量1,000,000并再次運(yùn)行腳本,合并排序最終會(huì)在97幾秒鐘內(nèi)完成,而快排則僅在10幾秒鐘內(nèi)對(duì)列表進(jìn)行排序。

          分析快排的優(yōu)勢(shì)和劣勢(shì)

          顧名思義,快排非常快。盡管從理論上講,它的最壞情況是O(n 2),但在實(shí)踐中,快速排序的良好實(shí)現(xiàn)勝過(guò)大多數(shù)其他排序?qū)崿F(xiàn)。而且,就像合并排序一樣,快排也很容易并行化。

          快排的主要缺點(diǎn)之一是缺乏保證達(dá)到平均運(yùn)行時(shí)復(fù)雜度的保證。盡管最壞的情況很少見(jiàn),但是某些應(yīng)用程序不能承受性能不佳的風(fēng)險(xiǎn),因此無(wú)論輸入如何,它們都選擇不超過(guò)O(n log 2 n)的算法。

          就像合并排序一樣,快排也會(huì)在內(nèi)存空間與速度之間進(jìn)行權(quán)衡。這可能成為對(duì)較大列表進(jìn)行排序的限制。

          通過(guò)快速實(shí)驗(yàn)對(duì)十個(gè)元素的列表進(jìn)行排序,可以得出以下結(jié)果:

          Algorithm:?bubble_sort.?Minimum?execution?time:?0.0000909000000000014
          Algorithm:?insertion_sort.?Minimum?execution?time:?0.00006681900000000268
          Algorithm:?quicksort.?Minimum?execution?time:?0.0001319930000000004

          結(jié)果表明,當(dāng)列表足夠小時(shí),快速排序也要付出遞歸的代價(jià),完成時(shí)間比插入排序和冒泡排序都要長(zhǎng)。

          Python中的Timsort算法

          最后這個(gè)算法就有意思了!

          所述Timsort算法被認(rèn)為是一種混合的排序算法,因?yàn)樗捎貌迦肱判蚝秃喜⑴判虻淖罴训膬蓚€(gè)世界級(jí)組合。Timsort與Python社區(qū)也很有緣,它是由Tim Peters于2002年創(chuàng)建的,被用作Python語(yǔ)言的標(biāo)準(zhǔn)排序算法。我們使用的內(nèi)置sorted函數(shù)就是這個(gè)算法。

          Timsort的主要特征是它利用了大多數(shù)現(xiàn)實(shí)數(shù)據(jù)集中存在的已排序元素。這些稱為natural runs。然后,該算法會(huì)遍歷列表,將元素收集到運(yùn)行中,然后將它們合并到一個(gè)排序的列表中。

          在Python中實(shí)現(xiàn)Timsort

          本篇?jiǎng)?chuàng)建一個(gè)準(zhǔn)系統(tǒng)的Python實(shí)現(xiàn),該實(shí)現(xiàn)說(shuō)明Timsort算法的所有部分。如果有興趣,也可以查看Timsort的原始C實(shí)現(xiàn)。

          實(shí)施Timsort的第一步是修改insertion_sort()的實(shí)現(xiàn):

          def?insertion_sort(array,?left=0,?right=None):
          ????if?right?is?None:
          ????????right?=?len(array)?-?1

          ????#?從指示的left元素循環(huán),直到right被指示
          ????for?i?in?range(left?+?1,?right?+?1):
          #?這個(gè)是我們想要放在正確位置的元素
          ????????key_item?=?array[i]

          ? #?初始化變量,用于尋找元素正確位置
          ????????j?=?i?-?1
          #?遍歷元素左邊的列表元素,一旦key_item比被比較元素小,那么找到正確位置插入
          ????????while?j?>=?left?and?array[j]?>?key_item:
          #?把被檢測(cè)元素向左平移一個(gè)位置,并將j指向下一個(gè)元素(從右向左)
          ????????????array[j?+?1]?=?array[j]
          ????????????j?-=?1

          #?當(dāng)完成元素位置的變換,把key_item放在正確的位置上
          ????????array[j?+?1]?=?key_item

          ????return?array

          此修改后的實(shí)現(xiàn)添加了兩個(gè)參數(shù)left和right,它們指示應(yīng)該對(duì)數(shù)組的哪一部分進(jìn)行排序。這使Timsort算法可以對(duì)數(shù)組的一部分進(jìn)行排序。修改功能而不是創(chuàng)建新功能意味著可以將其同時(shí)用于插入排序和Timsort。

          現(xiàn)在看一下Timsort的實(shí)現(xiàn):

          def?timsort(array):
          ????min_run?=?32
          ????n?=?len(array)

          ????#?開(kāi)始切分、排序輸入數(shù)組的小部分,切分在`min_run`定義
          ????for?i?in?range(0,?n,?min_run):
          ????????insertion_sort(array,?i,?min((i?+?min_run?-?1),?n?-?1))

          ????#?現(xiàn)在可以合并排序的切分塊了
          ????#?從`min_run`開(kāi)始,?每次循環(huán)都加倍直到超過(guò)數(shù)組的長(zhǎng)度
          ????size?=?min_run
          ????while?size?????????#?Determine?the?arrays?that?will
          ????????#?be?merged?together
          ????????for?start?in?range(0,?n,?size?*?2):
          ????????????#?計(jì)算中點(diǎn)(第一個(gè)數(shù)組結(jié)束第二個(gè)數(shù)組開(kāi)始的地方)和終點(diǎn)(第二個(gè)數(shù)組結(jié)束的地方)
          ????????????midpoint?=?start?+?size?-?1
          ????????????end?=?min((start?+?size?*?2?-?1),?(n-1))

          ????????????#?合并兩個(gè)子數(shù)組
          ????????????#?left數(shù)組應(yīng)該從起點(diǎn)到中點(diǎn)+1,?right數(shù)組應(yīng)該從中點(diǎn)+1到終點(diǎn)+1
          ????????????merged_array?=?merge(
          ????????????????left=array[start:midpoint?+?1],
          ????????????????right=array[midpoint?+?1:end?+?1])

          ????????????#?最后,把合并的數(shù)組放回?cái)?shù)組
          ????????????array[start:start?+?len(merged_array)]?=?merged_array

          ????????#?每次迭代都應(yīng)該讓數(shù)據(jù)size加倍
          ????????size?*=?2

          ????return?array

          盡管實(shí)現(xiàn)比以前的算法要復(fù)雜一些,但是我們可以通過(guò)以下方式快速總結(jié)一下:

          之前已經(jīng)了解到,插入排序在小列表上速度很快,而Timsort則利用了這一優(yōu)勢(shì)。Timsort使用新引入的leftright參數(shù)在insertion_sort()對(duì)列表進(jìn)行適當(dāng)排序,而不必像merge sort和快排那樣創(chuàng)建新數(shù)組。

          定義min_run = 32作為值有兩個(gè)原因:

          1. 使用插入排序?qū)π?shù)組進(jìn)行排序非常快,并且min_run利用此特性的價(jià)值很小。使用min_run太大的值進(jìn)行初始化將無(wú)法達(dá)到使用插入排序的目的,并使算法變慢。

          2. 合并兩個(gè)平衡列表比合并不成比例的列表要有效得多。min_run在合并算法創(chuàng)建的所有不同運(yùn)行時(shí),選擇一個(gè)為2的冪的值可確保更好的性能。

          結(jié)合以上兩個(gè)條件,可以提供幾種min_run選擇。本教程中的實(shí)現(xiàn)min_run = 32是其中一種可能性。

          衡量Timsort的大O時(shí)間復(fù)雜性

          平均而言,Timsort的復(fù)雜度為O(n log 2 n),就像合并排序和快速排序一樣。對(duì)數(shù)部分來(lái)自執(zhí)行每個(gè)線性合并操作的運(yùn)行大小加倍。

          但是,Timsort在已排序或接近排序的列表上表現(xiàn)特別出色,從而導(dǎo)致了O(n)的最佳情況。在這種情況下,Timsort明顯勝過(guò)合并排序,并與快速排序的最佳情況相匹配。但是,對(duì)于Timsort來(lái)說(shuō),最糟糕的情況也是O(n log 2 n)。

          對(duì)Timsort測(cè)量運(yùn)行時(shí)間

          調(diào)用時(shí)間運(yùn)行測(cè)試函數(shù):

          if?__name__?==?"__main__":
          ?????#?生成包含“?ARRAY_LENGTH”個(gè)元素的數(shù)組,元素是介于0到999之間的隨機(jī)整數(shù)值
          ?????array?=?[randint(0,?1000)?for?i?in?range(ARRAY_LENGTH)]
          ?
          ?????#?使用排序算法的名稱和剛創(chuàng)建的數(shù)組調(diào)用該函數(shù)
          ????run_sorting_algorithm(algorithm="timsort",?array=array)

          執(zhí)行時(shí)間:

          $?python?sorting.py
          Algorithm:?timsort.?Minimum?execution?time:?0.5121690789999998

          0.51秒,Timsort比合并排序整整快0.1秒,即17%。它也比插入排序快11,000%!

          現(xiàn)在,嘗試使用這四種算法對(duì)已經(jīng)排序的列表進(jìn)行排序,然后看看會(huì)發(fā)生什么。

          if?__name__?==?"__main__":
          ????#?生成包含“?ARRAY_LENGTH”個(gè)元素的數(shù)組
          ????array?=?[i?for?i?in?range(ARRAY_LENGTH)]

          ????#?調(diào)用每個(gè)函數(shù)
          ????run_sorting_algorithm(algorithm="insertion_sort",?array=array)
          ????run_sorting_algorithm(algorithm="merge_sort",?array=array)
          ????run_sorting_algorithm(algorithm="quicksort",?array=array)
          ????run_sorting_algorithm(algorithm="timsort",?array=array)

          現(xiàn)在執(zhí)行腳本,那么所有算法都將運(yùn)行并輸出相應(yīng)的執(zhí)行時(shí)間:

          Algorithm:?insertion_sort.?Minimum?execution?time:?53.5485634999991
          Algorithm:?merge_sort.?Minimum?execution?time:?0.372304601
          Algorithm:?quicksort.?Minimum?execution?time:?0.24626494199999982
          Algorithm:?timsort.?Minimum?execution?time:?0.23350277099999994

          這次,Timsort的速度比合并排序快了37%,比快排快了5%,從而增強(qiáng)了利用已排序運(yùn)行的能力。

          請(qǐng)注意,Timsort如何從兩種算法中受益,這兩種算法單獨(dú)使用時(shí)速度要慢得多。Timsort的神奇之處在于將這些算法結(jié)合起來(lái)并發(fā)揮其優(yōu)勢(shì),以獲得令人印象深刻的結(jié)果。

          分析Timsort的優(yōu)勢(shì)和劣勢(shì)

          Timsort的主要缺點(diǎn)是它的復(fù)雜性。盡管實(shí)現(xiàn)了原始算法的非常簡(jiǎn)化的版本,但由于它同時(shí)依賴于insertion_sort()merge(),因此仍需要更多代碼。

          Timsort的優(yōu)點(diǎn)之一是其能夠以O(shè)(n log 2 n)的方式執(zhí)行預(yù)測(cè),而與輸入數(shù)組的結(jié)構(gòu)無(wú)關(guān)。與快排相比,快排可以降級(jí)為O(n 2)。對(duì)于小數(shù)組,Timsort也非常快,因?yàn)樵撍惴ㄗ兂闪藛蝹€(gè)插入排序。

          對(duì)于現(xiàn)實(shí)世界中的使用(通常對(duì)已經(jīng)具有某些預(yù)先存在的順序的數(shù)組進(jìn)行排序),Timsort是一個(gè)不錯(cuò)的選擇。它的適應(yīng)性使其成為排序任何長(zhǎng)度的數(shù)組的絕佳選擇。

          結(jié)論

          排序是任何Pythonista工具包中必不可少的工具。了解Python中不同的排序算法以及如何最大程度地發(fā)揮它們的潛力,你就可以實(shí)現(xiàn)更快,更高效的應(yīng)用程序和程序!


          參考:?

          1. https://realpython.com/sorting-algorithms-python/

          2. https://blog.csdn.net/weixin_38483589/article/details/84147376

          3. https://mp.weixin.qq.com/s/OHoe6TTX--Ys5G-5yR6juA

          作者:Python數(shù)據(jù)科學(xué)

          來(lái)源:Python數(shù)據(jù)科學(xué)



          _往期文章推薦_

          你“聽(tīng)”過(guò)這些經(jīng)典排序算法嗎?




          瀏覽 76
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产女人18毛片水18精 | 亚洲毛片在线 | 深夜福利 性色av | 亚洲色婷婷久久精品AV蜜桃 | 色偷偷伊人|