算法學習-分治法(一)

問題一:最大子數組問題

在這裏插入圖片描述

問題分析

我們的目標是找出一段連續的時間[A, B],使得price[B]- price[A]差值最大。將股票的價格數組轉換成A[i]表示price[i]- price[i - 1],則問題轉換成A數組的最大子數組問題
將問題更新,給定一個數組A[left…right],求出A數組的最大子數組(需要返回子數組的起始下標,終點下標,最大子數組之和)
分治算法中,子問題規模劃分是否均勻會極大的影響算法效率,爲了獲得更好的時間,需要將子問題儘可能均勻的劃分,記劃分中點爲mid,將數組劃分爲A[left, mid],A[mid+1,right]。最大子數組一定是下列三種情況之一

  • 在A[left, mid]中
  • 在A[mid+1, right]中
  • 跨過mid,將最大子數組記爲A[i,j],滿足left \leq i \leqmid < j \leqright

算法僞代碼

FindMaxSubArray(A, left, right)
    mid = (right + left) / 2
    //分治法算子問題
    (begin_left, end_left, max_sum_left) = FindMaxSubArray(A, left, mid)
    (begin_right, end_right, max_sum_right) = FindMaxSubArray(A, mid + 1, right)
    //處理子問題的解,得到最終解
    (begin_mid, end_mid, max_sum_mid) = FindMaxCrossSubArray(A, mid, left, right)
    if max_sum_mid >= max_sum_left && max_sum_mid >= max_sum_right
        return (begin_mid, end_mid, max_sum_mid)
    else if max_sum_left >= max_sum_right 
        return (begin_left, end_left, max_sum_left)
    else
        return (begin_right, end_right, max_sum_right)

//獲得A數組中下標跨過mid的最大子數組
FindMaxCrossSubArray(A, mid, left, right)
    sum = 0
    max_sum_left = 負無窮
    begin = mid
    for i = mid downto left
        sum += A[i]
        if sum > max_sum_left
            max_sum_left = max_sum
            begin = i
    
    sum = 0
    max_sum_right = 負無窮
    end = mid + 1
    for i = mid + 1 to right
        sum += A[i]
        if sum > max_sum_right
            max_sum_right = max_sum
            end = i
    return (begin, end, max_sum_left + max_sum_right)

時間複雜度分析

T(n)=2T(n/2)+O(n)T(n) = 2T(n / 2) + O(n)
利用Master定理可以得到算法時間複雜度是O(nlognnlogn

問題擴展一

在這裏插入圖片描述

問題分析

只需要在上述僞代碼返回部分判斷得到的最大子數組之和是不是小於0,如果小於0,返回[-1,-1,0]即可

問題擴展二

在這裏插入圖片描述

問題分析

(對於數組元素都是負數或者0的數組不予考慮,可以在下面的算法開始前判斷一下,時間複雜度是θ(n)\theta(n)
對於任何一個給定的數組,最大子數組一定是以一個正數作爲開頭,以正數作爲結尾,因爲如果開頭是負數或者是0,將開頭去掉,可以得到一個和更大或者和不變的最大子數組,同理,如果結尾是負數或者是0,同理。
算法的自然語言描述

  1. 初始化maxsum = 0,maxbegin = 0, maxend= 0
  2. 遍歷數組元素,直到找到首個爲正數的數組元素,將其加進persum,記錄perbegin和perend.
  3. 比較persum與maxsum,如果persum大於maxsum,將maxsum、maxbegin、maxend更新
  4. 繼續遍歷數組,直到數組結束
    • 如果遇到的數組元素是正數,將其加進persum,並更新perbegin,perend,跳轉到3
    • 如果遇到的數組元素是負數或者0
      • 如果將該數組元素加進persum,persum仍然是正數,則將其加進去
      • 如果是負數,則放棄當前的persum,perbegin,perend

算法僞代碼

FindMaxSubArray(A, left, right)
    maxsum = 0, maxbegin = 0, maxend = 0
    persum = 0, perbegin, perend = 0
    for i = left to right
        if A[i] > 0 
            persum += A[i]
            if perbegin == 0
                perbegin = i
            if perend == 0
                perend = i
        
            if (maxsum > persum)
                maxsum = persum
                maxbegin = perbegin
                maxend = perend
        elif A[i] <= 0 
            if persum + A[i] > 0
                perend = i
                persum += A[i]
            
            else 
                persum = 0
                perbegin = 0
                perend = 0
    return (maxbegin, maxend, maxsum)
        

還可以使用動態規劃,見動態規劃部分

問題二:最近點對

給定平面上n個點,求其中的一點對,使得在n個點的所有點對中,該點對的距離最小。嚴格地說,最接近點對可能多於1對。爲了簡單起見,這裏只限於找其中的一對。
(1)設計一個時間複雜度爲O(n2)的算法求距離最近的點對,要求寫出算法僞代碼;
(2)利用分治的思想設計一個時間複雜度爲O(nlogn)的算法求距離最近的點對,要求寫出算法僞代碼。

問題分析

第一問比較簡單,只要計算平面上所有的點之間的距離即可,算法時間複雜度是O(n2)O(n^2)
第二問比較複雜,主要思想是儘可能的將點分成左右兩部分,採用分治法分別計算出左右兩部分的最小距離,但是點之間的最小距離,可能出現在左側點和右側點之間,合併階段要處理這種情況,具體細節見僞代碼

算法僞代碼

FindMinDisSet(points) //輸入是平面上的點的數組
    length = points.length
    mindis1, mindis2, mindis = 正無窮
    for i = 0 to length
        for j = 0 to length
            if i != j
                dis = Dis(points[i], points[j])//返回兩點之間的歐氏距離
                if (mindis > dis)
                    mindis = dis
                    mindis1 = i
                    mindis2 = j
    return (points[i], points[j], mindis)

FindMinDis(points)
    //如果只有兩個點,直接返回兩點之間的距離
    if points.length == 2
        return Dis(points[1], points[2])
    //如果不夠兩個點,返回一個正無窮
    if points.length < 2
        return 正無窮
    //可以利用選擇算法在線性時間找到所有點橫座標的中位數
    //根據找到的中位數將點分成左右兩部分,下面分別處理
    mid = points中所有點橫座標的中位數
    for all point in points
        if point.x <= mid
            points_left.add(point)
        else point_right.add(point)

    dis_left = FindMinDis(points_left)
    dis_right = FindMinDis(points_right)
    //保存上面得到的兩個距離中的最小值
    dis_min = min(dis_left, dis_right)

    //遍歷所有點,找到所有距離直線X= mid距離小於dis_min的點,放到points_mid中
    for all point in points
        dis = distance between point and line X = mid
        if (dis < dis_min)
            points_mid.add(point)

    points_mid_sorted = 將points_mid按照y從小到大排序

    for all point in points_mid
        記錄point與其後六個點之間的距離
        如果距離小於dis_min,則將其更新

    return dis_min
    


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章