久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Python實(shí)現(xiàn)各種排序算法的代碼示例總結(jié)

 powerbaby 2016-01-28
這篇文章主要介紹了Python實(shí)現(xiàn)各種排序算法的代碼示例總結(jié),其實(shí)Python是非常好的算法入門學(xué)習(xí)時(shí)的配套高級(jí)語(yǔ)言,需要的朋友可以參考下

在Python實(shí)踐中,,我們往往遇到排序問(wèn)題,,比如在對(duì)搜索結(jié)果打分的排序(沒(méi)有排序就沒(méi)有Google等搜索引擎的存在),,當(dāng)然,這樣的例子數(shù)不勝數(shù),?!稊?shù)據(jù)結(jié)構(gòu)》也會(huì)花大量篇幅講解排序,。之前一段時(shí)間,,由于需要,,我復(fù)習(xí)了一下排序算法,,并用Python實(shí)現(xiàn)了各種排序算法,,放在這里作為參考。

最簡(jiǎn)單的排序有三種:插入排序,,選擇排序和冒泡排序,。這三種排序比較簡(jiǎn)單,它們的平均時(shí)間復(fù)雜度均為O(n^2),,在這里對(duì)原理就不加贅述了,。貼出來(lái)源代碼。

插入排序:

1
2
3
4
5
6
7
8
9
10
11
12
def insertion_sort(sort_list):
  iter_len = len(sort_list)
  if iter_len < 2:
    return sort_list
  for i in range(1, iter_len):
    key = sort_list[i]
    j = i - 1
    while j >= 0 and sort_list[j] > key:
      sort_list[j+1] = sort_list[j]
      j -= 1
    sort_list[j+1] = key
  return sort_list

冒泡排序:

1
2
3
4
5
6
7
8
9
def bubble_sort(sort_list):
  iter_len = len(sort_list)
  if iter_len < 2:
    return sort_list
  for i in range(iter_len-1):
    for j in range(iter_len-i-1):
      if sort_list[j] > sort_list[j+1]:
        sort_list[j], sort_list[j+1] = sort_list[j+1], sort_list[j]
  return sort_list

選擇排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def selection_sort(sort_list):
  iter_len = len(sort_list)
  if iter_len < 2:
    return sort_list
  for i in range(iter_len-1):
    smallest = sort_list[i]
    location = i
    for j in range(i, iter_len):
      if sort_list[j] < smallest:
        smallest = sort_list[j]
        location = j
    if i != location:
      sort_list[i], sort_list[location] = sort_list[location], sort_list[i]
  return sort_list

這里我們可以看到這樣的句子:

sort_list[i], sort_list[location] = sort_list[location], sort_list[i]
不了解Python的同學(xué)可能會(huì)覺(jué)得奇怪,,沒(méi)錯(cuò),,這是交換兩個(gè)數(shù)的做法,通常在其他語(yǔ)言中如果要交換a與b的值,,常常需要一個(gè)中間變量temp,,首先把a(bǔ)賦給temp,然后把b賦給a,,最后再把temp賦給b,。但是在python中你就可以這么寫:a, b = b, a,其實(shí)這是因?yàn)橘x值符號(hào)的左右兩邊都是元組(這里需要強(qiáng)調(diào)的是,,在python中,,元組其實(shí)是由逗號(hào)“,”來(lái)界定的,而不是括號(hào)),。

平均時(shí)間復(fù)雜度為O(nlogn)的算法有:歸并排序,,堆排序和快速排序,。

歸并排序。對(duì)于一個(gè)子序列,,分成兩份,,比較兩份的第一個(gè)元素,小者彈出,,然后重復(fù)這個(gè)過(guò)程,。對(duì)于待排序列,以中間值分成左右兩個(gè)序列,,然后對(duì)于各子序列再遞歸調(diào)用,。源代碼如下,由于有工具函數(shù),,所以寫成了callable的類:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class merge_sort(object):
  def _merge(self, alist, p, q, r):
    left = alist[p:q+1]
    right = alist[q+1:r+1]
    for i in range(p, r+1):
      if len(left) > 0 and len(right) > 0:
        if left[0] <= right[0]:
          alist[i] = left.pop(0)
        else:
          alist[i] = right.pop(0)
      elif len(right) == 0:
        alist[i] = left.pop(0)
      elif len(left) == 0:
        alist[i] = right.pop(0)
  def _merge_sort(self, alist, p, r):
    if p<r:
      q = int((p+r)/2)
      self._merge_sort(alist, p, q)
      self._merge_sort(alist, q+1, r)
      self._merge(alist, p, q, r)
  def __call__(self, sort_list):
    self._merge_sort(sort_list, 0, len(sort_list)-1)
    return sort_list

堆排序,,是建立在數(shù)據(jù)結(jié)構(gòu)——堆上的。關(guān)于堆的基本概念,、以及堆的存儲(chǔ)方式這里不作介紹,。這里用一個(gè)列表來(lái)存儲(chǔ)堆(和用數(shù)組存儲(chǔ)類似),對(duì)于處在i位置的元素,,2i+1位置上的是其左孩子,,2i+2是其右孩子,類似得可以得出該元素的父元素,。

首先我們寫一個(gè)函數(shù),,對(duì)于某個(gè)子樹,從根節(jié)點(diǎn)開始,,如果其值小于子節(jié)點(diǎn)的值,,就交換其值。用此方法來(lái)遞歸其子樹,。接著,,我們對(duì)于堆的所有非葉節(jié)點(diǎn),自下而上調(diào)用先前所述的函數(shù),,得到一個(gè)樹,,對(duì)于每個(gè)節(jié)點(diǎn)(非葉節(jié)點(diǎn)),它都大于其子節(jié)點(diǎn),。(其實(shí)這是建立最大堆的過(guò)程)在完成之后,,將列表的頭元素和尾元素調(diào)換順序,這樣列表的最后一位就是最大的數(shù),,接著在對(duì)列表的0到n-1部分再調(diào)用以上建立最大堆的過(guò)程,。最后得到堆排序完成的列表。以下是源代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class heap_sort(object):
  def _left(self, i):
    return 2*i+1
  def _right(self, i):
    return 2*i+2
  def _parent(self, i):
    if i%2==1:
      return int(i/2)
    else:
      return i/2-1
  def _max_heapify(self, alist, i, heap_size=None):
    length = len(alist)
    if heap_size is None:
      heap_size = length
    l = self._left(i)
    r = self._right(i)
    if l < heap_size and alist[l] > alist[i]:
      largest = l
    else:
      largest = i
    if r < heap_size and alist[r] > alist[largest]:
      largest = r
    if largest!=i:
      alist[i], alist[largest] = alist[largest], alist[i]
      self._max_heapify(alist, largest, heap_size)
  def _build_max_heap(self, alist):
    roop_end = int(len(alist)/2)
    for i in range(0, roop_end)[::-1]:
      self._max_heapify(alist, i)
  def __call__(self, sort_list):
    self._build_max_heap(sort_list)
    heap_size = len(sort_list)
    for i in range(1, len(sort_list))[::-1]:
      sort_list[0], sort_list[i] = sort_list[i], sort_list[0]
      heap_size -= 1
      self._max_heapify(sort_list, 0, heap_size)
    return sort_list

最后一種要說(shuō)明的交換排序算法(以上所有算法都為交換排序,原因是都需要通過(guò)兩兩比較交換順序)自然就是經(jīng)典的快速排序,。

先來(lái)講解一下原理,。首先要用到的是分區(qū)工具函數(shù)(partition),對(duì)于給定的列表(數(shù)組),,我們首先選擇基準(zhǔn)元素(這里我選擇最后一個(gè)元素),,通過(guò)比較,最后使得該元素的位置,,使得這個(gè)運(yùn)行結(jié)束的新列表(就地運(yùn)行)所有在基準(zhǔn)元素左邊的數(shù)都小于基準(zhǔn)元素,,而右邊的數(shù)都大于它。然后我們對(duì)于待排的列表,,用分區(qū)函數(shù)求得位置,,將列表分為左右兩個(gè)列表(理想情況下),然后對(duì)其遞歸調(diào)用分區(qū)函數(shù),,直到子序列的長(zhǎng)度小于等于1,。

下面是快速排序的源代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class quick_sort(object):
  def _partition(self, alist, p, r):
    i = p-1
    x = alist[r]
    for j in range(p, r):
      if alist[j] <= x:
        i += 1
        alist[i], alist[j] = alist[j], alist[i]
    alist[i+1], alist[r] = alist[r], alist[i+1]
    return i+1
  def _quicksort(self, alist, p, r):
    if p < r:
      q = self._partition(alist, p, r)
      self._quicksort(alist, p, q-1)
      self._quicksort(alist, q+1, r)
  def __call__(self, sort_list):
    self._quicksort(sort_list, 0, len(sort_list)-1)
    return sort_list

細(xì)心的朋友在這里可能會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,如果待排序列正好是順序的時(shí)候,,整個(gè)的遞歸將會(huì)達(dá)到最大遞歸深度(序列的長(zhǎng)度),。而實(shí)際上在操作的時(shí)候,當(dāng)列表長(zhǎng)度大于1000(理論值)的時(shí)候,,程序會(huì)中斷,,報(bào)超出最大遞歸深度的錯(cuò)誤(maximum recursion depth exceeded)。在查過(guò)資料后我們知道,,Python在默認(rèn)情況下,,最大遞歸深度為1000(理論值,其實(shí)真實(shí)情況下,,只有995左右,各個(gè)系統(tǒng)這個(gè)值的大小也不同),。這個(gè)問(wèn)題有兩種解決方案,,1)重新設(shè)置最大遞歸深度,采用以下方法設(shè)置:

1
2
import sys
sys.setrecursionlimit(99999)

2)第二種方法就是采用另外一個(gè)版本的分區(qū)函數(shù),,稱為隨機(jī)化分區(qū)函數(shù),。由于之前我們的選擇都是子序列的最后一個(gè)數(shù),因此對(duì)于特殊情況的健壯性就差了許多?,F(xiàn)在我們隨機(jī)從子序列選擇基準(zhǔn)元素,,這樣可以減少對(duì)特殊情況的差錯(cuò)率。新的randomize partition函數(shù)如下:

1
2
3
4
def _randomized_partition(self, alist, p, r):
  i = random.randint(p, r)
  alist[i], alist[r] = alist[r], alist[i]
  return self._partition(alist, p, r)

完整的randomize_quick_sort的代碼如下(這里我直接繼承之前的quick_sort類):

1
2
3
4
5
6
7
8
9
10
11
12
import random
class randomized_quick_sort(quick_sort):
  def _randomized_partition(self, alist, p, r):
    i = random.randint(p, r)
    alist[i], alist[r] = alist[r], alist[i]
    return self._partition(alist, p, r)
  def _quicksort(self, alist, p, r):
    if p<r:
      q = self._randomized_partition(alist, p, r)
      self._quicksort(alist, p, q-1)
      self._quicksort(alist, q+1, r)

關(guān)于快速排序的討論還沒(méi)有結(jié)束,。我們都知道,,Python是一門很優(yōu)雅的語(yǔ)言,而Python寫出來(lái)的代碼是相當(dāng)簡(jiǎn)潔而可讀性極強(qiáng)的。這里就介紹快排的另一種寫法,,只需要三行就能夠搞定,,但是又不失閱讀性。(當(dāng)然,,要看懂是需要一定的Python基礎(chǔ)的)代碼如下:

1
2
3
4
5
6
def quick_sort_2(sort_list):
  if len(sort_list)<=1:
    return sort_list
  return quick_sort_2([lt for lt in sort_list[1:] if lt<sort_list[0]]) + \
      sort_list[0:1] + \
      quick_sort_2([ge for ge in sort_list[1:] if ge>=sort_list[0]])

怎么樣看懂了吧,,這段代碼出自《Python cookbook 第二版》,這種寫法展示出了列表推導(dǎo)的強(qiáng)大表現(xiàn)力,。

對(duì)于比較排序算法,,我們知道,可以把所有可能出現(xiàn)的情況畫成二叉樹(決策樹模型),,對(duì)于n個(gè)長(zhǎng)度的列表,,其決策樹的高度為h,葉子節(jié)點(diǎn)就是這個(gè)列表亂序的全部可能性為n!,,而我們知道,,這個(gè)二叉樹的葉子節(jié)點(diǎn)不會(huì)超過(guò)2^h,所以有2^h>=n,!,,取對(duì)數(shù),可以知道,,h>=logn!,,這個(gè)是近似于O(nlogn)。也就是說(shuō)比較排序算法的最好性能就是O(nlgn),。

那有沒(méi)有線性時(shí)間,,也就是時(shí)間復(fù)雜度為O(n)的算法呢?答案是肯定的,。不過(guò)由于排序在實(shí)際應(yīng)用中算法其實(shí)是非常復(fù)雜的,。這里只是討論在一些特殊情形下的線性排序算法。特殊情形下的線性排序算法主要有計(jì)數(shù)排序,,桶排序和基數(shù)排序,。這里只簡(jiǎn)單說(shuō)一下計(jì)數(shù)排序。

計(jì)數(shù)排序是建立在對(duì)待排序列這樣的假設(shè)下:假設(shè)待排序列都是正整數(shù),。首先,,聲明一個(gè)新序列l(wèi)ist2,序列的長(zhǎng)度為待排序列中的最大數(shù),。遍歷待排序列,,對(duì)每個(gè)數(shù),設(shè)其大小為i,,list2[i]++,,這相當(dāng)于計(jì)數(shù)大小為i的數(shù)出現(xiàn)的次數(shù),。然后,申請(qǐng)一個(gè)list,,長(zhǎng)度等于待排序列的長(zhǎng)度(這個(gè)是輸出序列,,由此可以看出計(jì)數(shù)排序不是就地排序算法),倒序遍歷待排序列(倒排的原因是為了保持排序的穩(wěn)定性,,及大小相同的兩個(gè)數(shù)在排完序后位置不會(huì)調(diào)換),,假設(shè)當(dāng)前數(shù)大小為i,list[list2[i]-1] = i,,同時(shí)list2[i]自減1(這是因?yàn)檫@個(gè)大小的數(shù)已經(jīng)輸出一個(gè),,所以大小要自減)。于是,,計(jì)數(shù)排序的源代碼如下,。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class counting_sort(object):
  def _counting_sort(self, alist, k):
    alist3 = [0 for i in range(k)]
    alist2 = [0 for i in range(len(alist))]
    for j in alist:
      alist3[j] += 1
    for i in range(1, k):
      alist3[i] = alist3[i-1] + alist3[i]
    for l in alist[::-1]:
      alist2[alist3[l]-1] = l
      alist3[l] -= 1
    return alist2
  def __call__(self, sort_list, k=None):
    if k is None:
      import heapq
      k = heapq.nlargest(1, sort_list)[0] + 1
    return self._counting_sort(sort_list, k)

各種排序算法介紹完(以上的代碼都通過(guò)了我寫的單元測(cè)試),我們?cè)倩氐絇ython這個(gè)主題上來(lái),。其實(shí)Python從最早的版本開始,,多次更換內(nèi)置的排序算法。從開始使用C庫(kù)提供的qsort例程(這個(gè)方法有相當(dāng)多的問(wèn)題),,到后來(lái)自己開始實(shí)現(xiàn)自己的算法,,包括2.3版本以前的抽樣排序和折半插入排序的混合體,以及最新的適應(yīng)性的排序算法,,代碼也由C語(yǔ)言的800行到1200行,,以至于更多。從這些我們可以知道,,在實(shí)際生產(chǎn)環(huán)境中,,使用經(jīng)典的排序算法是不切實(shí)際的,它們僅僅能做學(xué)習(xí)研究之用,。而在實(shí)踐中,,更推薦的做法應(yīng)該遵循以下兩點(diǎn):

當(dāng)需要排序的時(shí)候,盡量設(shè)法使用內(nèi)建Python列表的sort方法,。
當(dāng)需要搜索的時(shí)候,,盡量設(shè)法使用內(nèi)建的字典。
我寫了測(cè)試函數(shù),,來(lái)比較內(nèi)置的sort方法相比于以上方法的優(yōu)越性。測(cè)試序列長(zhǎng)度為5000,,每個(gè)函數(shù)測(cè)試3次取平均值,,可以得到以下的測(cè)試結(jié)果:

20151211165042473.png (530×165)

可以看出,Python內(nèi)置函數(shù)是有很大的優(yōu)勢(shì)的,。因此在實(shí)際應(yīng)用時(shí),,我們應(yīng)該盡量使用內(nèi)置的sort方法。

由此,我們引出另外一個(gè)問(wèn)題,。怎么樣判斷一個(gè)序列中是否有重復(fù)元素,,如果有返回True,沒(méi)有返回False,。有人會(huì)說(shuō),,這不很簡(jiǎn)單么,直接寫兩個(gè)嵌套的迭代,,遍歷就是了,。代碼寫下來(lái)應(yīng)該是這樣。

1
2
3
4
5
6
7
def normal_find_same(alist):
  length = len(alist)
  for i in range(length):
    for j in range(i+1, length):
      if alist[i] == alist[j]:
        return True
  return False

這種方法的代價(jià)是非常大的(平均時(shí)間復(fù)雜度是O(n^2),,當(dāng)列表中沒(méi)有重復(fù)元素的時(shí)候會(huì)達(dá)到最壞情況),,由之前的經(jīng)驗(yàn),我們可以想到,,利用內(nèi)置sort方法極快的經(jīng)驗(yàn),,我們可以這么做:首先將列表排序,然后遍歷一遍,,看是否有重復(fù)元素,。包括完整的測(cè)試代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time
import random
def record_time(func, alist):
  start = time.time()
  func(alist)
  end = time.time()
  return end - start
def quick_find_same(alist):
  alist.sort()
  length = len(alist)
  for i in range(length-1):
    if alist[i] == alist[i+1]:
      return True
  return False
if __name__ == "__main__":
  methods = (normal_find_same, quick_find_same)
  alist = range(5000)
  random.shuffle(alist)
  for m in methods:
    print 'The method %s spends %s' % (m.__name__, record_time(m, alist))

運(yùn)行以后我的數(shù)據(jù)是,對(duì)于5000長(zhǎng)度,,沒(méi)有重復(fù)元素的列表,,普通方法需要花費(fèi)大約1.205秒,而快速查找法花費(fèi)只有0.003秒,。這就是排序在實(shí)際應(yīng)用中的一個(gè)例子,。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多