簡體   English   中英

如何找到遞推關系,並計算歸並排序碼的主定理?

[英]How to find the recurrence relation, and calculate Master Theorem of a Merge Sort Code?

我試圖找到這個合並排序代碼的主定理,但首先我需要找到它的遞歸關系,但我很難做到並理解兩者。 我已經在這里看到了一些類似的問題,但無法理解解釋,例如,首先我需要找出代碼有多少操作? 有人可以幫我嗎?


def mergeSort(alist):
    print("Splitting ",alist)
    if len(alist)>1:
        mid = len(alist)//2
        lefthalf = alist[:mid]
        righthalf = alist[mid:]

        mergeSort(lefthalf)
        mergeSort(righthalf)

        i=0
        j=0
        k=0
        while i < len(lefthalf) and j < len(righthalf):
            if lefthalf[i] < righthalf[j]:
                alist[k]=lefthalf[i]
                i=i+1
            else:
                alist[k]=righthalf[j]
                j=j+1
            k=k+1

        while i < len(lefthalf):
            alist[k]=lefthalf[i]
            i=i+1
            k=k+1

        while j < len(righthalf):
            alist[k]=righthalf[j]
            j=j+1
            k=k+1
    print("Merging ",alist)

alist = [54,26,93,17,77,31,44,55,20]
mergeSort(alist)
print(alist)

好的,讓我們開始分析。 假設您的列表有n元素,並且n是 2 的冪(避免出現大小為(n+1)/2 and (n-1)/2的子列表而不失一般性的問題)。 我正在跳過打印和一些具有恆定時間 ( +c' ) 的命令。

    if len(alist)>1:
        mid = len(alist)//2

通過遍歷所有元素,可以在線性時間內完成計數。 除以 2 不會改變整體行為:
T(n) = (a_1)*n + (a_2)*n +... + c'

        lefthalf = alist[:mid]
        righthalf = alist[mid:]

拆分列表可以理解為復制列表,所以線性復雜度
T(n) = (a_1)*n + (a_2)*n + (a_3)*(n/2)*2 +... + c'

        mergeSort(lefthalf)
        mergeSort(righthalf)

這是棘手的部分。 您對mergeSort合並排序的時間復雜度一無所知。 但是您知道調用的輸入大小: n/2重新調用
T(n) = (a_1)*n + (a_2)*n + (a_3)*n + T(n/2) + T(n/2) +... + c'

        while i < len(lefthalf) and j < len(righthalf):
            if lefthalf[i] < righthalf[j]:
                alist[k]=lefthalf[i]
                i=i+1
            else:
                alist[k]=righthalf[j]
                j=j+1
            k=k+1

這里開始一個循環,這意味着循環的每個命令都可以像循環重復一樣頻繁地執行。 值得慶幸的是,內容只是具有恆定時間的命令。 唯一困難的部分是確定最多只能為(len(lefthalf) -1) + (len(righthalf) -1)的迭代次數,最多為n-2 (並且至少為n/2-1 )。 將再次檢查while條件,導致時間復雜度上限(a_4)*(n-2) + c_4 = (a_4)*n - 2*a_4 + c_4 = (a_4)*n + c_4'

其他兩個循環也會發生類似的事情(里面的命令是常量,最大n/2循環,如果條件為假,則開銷恆定),上限:
(a_5)*n/2 + c_5 = (a_5')*n + c_5
(a_6)*n/2 + c_6 = (a_6')*n + c_6

總結總結:

T(n) = (a_1)*n + (a_2)*n + (a_3)*n + T(n/2) + T(n/2) + (a_4)*n + c_4' + (a_5')*n + c_5 + (a_6')*n + c_6 + c'
= (a_1+a_2+a_3+a_4+a_5'+a_6')*n + 2T(n/2) + c_4'+c_5+c_6+c'
= 2T(n/2) + a'*n + c

這是上限 (Big O) 的公式,但計算下限 (Big Omega) 將以相同的結構結束,只有a'c的實際(但無意義)值會有所不同。

所以知道你有一個公式可以使用。 大師定理可以應用於T(n) = a*T(n/b) + f(n) 您可以清楚地看到a=2b=2f(n) = a'*n + c 現在,根據f(n)的復雜度以及ab的關系,可以將T(n)的復雜度分為 3 種情況之一。 首先,您需要計算臨界常數c_crit = log_b a = log_2 2 = 1 現在我們需要f(n) = a'*n + c = O(n) = O(n^1)的復雜度(線性復雜度)。

  • 第一種情況: f(n) = O(n^c)其中c小於c_crit不適用(1 不小於 1)
  • 第三種情況: f(n) = Omega(n^c)其中c大於c_crit不適用(當 Big O 為上限時,Big Omega 為下限)
  • 第二種情況: f(n) = Theta(n^c_crit log^kn)對於k>=0c_crit = 1 , k=0 這個條件成立,所以從主定理,你現在可以得出結論

T(n) = Theta(n^c_crit log^(k+1) n) = Theta(n^1 log^(0+1) n) = Theta(n log(n))

所以這個算法的復雜度n log(n) 此示例中f(n)的 Big O、Big Omega 和 Big Theta 相同,但在其他示例中可能不同。

要使用主定理確定分治算法的運行時間,您需要將算法的運行時間表示為輸入大小的遞歸 function,格式為:

T(n) = aT(n/b) + f(n)

T(n)是我們如何在輸入大小 n 上表達算法的總運行時間。

a代表算法進行的遞歸調用的次數。

T(n/b)表示遞歸調用: n/b表示遞歸調用的輸入大小是原始輸入大小的某個特定部分(分治法的除法部分)。

f(n)表示您在算法主體中需要做的工作量,通常只是將遞歸調用的解決方案組合成一個整體解決方案(您可以說這是征服部分)。

下面是對mergeSort 的稍微重構的定義:

def mergeSort(arr):
  if len(arr) <= 1: return # array size 1 or 0 is already sorted
  
  # split the array in half
  mid = len(arr)//2
  L = arr[:mid]
  R = arr[mid:]

  mergeSort(L) # sort left half
  mergeSort(R) # sort right half
  merge(L, R, arr) # merge sorted halves

我們需要確定an/bf(n)

因為每次對 mergeSort 的調用都會進行兩次遞歸調用: mergeSort(L)mergeSort(R)a=2

T(n) = 2T(n/b) + f(n)

n/b表示進行遞歸調用的當前輸入的比例。 因為我們要找到中點並將輸入分成兩半,將當前數組的一半傳遞給每個遞歸調用, n/b = n/2b=2 (如果每個遞歸調用得到原始數組b的 1/4 則為4

T(n) = 2T(n/2) + f(n)

f(n)表示算法除了進行遞歸調用之外所做的所有工作。 每次調用 mergeSort 時,我們都會計算 O(1) 時間的中點。 我們還將數組拆分為LR ,從技術上講,創建這兩個子數組副本是 O(n)。 然后,假設mergeSort(L)對數組的左半部分進行排序,而mergeSort(R)對右半部分進行排序,我們仍然必須將已排序的子數組合並在一起,以使用merge function 對整個數組進行排序。 總之,這使得f(n) = O(1) + O(n) + complexity of merge 現在讓我們看一下merge

def merge(L, R, arr):
  i = j = k = 0    # 3 assignments
  while i < len(L) and j < len(R): # 2 comparisons
    if L[i] < R[j]: # 1 comparison, 2 array idx
      arr[k] = L[i] # 1 assignment, 2 array idx
      i += 1        # 1 assignment
    else:
      arr[k] = R[j] # 1 assignment, 2 array idx
      j += 1        # 1 assignment
    k += 1          # 1 assignment

  while i < len(L): # 1 comparison
    arr[k] = L[i]   # 1 assignment, 2 array idx
    i += 1          # 1 assignment
    k += 1          # 1 assignment

  while j < len(R): # 1 comparison
    arr[k] = R[j]   # 1 assignment, 2 array idx
    j += 1          # 1 assignment
    k += 1          # 1 assignment

這個 function 有更多的事情要做,但我們只需要得到它的整體復雜性 class 就能夠准確地應用主定理。 我們可以計算每一個操作,即每一個比較、數組索引和賦值,或者更一般地對其進行推理。 一般來說,您可以說,在三個 while 循環中,我們將遍歷 L 和 R 的每個成員,並將它們分配給 output 數組 arr,為每個元素執行恆定數量的工作。 注意我們正在處理 L 和 R 的每個元素(總共 n 個元素),並且為每個元素做恆定量的工作就足以說明合並在 O(n) 中。

但是,如果您願意,您可以更具體地使用計數操作。 對於第一個 while 循環,每次迭代我們進行 3 次比較、5 個數組索引和 2 個賦值(常數),並且循環運行直到 L 和 R 之一被完全處理。 然后,接下來的兩個 while 循環之一可能會運行以處理來自另一個數組的任何剩余元素,執行 1 個比較、2 個數組索引和每個元素的 3 個變量分配(持續工作)。 因此,因為 L 和 R 的 n 個總元素中的每一個都會導致在 while 循環中執行最多恆定數量的操作(根據我的計數,10 或 6 個,所以最多 10 個),並且i=j=k=0語句只有 3 個常量賦值,合並在 O(3 + 10*n) = O(n) 中。 回到整體問題,這意味着:

f(n) = O(1) + O(n) + complexity of merge
     = O(1) + O(n) + O(n)
     = O(2n + 1)
     = O(n)

T(n) = 2T(n/2) + n

在我們應用主定理之前的最后一步:我們希望 f(n) 寫成 n^c。 對於 f(n) = n = n^1, c=1 (注意:如果 f(n) = n^c*log^k(n) 而不是簡單的 n^c,情況會發生非常輕微的變化,但我們在這里不必擔心)

您現在可以應用主定理,它最基本的形式是比較a (遞歸調用的數量增長的速度)與b^c (每個遞歸調用的工作量減少的速度)。 有 3 種可能的情況,我試圖解釋其中的邏輯,但如果括號中的解釋沒有幫助,你可以忽略它們:

  1. a > b^c, T(n) = O(n^log_b(a)) (遞歸調用總數的增長速度快於每次調用的工作量減少的速度,因此總工作量由遞歸樹底層的調用次數決定。調用次數從 1 開始,乘以a log_b(n) 次,因為 log_b(n) 是遞歸樹的深度。因此,總工作量 = a^log_b(n) = n^log_b(a))

  2. a = b^c, T(n) = O(f(n)*log(n)) (調用次數的增長與每次調用工作量的減少相平衡。因此,遞歸樹每一層的工作量是恆定的,所以總工作量就是 f(n)*(depth of tree) = f(n) *log_b(n) = O(f(n)*log(n))

  3. a < b^c, T(n) = O(f(n)) (每次調用的工作量減少的速度快於調用次數的增加量。因此,總工作量由遞歸樹頂層的工作量支配,即 f(n))

對於 mergeSort 的情況,我們已經看到 a = 2、b = 2 和 c = 1。作為 a = b^c,我們應用第二種情況:

T(n) = O(f(n)*log(n)) = O(n*log(n))

你完成了。 這似乎需要做很多工作,但是為 T(n) 提出一個遞歸式會變得越容易,而且一旦你有一個遞歸式,它就可以很快地檢查它屬於哪種情況,這使得主定理相當解決更復雜的分/治重復的有用工具。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM