欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

Python 搜索、排序、復(fù)雜度分析

系統(tǒng) 1769 0

概述

算法是計(jì)算機(jī)程序的一個(gè)基本的構(gòu)建模塊。評(píng)價(jià)算法質(zhì)量的最基本的標(biāo)準(zhǔn)是正確性,另一個(gè)重要的標(biāo)準(zhǔn)是運(yùn)行時(shí)間性能。當(dāng)在一臺(tái)真實(shí)、資源有限的計(jì)算機(jī)上運(yùn)行一個(gè)算法的時(shí)候,經(jīng)濟(jì)性的考慮就有了用武之地,這樣一個(gè)過(guò)程會(huì)消耗兩種資源:處理時(shí)間和空間或內(nèi)存。

統(tǒng)計(jì)指令

用于估算算法性能的另一種技術(shù)是統(tǒng)計(jì)對(duì)不同的問(wèn)題規(guī)模所要執(zhí)行的指令的數(shù)目。不管算法在什么平臺(tái)上運(yùn)行,這個(gè)統(tǒng)計(jì)數(shù)字對(duì)于算法所要執(zhí)行的抽象的工作量給出了一個(gè)很好的預(yù)計(jì)。然而要記住,當(dāng)統(tǒng)計(jì)指令的時(shí)候, 所統(tǒng)計(jì)的是用于編寫算法的較高級(jí)代碼中的指令數(shù)目,而不是執(zhí)行機(jī)器語(yǔ)言的程序中的指令數(shù)目

當(dāng)以這種方式分析算法的時(shí)候,你需要區(qū)分兩種指令:

(1)不管問(wèn)題規(guī)模多大,都執(zhí)行相同次數(shù)的指令

(2)根據(jù)問(wèn)題的規(guī)模,執(zhí)行不同次數(shù)的指令

現(xiàn)在,我們先忽略第一類指令,因?yàn)樗鼈兊挠绊懖⒉伙@著,第二類指令通常在循環(huán)或遞歸函數(shù)中可以找到。


復(fù)雜度分析

復(fù)雜度的階

Python 搜索、排序、復(fù)雜度分析_第1張圖片

大O表示法

一個(gè)算法幾乎不太可能只是嚴(yán)格執(zhí)行等于n、n^2或k^n的那么多次的操作,算法通常在循環(huán)體內(nèi)、循環(huán)體之前或之后還要執(zhí)行其他的工作。例如,我們說(shuō)算法執(zhí)行2n+3或2n^2操作,可能會(huì)更加精確。

一個(gè)算法的總工作量,通常是多項(xiàng)式中的數(shù)項(xiàng)之和,當(dāng)用多項(xiàng)式來(lái)表示工作量的時(shí)候,有一個(gè)項(xiàng)是 主項(xiàng) ,隨著n變得越來(lái)越大,主項(xiàng)也變得很大,以至于你可以忽略其他的項(xiàng)所表示的工作量。例如,在多項(xiàng)式(1/2)n^2-(1/2)n中,我們主要關(guān)注平方項(xiàng)(1/2)n^2,實(shí)際上忽略掉線性項(xiàng)(1/2)n,還可以刪除掉系數(shù)1/2,因?yàn)?1/2)n^2和n^2之間的差別不會(huì)隨著n的增加而改變。這種類型的分析有時(shí)候叫做 漸進(jìn)分析 (asymptotic analysis)。


搜索算法

搜索最小值

Python的min函數(shù)返回列表中的最小的項(xiàng)。為了研究這個(gè)算法的復(fù)雜度,我們開(kāi)發(fā)了一個(gè)替代的版本,它返回了最小項(xiàng)的索引。這個(gè)算法假設(shè)列表不為空,并且其中的項(xiàng)的順序是任意的,該算法首先將列表中的第1個(gè)位置當(dāng)作最小項(xiàng),然后,向右搜索以找到一個(gè)更小的項(xiàng),如果找到了,將最小項(xiàng)的位置重新設(shè)置為當(dāng)前位置,當(dāng)這個(gè)算法到達(dá)列表末尾的時(shí)候,它就返回最小項(xiàng)的位置。

            
              class indexOfMin():
    def indexOfMin(self,lyst):
        """Return the index of the minmum item."""
        minIndex = 0
        currentIndex = 1
        while currentIndex < len(lyst):
            if lyst[currentIndex] < lyst[minIndex]:
                minIndex = currentIndex
            currentIndex += 1
        return minIndex

if __name__ == "__main__":
    a = indexOfMin()
    lyst = [3,5,7,1,9,10]
    print(a.indexOfMin(lyst))
            
          

對(duì)于大小為n的列表,該算法必須進(jìn)行n-1次比較,因此,算法的復(fù)雜度為O(n)。

順序搜索一個(gè)列表

Python的in運(yùn)算符作為list類中名為_(kāi)_contains__的一個(gè)方法而實(shí)現(xiàn)。該方法在列表(任意排列的項(xiàng))中搜索一個(gè)特定的項(xiàng)(叫做目標(biāo)項(xiàng))。在這樣的一個(gè)列表中搜索一個(gè)目標(biāo)項(xiàng)的唯一的方法是,從第1個(gè)位置的項(xiàng)開(kāi)始,將其與目標(biāo)項(xiàng)進(jìn)行比較,如果這兩個(gè)項(xiàng)相等,該方法返回一個(gè)True。否則,該方法移動(dòng)到下一個(gè)位置并且將其項(xiàng)與目標(biāo)項(xiàng)進(jìn)行比較。如果該方法到達(dá)了最后一個(gè)位置,卻仍然沒(méi)有找到目標(biāo)項(xiàng),它就返回False。這種搜索叫做 順序搜索 (sequential search)或 線性搜索 (linear search)。一個(gè)更為有用的順序搜索函數(shù),應(yīng)該返回它所找到的目標(biāo)項(xiàng)的索引,或者如果沒(méi)有找到目標(biāo)項(xiàng)的話,返回-1。

            
              class Search():
    def sequentialSearch(self,target,lyst):
        """Returns the position of the target item if found, or -1 otherwise."""        
        position = 0
        while position < len(lyst):
            if target == lyst[position]:
                return position
            position += 1
        return -1

if __name__ == '__main__':
    a = Search()
    target = 3
    lyst = [2,4,5,1,3,7]
    print(a.sequentialSearch(target,lyst))
            
          

最好情況、最壞情況和平均情況的性能

有些算法的性能取決于所處數(shù)據(jù)的放置方式。順序搜索算法在查找一個(gè)目標(biāo)項(xiàng)的時(shí)候,在列表的開(kāi)頭處所做的工作比在列表的末尾所做的工作要少。對(duì)于這樣的算法,我們可以確定其最好情況的性能、最壞情況的性能和平均情況的性能。 通常一般考慮的是最壞情況的性能和平均情況的性能

順序搜索的分析要考慮如下3種情況:

(1)在最壞情況下,目標(biāo)項(xiàng)位于列表的末尾,或者根本就不在列表之中。那么,算法必須訪問(wèn)每一個(gè)項(xiàng),并且對(duì)大小為n的列表要執(zhí)行n次迭代。因此,順序搜索的最壞情況的復(fù)雜度為O(n)。

(2)在最好的情況下,算法只進(jìn)行了1次迭代就在第1個(gè)位置找到目標(biāo)項(xiàng),復(fù)雜度為O(1)。

(3)要確定平均情況,把在每一個(gè)可能的位置找到目標(biāo)項(xiàng)所需的迭代次數(shù)相加,并且 總和除以n 。因此,算法執(zhí)行了(n+1)/2次迭代,對(duì)于很大的n,常數(shù)因子2的作用并不大,因此,平均情況下的復(fù)雜度仍然為O(n)。

顯然,順序搜索的最好情況的性能是很少見(jiàn)的,而平均情況和最壞情況的性能則基本相同。


有序列表的二叉搜索

對(duì)于那些沒(méi)有按照特定的順序排列的數(shù)據(jù),順序搜索是必要的。 當(dāng)搜索經(jīng)過(guò)排序的數(shù)據(jù)的時(shí)候,可以使用二叉搜索(又稱為二分搜索)。

現(xiàn)在來(lái)考慮Python實(shí)現(xiàn)二叉搜索的一個(gè)示例。首先,假設(shè)列表中的項(xiàng)都是按照升序排列的,搜索算法直接找到列表的中間位置,并且將該位置的項(xiàng)和目標(biāo)項(xiàng)進(jìn)行比較。如果它們是一致的,算法返回該位置。否則,如果目標(biāo)項(xiàng)小于當(dāng)前項(xiàng),算法搜索列表中間位置以前的部分。反之,搜索中間以后的部分。

            
              def binarySearch(target, lyst, profiler):
    """Returns the position of the target item if found,
    or -1 otherwise."""
    left = 0
    right = len(lyst) - 1
    while left <= right:
        profiler.comparison()
        midpoint = (left + right) // 2
        if target == lyst[midpoint]:
            return midpoint
        elif target < lyst[midpoint]:
            right = midpoint - 1
        else:
            left = midpoint + 1
    return -1
            
          

在最壞的情況下,循環(huán)要運(yùn)行列表的大小除以2直到得到的商為1的次數(shù),對(duì)于大小為n的列表,實(shí)際上執(zhí)行了n/2/2.../2的連續(xù)除法,直到結(jié)果為1。假設(shè)k是用n除以2的次數(shù),要求解k,讓n/(2^k)=1就行了,那么n=2^k,k=log2n,因此,二叉搜索的最壞情況的復(fù)雜度為O(log2n)。

Python 搜索、排序、復(fù)雜度分析_第2張圖片


比較數(shù)據(jù)項(xiàng)

二叉搜索和搜索最小項(xiàng),都是假設(shè)列表中的項(xiàng)是可以相互比較的。在Python中,這意味著這些項(xiàng)具有相同的類型,并且它們都識(shí)別比較運(yùn)算符==、<和>。幾個(gè)內(nèi)建的Python類型的對(duì)象,例如數(shù)字、字符串和列表,都可以使用這些運(yùn)算符來(lái)比較。

為了允許算法對(duì)一個(gè) 新對(duì)象的類 使用比較運(yùn)算符==、<和>,程序員應(yīng)該在該類中定義__eq__、__lt__和__gt__方法。__lt__方法的方法頭如下所示:

            
              def __lt__(self,other):
            
          

如果self小于other,該方法返回True,否則,它返回False。 比較對(duì)象的標(biāo)準(zhǔn),取決于它們內(nèi)部的結(jié)構(gòu),以及應(yīng)該以何種方式對(duì)其排 序。

例如,SavingAccount對(duì)象可能包含3個(gè)數(shù)據(jù)字段,一個(gè)用于名稱,一個(gè)用于PIN,還有一個(gè)用余額。假設(shè)賬戶應(yīng)該按照名稱的字母順序來(lái)排序,那么,需要對(duì)__lt__方法按照如下的方式來(lái)實(shí)現(xiàn):

            
              class SavingAccount(object):
    """This class represents a saving account with the owner's name, PIN, and balance."""
    def __init__(self,name,pin,balance = 0.0):
        self._name = name
        self._pin = pin
        self._balance = balance

    def __lt__(self,other):
        return self._name < other._name
            
          

注意:__lt__方法對(duì)兩個(gè)賬戶對(duì)象的_name字段調(diào)用了<運(yùn)算符(名稱是字符串),并且字符串類型已經(jīng)包含了一個(gè)__lt__方法。當(dāng)對(duì)字符串應(yīng)用<運(yùn)算符的時(shí)候,Python自動(dòng)運(yùn)行__lt__方法。就像是在調(diào)用str函數(shù)時(shí)運(yùn)行str方法一樣。


基本排序算法

這里介紹的幾個(gè)算法很容易編寫,但是效率不高,下一節(jié)介紹的算法都很難編寫,但是更為高效(這是一種常見(jiàn)的取舍)。每個(gè)Python排序函數(shù)都是在整數(shù)的一個(gè)列表上進(jìn)行操作的,并且都會(huì)使用一個(gè)swap函數(shù)來(lái)交換列表中的兩項(xiàng)的位置。

swap函數(shù)的代碼如下:

            
              def swap(lyst,i,j):
    """Exchanges the items at position i and j."""
    temp = lyst[i]
    lyst[i] = lyst[j]
    lyst[j] = temp
            
          


選擇排序

最簡(jiǎn)單的策略就是搜索整個(gè)列表,找到最小項(xiàng)的位置。 如果該位置不是列表的第1個(gè)位置,算法就會(huì)交換在這兩個(gè)位置的項(xiàng),然后,算法回到第2個(gè)位置并且重復(fù)這個(gè)過(guò)程,如果必要的話,將最小項(xiàng)和第2個(gè)位置的項(xiàng)進(jìn)行交換。當(dāng)算法到達(dá)整個(gè)過(guò)程的最后一個(gè)位置,列表就是排序好的了。這個(gè)算法叫做選擇排序(selection sort)。

Python 搜索、排序、復(fù)雜度分析_第3張圖片

            
              def selectionSort(lyst):
    i = 0
    while i < len(lyst) - 1:
        minIndex = 1
        j = i + 1
        while j < len(lyst):
            if lyst[j] < lyst[minIndex]:
                minIndex = j
            j += 1
        if minIndex != i:
            swap(lyst,minIndex,i)
        i += 1
            
          

這個(gè)函數(shù)包含了一個(gè)嵌套的循環(huán)。總的比較次數(shù)為(1/2)(n^2)-(1/2)n,對(duì)于較大的n,我們可以選擇影響很大的項(xiàng),而忽略常數(shù)項(xiàng)。因此,選擇排序在各種情況下的復(fù)雜度為 O(n^2)


冒泡排序

另一種排序方法相對(duì)容易理解和編碼,它叫做冒泡排序(bubble sort)。其策略是從列表的開(kāi)頭處開(kāi)始,并且比較一對(duì)數(shù)據(jù)項(xiàng),直到移動(dòng)到列表的末尾,每當(dāng)成對(duì)的兩項(xiàng)之間的順序不正確的時(shí)候,算法就交換其位置。 這個(gè)過(guò)程的效果就是將最大的項(xiàng)以冒泡的方法排到列表的末尾。 然后,算法從列表開(kāi)頭到倒數(shù)第2個(gè)列表項(xiàng)重復(fù)這一個(gè)過(guò)程。依次類推,直到該算法從列表的最后一項(xiàng)開(kāi)始執(zhí)行。此時(shí),列表是已經(jīng)排序好的。

Python 搜索、排序、復(fù)雜度分析_第4張圖片

            
              def bubbleSort(lyst):
    n = len(lyst)
    while n > 1:
        i = 1
        while i < n:
            if lyst[i] < lyst[i-1]:
                swap(lyst,i,i-1)
            i += 1
        n -= 1
            
          

和選擇排序一樣,冒泡排序也有一個(gè)嵌套的循環(huán),對(duì)于大小為n的列表,內(nèi)部的循環(huán)執(zhí)行(1/2)(n^2)-(1/2)n次,因此,冒泡排序的復(fù)雜度是O(n^2)。


插入排序

(1)在第i輪通過(guò)列表的時(shí)候(其中i的范圍從1到n-1),第i個(gè)項(xiàng)應(yīng)該插入到列表的前i個(gè)項(xiàng)之中的正確位置。

(2)在第i輪之后,前i個(gè)項(xiàng)應(yīng)該是排好序的。

(3)這個(gè)過(guò)程類似于人們排列手中的撲克牌的順序,也就是說(shuō),如果你按照順序放好了前i-1張牌,抓取了第i張牌,并且將其與手中的這些牌進(jìn)行比較,直到找到其合適的位置。

(4)插入排序包括兩個(gè)循環(huán)。外圍的循環(huán)遍歷從1到n-1的位置。對(duì)于這個(gè)循環(huán)中的每一個(gè)位置i,我們都保存該項(xiàng)并且從位置i-1開(kāi)始內(nèi)部循環(huán)。

            
              def insertSort(lyst):
    i = 1
    while i < len(lyst):
        itemInsert = lyst[i]
        j = i - 1
        while j >= 1:
            if itemToInsert < lyst[j]:
                lyst[j + 1] = lyst[j]
                j -= 1
            else:
                break
        lyst[j + 1] = itemToInsert
        i += 1

            
          

插入排序的最壞情況下的復(fù)雜度是O(n^2)。列表中排好序的項(xiàng)越多,插入排序的效果越好。在最好情況下,列表本身是有序的,那么插入排序的復(fù)雜度是線性的。然而,在平均情況下,插入排序的復(fù)雜度仍然是二次方階的。


更快的排序

到目前為止,我們介紹的3種排序算法都擁有O(n^2)的運(yùn)行時(shí)間。然而,我們可以利用一些復(fù)雜度為O(nlogn)的更好的算法。這些更好的算法都采用了 分而治之(divide-and-conquer) 的策略,也就是說(shuō),每個(gè)算法都找到了一種方法,將列表分解為更小的子列表,隨后,這些子列表再遞歸的排序。理想情況下,如果這些子列表的復(fù)雜度為log(n),而重新排列每一個(gè)子列表中的數(shù)據(jù)所需的工作量為n,那么,這樣的排序算法總的復(fù)雜度就是O(nlogn)。

快速排序

快速排序所使用的策略可以概括如下:

(1)首先,從列表的中點(diǎn)位置選取一項(xiàng)。這一項(xiàng)叫作 基準(zhǔn)點(diǎn)

(2)將列表中的項(xiàng)分區(qū),以便小于基準(zhǔn)點(diǎn)的所有項(xiàng)都移動(dòng)到基準(zhǔn)點(diǎn)的左邊,而剩下的項(xiàng)都移動(dòng)到基準(zhǔn)點(diǎn)的右邊。根據(jù)相關(guān)的實(shí)際項(xiàng),基準(zhǔn)點(diǎn)自身的最終位置也是變化的。但是,不管基準(zhǔn)點(diǎn)最終位于何處,這個(gè)位置都是它在完全排序的列表中的最終位置。

(3) 分而治之。對(duì)于在基準(zhǔn)點(diǎn)分割列表而形成的子列表,遞歸地重復(fù)應(yīng)用該過(guò)程。一個(gè)子列表包含了基準(zhǔn)點(diǎn)左邊的所有的項(xiàng)(現(xiàn)在是較小的項(xiàng)),另一個(gè)子列表包含了基準(zhǔn)點(diǎn)右邊的所有的項(xiàng)(現(xiàn)在是較大的項(xiàng))。

(4)每次遇到小于2個(gè)項(xiàng)的一個(gè)子列表,就結(jié)束這個(gè)過(guò)程。

1.分割

從程序員的角度來(lái)看,該算法最復(fù)雜的部分就是對(duì)子列表中的項(xiàng)進(jìn)行分割的操作。步驟如下:

(1)將基準(zhǔn)點(diǎn)和子列表中的最后一項(xiàng)交換。

(2)在已知小于基準(zhǔn)點(diǎn)的項(xiàng)和剩余的項(xiàng)之間建立一個(gè)邊界。一開(kāi)始,這個(gè)邊界就放在第一項(xiàng)之前。

(3)從子列表的第一項(xiàng)開(kāi)始掃描整個(gè)子列表,每次遇到小于基準(zhǔn)點(diǎn)的項(xiàng),就將其與邊界之后的第一項(xiàng)進(jìn)行交換,并且邊界向后移動(dòng)。

(4)將基準(zhǔn)點(diǎn)和邊界之后的第一項(xiàng)進(jìn)行交換,從而完成這個(gè)過(guò)程。

例子:

Python 搜索、排序、復(fù)雜度分析_第6張圖片

2.快速排序的復(fù)雜度分析

在第1次分割操作中,從列表頭部到其尾部掃描了所有的項(xiàng)。因此, 這個(gè)操作過(guò)程的工作量和列表的長(zhǎng)度n成正比

在這次分割之后的工作量,和左邊的子列表的長(zhǎng)度加上右邊的子列表的長(zhǎng)度(加在一起是n-1)成正比。當(dāng)再次分割這些子列表的時(shí)候,有了4個(gè)子列表,它們組合在一起的長(zhǎng)度近似為n, 因此,組合的工作量還是和n成正比的 。隨著將列表分割為更多的子列表,總的工作量還是和n成正比。

然后我們需要確定 對(duì)列表分割多少次 如果每一次新的子列表之間的分割線都盡可能地靠近當(dāng)前子列表的中央,大概經(jīng)過(guò)log2n步之后會(huì)得到一個(gè)單個(gè)的元素 。因此,這個(gè)算法在最好的情況下的性能為 O(nlog2n)

對(duì)于最壞的情況,考慮一個(gè)列表已經(jīng)排好序的情況。如果所選擇的基準(zhǔn)點(diǎn)元素是第1個(gè)元素,那么在第1次分割的時(shí)候,基準(zhǔn)點(diǎn)的右邊有n-1個(gè)元素,在第2次分割的時(shí)候,基準(zhǔn)點(diǎn)的右邊有n-2個(gè)元素,依次類推,因此,在最壞的情況下,快速排序算法的性能是O(n^2)。

如果將快速排序?qū)崿F(xiàn)為一個(gè)遞歸算法,你的分析還必須考慮到調(diào)用棧所使用的內(nèi)存。 每一次遞歸調(diào)用都需要一個(gè)固定大小的內(nèi)存用于棧 ,并且每一次分割之后有兩次遞歸調(diào)用。因此, 在最好的情況下,內(nèi)存使用是O(log2n),在最壞的情況下,內(nèi)存使用是O(n)

3.快速排序的實(shí)現(xiàn)

快速排序使用遞歸算法更容易編碼。

            
              def quicksort(lyst):
    quicksortHelper(lyst, 0, len(lyst) - 1)

def quicksortHelper(lyst, left, right):
    if left < right:
        pivotLocation = partition(lyst, left, right)
        quicksortHelper(lyst, left, pivotLocation - 1)
        quicksortHelper(lyst, pivotLocation + 1, right)

def partition(lyst, left, right):
    # Find the pivot and exchange it with the last item
    middle = (left + right) // 2
    pivot = lyst[middle]
    lyst[middle] = lyst[right]
    lyst[right] = pivot
    # Set boundary point to first position
    boundary = left
    # Move items less than pivot to the left
    for index in range(left, right):
        if lyst[index] < pivot:
            swap(lyst, index, boundary)
            boundary += 1
    # Exchange the pivot item and the boundary item
    swap (lyst, right, boundary)
    return boundary

def swap(lyst, i, j):
    """Exchanges the items at positions i and j."""
    # You could say lyst[i], lyst[j] = lyst[j], lyst[i]
    # but the following code shows what is really going on
    temp = lyst[i]
    lyst[i] = lyst[j]
    lyst[j] = temp


import random


def main(size = 20, sort = quicksort):
    lyst = []
    for count in range(size):
        lyst.append(random.randint(1, size + 1))
    print(lyst)
    sort(lyst)
    print(lyst)

if __name__ == "__main__":
    main() 
            
          


合并排序

另一種名為合并排序(merge sort,又稱為歸并排序)的算法利用了 遞歸、分而治之 的策略來(lái)突破O(n^2)的障礙。如下是該算法的一個(gè)非正式的概述:

(1)計(jì)算一個(gè)列表的中間位置,并且遞歸地排序其左邊和右邊的子列表(分而治之)。

(2)將兩個(gè)排好序的子列表重新合并為單個(gè)的排好序的列表。

(3)當(dāng)子列表不再能夠劃分的時(shí)候,停止這個(gè)過(guò)程。

有3個(gè)Python函數(shù)在這個(gè)頂層的設(shè)計(jì)策略中協(xié)作:

(1)mergeSort——用戶調(diào)用的函數(shù)。

(2)mergeSortHelper——一個(gè)輔助函數(shù),它隱藏了遞歸調(diào)用所需要的額外參數(shù)。

(3)Merge——實(shí)現(xiàn)合并過(guò)程的一個(gè)函數(shù)。

1.實(shí)現(xiàn)合并過(guò)程

合并的過(guò)程使用了和列表相同大小的一個(gè)數(shù)組,這個(gè)數(shù)組名為copyBuffer,為了避免每次調(diào)用merge的時(shí)候?yàn)閏opyBuffer分配和釋放內(nèi)存的開(kāi)銷,只在mergeSort中分配一次該緩沖區(qū),并且在后續(xù)將其作為一個(gè)參數(shù)傳遞給mergeSortHelper和merge,每次調(diào)用mergeSortHelper的時(shí)候,都需要知道它所操作的子列表的邊界。這些邊界通過(guò)另外的參數(shù)low和high來(lái)提供,如下是mergeSort的代碼:

            
              from arrays import Array

def mergeSort(lyst):
    # lyst: list being sorted
    # copyBuffer: temporary space needed during merge
    copyBuffer = Array(len(lyst))
    mergeSortHelper(lyst,copyBuffer,0,len(lyst)-1)
            
          

在檢查到至少有兩項(xiàng)的一個(gè)子列表已經(jīng)傳遞給它之后,mergeSortHelper函數(shù)計(jì)算了這個(gè)子列表的中點(diǎn), 遞歸地對(duì)中點(diǎn)以上和中點(diǎn)以下的部分進(jìn)行排序,并且調(diào)用merge來(lái)合并結(jié)果 。如下是mergeSortHelper的代碼:

            
              def mergeSortHelper(lyst,copyBuffer,low,high):
    # lyst: list being sorted
    # copyBuffer: temp space needed during merge
    # low,high : bounds of sublist
    # middle: midpoint of sublist
    if low < high:
        middle = (low+high)//2
        mergeSortHelper(lyst,copyBuffer,low,middle)
        mergeSortHelper(lyst,copyBuffer,middle+1,high)
        merge(lyst,copyBuffer,low,middle,high)
            
          

下圖展示了在對(duì)擁有8項(xiàng)的一個(gè)列表開(kāi)始遞歸調(diào)用mergeSortHelper的過(guò)程中所生成的子列表。注意,在這個(gè)例子中,子列表在每一個(gè)層級(jí)都是平均劃分的,在第k層,有2^k個(gè)子列表需要合并。 如果最初列表的長(zhǎng)度不是2的冪,那么,無(wú)法在每一個(gè)層級(jí)都做到完全平均的劃分,并且最后的層級(jí)將不會(huì)擁有足夠的子列表

Python 搜索、排序、復(fù)雜度分析_第7張圖片

Python 搜索、排序、復(fù)雜度分析_第8張圖片

最后,下面是merge函數(shù)的代碼:

            
              def merge(lyst,copyBuffer,low,middle,high):
    # Initialize i1 and i2 to the first items in each sublist
    i1 = low
    i2 = middle + 1

    # Interleave items from the sublists into the copyBuffer in such a way that order is maintained.
    for i in range(low,high+1):
        if i1 > middle:
            copyBuffer[i] = lyst[i2] # 列表1已經(jīng)用完
            i2 += 1
        elif i2 > high:
            copyBuffer[i] = lyst[i1] # 列表2已經(jīng)用完
            i1 += 1
        elif lyst[i1] < lyst[i2]:
            copyBuffer[i] = lyst[i1] # 將小的數(shù)放上去
            i1 += 1
        else:
            copyBuffer[i] = lyst[i2] # 將小的數(shù)放上去
            i2 += 1

    for i in range(low,high+1):
        lyst[i] = copyBuffer[i] # 排序完的copyBuffer返回給lyst
            
          

merge函數(shù)將兩個(gè)排好序的子列表合并到一個(gè)大的排好序的子列表中。第1個(gè)子列表在low和middle之間,第2個(gè)子列表在middle+1和high之間,這個(gè)過(guò)程包含步驟:

(1)將索引指針設(shè)置為每個(gè)子列表的第1項(xiàng),這分別是low和middle+1的位置。

(2)從每個(gè)子列表的第1項(xiàng)開(kāi)始,重復(fù)地比較各項(xiàng)。將較小的項(xiàng)從其子列表中復(fù)制到緩存中去,并且繼續(xù)處理子列表中的下一項(xiàng)。重復(fù)這個(gè)過(guò)程,直到兩個(gè)子列表中的所有項(xiàng)都已經(jīng)復(fù)制過(guò)了。 如果先到達(dá)了其中一個(gè)子列表的末尾,通過(guò)從另一個(gè)子列表復(fù)制剩余的項(xiàng),從而結(jié)束這個(gè)過(guò)程

(3)將copyBuffer中l(wèi)ow到high之間的部分,復(fù)制回lyst對(duì)應(yīng)的位置。

2.合并排序的復(fù)雜度分析

合并排序的運(yùn)行時(shí)間由兩條for語(yǔ)句主導(dǎo),其中每一條都循環(huán)(high-low+1)次,結(jié)果,該函數(shù)的運(yùn)行時(shí)間是O(high-low),在一個(gè)層的所有合并花費(fèi)的時(shí)間是O(n)。由于mergeSortHelper在每一層都是盡可能平均地分割子列表,層級(jí)數(shù)是O(logn),并且在所有情況下,該函數(shù)的最大運(yùn)行時(shí)間是O(nlogn)。

根據(jù)列表的大小,合并排序有兩個(gè)空間需求,首先,在支持遞歸調(diào)用的調(diào)用棧上,需要O(logn)的空間,其次,復(fù)制緩存需要使用O(n)的空間。


指數(shù)算法:遞歸式的Fibonacci

遞歸實(shí)現(xiàn)

如果我們采用遞歸的Fibonacci函數(shù)來(lái)實(shí)現(xiàn),調(diào)用的次數(shù)似乎比問(wèn)題規(guī)模的平方增長(zhǎng)的還要快,具體代碼如下:

            
              from counter import Counter

def fib(n, counter):
    """Count the number of calls of the Fibonacci
    function."""
    counter.increment()
    if n < 3:
        return 1
    else:
        return fib(n - 1, counter) + fib(n - 2, counter)

problemSize = 2
print("%12s%15s" % ("Problem Size", "Calls"))
for count in range(5):
    counter = Counter()

    # The start of the algorithm
    fib(problemSize, counter)
    # The end of the algorithm
    
    print("%12d%15s" % (problemSize, counter))
    problemSize *= 2
            
          

counter函數(shù)如下:

            
              class Counter(object):
    """Models a counter."""
    
    # Class variable
    instances = 0
    
   #Constructor
    def __init__(self):
        """Sets up the counter."""
        Counter.instances += 1
        self.reset()
        
    # Mutator methods
    def reset(self):
        """Sets the counter to 0."""
        self._value = 0
        
    def increment(self, amount = 1):
        """Adds amount to the counter."""
        self._value += amount
        

    def decrement(self, amount = 1):
       """Subtracts amount from the counter."""
       self._value -= amount
        

    # Accessor methods
    def getValue(self):
        """Returns the counter's value."""
        return self._value
    

    def __str__(self):
        """Returns the string representation of the counter."""
        return str(self._value)
    
    def __eq__(self, other):
        """Returns True if self equals other

        or False otherwise."""
        if self is other: return True
        if type(self) != type(other): return False
        return self._value == other._value
            
          
結(jié)果如下:
            
              Problem Size          Calls
           2              1
           4              5
           8             41
          16           1973
          32        4356617
            
          

說(shuō)明工作量的快速增長(zhǎng)的另一種方式是顯示該函數(shù)針對(duì)給定的問(wèn)題規(guī)模的一個(gè)調(diào)用樹(shù),下圖展示了當(dāng)使用遞歸函數(shù)來(lái)計(jì)算第6個(gè)斐波那契數(shù)的時(shí)候所用到的調(diào)用。

Python 搜索、排序、復(fù)雜度分析_第9張圖片

注意:每個(gè)填滿的層級(jí)上的調(diào)用綜述,都是其上一個(gè)層級(jí)的調(diào)用總數(shù)的兩倍。因此,完全平衡樹(shù)的遞歸調(diào)用次數(shù)通常是2^(n+1)-2,其中n是調(diào)用樹(shù)的頂部或者根部的參數(shù),這顯然是一個(gè)指數(shù)級(jí)的O(k^n)的算法。

指數(shù)算法通常只對(duì)較小的問(wèn)題規(guī)模才切合實(shí)際。盡管遞歸的Fibonacci在設(shè)計(jì)上很優(yōu)雅,但是和使用循環(huán)按照線性時(shí)間運(yùn)行的較快版本相比,它還是差很多。

此外,使用相同參數(shù)重復(fù)調(diào)用的遞歸函數(shù),例如Fibonacci函數(shù),可以通過(guò)一種叫作記憶(memoization)的技術(shù),來(lái)使得其更有效率,根據(jù)這個(gè)技術(shù),程序維護(hù)一張記錄了函數(shù)中所使用的每一個(gè)參數(shù)的值的表。在函數(shù)遞歸地計(jì)算一個(gè)給定參數(shù)的值之前,它會(huì)檢查這張表,看看該參數(shù)是否已經(jīng)有了一個(gè)值了。如果是的,就直接返回這個(gè)值。如果沒(méi)有,繼續(xù)計(jì)算過(guò)程,并且隨后會(huì)將參數(shù)和值添加到這個(gè)表中。


Fibonacci函數(shù)轉(zhuǎn)換為一個(gè)線性算法

這種算法可以將復(fù)雜度降低到線性時(shí)間階。

            
              class Counter(object):
    """Tracks a count."""

    def __init__(self):
        self._number = 0

    def increment(self):
        self._number += 1

    def __str__(self):
        return str(self._number)

def fib(n, counter):
    """Count the number of iterations in the Fibonacci
    function."""
    sum = 1
    first = 1
    second = 1
    count = 3
    while count <= n:
        counter.increment()
        sum = first + second
        first = second
        second = sum
        count += 1
    return sum

problemSize = 2
print("%12s%15s" % ("Problem Size", "Iterations"))
for count in range(5):
    counter = Counter()

    # The start of the algorithm
    fib(problemSize, counter)
    # The end of the algorithm
    
    print("%12d%15s" % (problemSize, counter))
    problemSize *= 2
            
          
結(jié)果如下:
            
              Problem Size     Iterations
           2              0
           4              2
           8              6
          16             14
          32             30
            
          

這里可以看出,該函數(shù)的新版本的性能已經(jīng)提升到了線性階。


本文是數(shù)據(jù)結(jié)構(gòu)(用Python實(shí)現(xiàn))這本書的讀書筆記!







更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 99热综合 | 精品久久久一二三区 | 婷婷在线视频 | 免费一级片在线观看 | 亚洲视频免费在线观看 | 青青草娱乐在线 | 91精品国产综合久久国产大片 | 91一区二区三区 | 中文字幕日韩欧美一区二区三区 | 超级碰碰碰频视频免费观看 | 欧美video巨大粗暴多人 | 免费一级毛片 | 狠狠操伊人 | 日韩欧美一区二区三区免费观看 | 国产亚洲精品国产一区 | 高清久久 | 国产精品久久久久久日本一道 | 潘金莲强完整版 | 正在播放国产无套露脸 | 99精彩视频 | 久久亚洲国产成人影院 | 激情五月激情综合网 | 日本在线免费观看视频 | 亚洲人和日本人jizz | 九九热在线视频免费观看 | 欧美ol丝袜高跟秘书在线播放 | 久久久无码精品成人A片小说 | 欧美一级欧美三级 | 看片地址| 国产福利在线观看永久免费 | 色播放| 天天色网站 | 午夜国产精品无套 | 片一级片在线观看 | 亚洲精品a| 欧美黑人乱大交 | 91久久久久久久久 | 久久精品久久精品久久 | 日韩精品一区二区免费视频 | WWW.亚洲最大夜色伊人 | 天天色天天 |