基於python的分治法和例題


分治法

分治法的核心

  1. :將一個復雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題
  2. :最后的子問題,可以很容易的直接求解
  3. :所有子問題的解合並起來就是原問題的解

分治法的特征

  1. 問題的規模縮小到一定的程度就可以容易地解決
  2. 問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質
  3. 利用該問題分解出的子問題的解可以合並為該問題的解
  4. 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子問題

第一條特征:是絕大多數問題都可以滿足的,因為問題的計算復雜性一般是隨着問題規模的增加而增加

第二條特征:是應用分治法的前提它也是大多數問題可以滿足的,此特征反映了遞歸思想的應用

第三條特征:是關鍵,能否利用分治法完全取決於問題是否具有第三條特征,如果具備了第一條和第二條特征,而不具備第三條特征,則可以考慮用貪心法或動態規划法

第四條特征:涉及到分治法的效率,如果各子問題是不獨立的則分治法要做許多不必要的工作,重復地解公共的子問題,此時雖然可用分治法,但一般用動態規划法較好

分治法例題

01. 快速指數

求 x=base^a,base為底數,a為指數。

基本思想:對base^a分治:

def fast_power(base, a):
    # 指數為0返回1
    if a == 0:
        return 1.0
    # 指數為負數
    elif a < 0:
        return 1 / fast_power(base, -a)
    # 指數為奇數
    elif a % 2:
        return fast_power(base * base, a // 2) * base
    # 指數為偶數
    else:
        return fast_power(base * base, a // 2)

print(fast_power(2, 5)) # 32

02. 搜索峰值

列表沒有重復值,但可能存在多個峰值,返回任意一個峰值的index.
你可以想象成 num[0] = num[n] = -∞, 第一位和最后一位為負無窮
def search_peak(alist, start, end):
    if start == end:
        return start

    if start + 1 == end:
        if alist[start] > alist[end]:
            return start
        return end

    mid = start + (end - start) // 2

    # 如果當前值大於前一個值,並且當前值大於后一個值,則當前值是峰值
    if alist[mid - 1] < alist[mid] and alist[mid + 1] < alist[mid]:
        return mid
    # 如果前一個值大於當前值,並且當前值大於后一個值,呈下降趨勢,前方有峰值,否則后方有峰值
    elif alist[mid - 1] > alist[mid] and alist[mid] > alist[mid + 1]:
        return search_peak(alist, start, mid-1)
    else:
        return search_peak(alist, mid + 1, end)


alist = [1, 3, 5, 100, 63, 32, 60, 70, 23, 12, 2, 21, 32, 45, 39, 36,11]
print(search_peak(alist, 0, len(alist) - 1)) # 7

03. 在有序列表中找多余元素

給定兩個排好序的列表。這兩個數組只有一個不同的地方:
在第一個數組某個位置上多一個元素。請找到這個元素的索引位置。
def find_extra(lst1, lst2):
    index = len(lst2)

    left, right = 0, len(lst2) - 1

    while left <= right:
        mid = left + (right - left) // 2
        # 如果中間元素相同,則表示多余元素在后面,否則在前面
        if lst1[mid] == lst2[mid]:
            left = mid + 1
        else:
            index = mid
            right = mid - 1
    return index


lst1 = [3, 5, 7, 9, 10, 11, 13]
lst2 = [3, 5, 7, 9, 11, 13]
print(find_extra(lst1, lst2)) # 4

04. 最大子序列和

在一個一維數組中找到連續的子序列,且這個子序列的加和值最大。
例如,一位數組序列為 [−2, 1, −3, 4, −1, 2, 1, −5, 4]
則這個序列對應的加和值最大的子序列為[4, −1, 2, 1], 其加和值為6.

解決思路:
現將序列等分為左右兩份,則最大子列只可能出現在三個地方:
  1. 整個子序列出現在左半部分
  2. 整個子序列出現在右半部分
  3. 整個子序列跨越中間邊界
import sys


# O(nlogn)
def sub_list(alist, left, right):
    if left == right:
        return alist[left]

    mid = left + (right - left) // 2
    # 左邊序列的最大和
    left_sub = sub_list(alist, left, mid)
    # 右邊序列的最大和
    right_sub = sub_list(alist, mid + 1, right)
    # 中間序列的最大和
    mid_sub = max_crossing(alist, left, mid, right)
    # 返回最大值
    return max(left_sub, right_sub, mid_sub)


def max_crossing(alist, left, mid, right):
    sum = 0
    # sys.maxsize int類型最大值: 9223372036854775807
    left_sum = -sys.maxsize
    # 從中間到左邊求和
    for i in range(mid, left - 1, -1):
        sum += alist[i]
        if sum > left_sum:
            left_sum = sum

    sum = 0
    right_sum = -sys.maxsize
    # 從中間到右邊求和
    for i in range(mid + 1, right + 1):
        sum += alist[i]
        if sum > right_sum:
            right_sum = sum

    return left_sum + right_sum


alist = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
sum = sub_list(alist, 0, len(alist) - 1)
print(sum) # 6

動態規划簡單解法:

# O(n)
def sub_list(alist):
    result = -sys.maxsize
    local = 0
    for i in alist:
        local = max(local + i, i)
        result = max(result, local)
    return result


alist = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
sub_list(alist) # 6
動態規范解決

05. 計算逆序對

對數組做逆序對計數—距離數組的排序結果還有“多遠”。如果一個數組已經排好序(升序),那么逆序對個數為0;
如果數組是降序排列的,則逆序對個數最多。
在形式上,如果有兩個元素a[i], a[j],如果a[i] > a[j] 且 i < j,那么a[i], a[j]構成一個逆序對。
例如序列[2, 4, 1, 3, 5] 有三個逆序對,分別是(2, 1), (4, 1), (4, 3)
解決思路:
利用歸並排序,只要是左邊大於右邊就有逆序對
# 歸並排序
def merge(left_list, right_list):
    i, j = 0, 0
    result_list = list()
    # 定義一個計數元素 inv_count
    inv_count = 0

    while i < len(left_list) and j < len(right_list):
        if left_list[i] < right_list[j]:
            result_list.append(left_list[i])
            i += 1
        # 只要right>left則是逆序對,inv_count加len(left_list)-i
        elif left_list[i] > right_list[j]:
            result_list.append(right_list[j])
            j += 1
            inv_count += len(left_list) - i

    result_list += left_list[i:]
    result_list += right_list[j:]

    return result_list, inv_count


def count_Inversions(alist):
    if len(alist) <= 1:
        return alist, 0

    mid = len(alist) // 2

    left_list, left_inv = count_Inversions(alist[:mid])
    right_list, right_inv = count_Inversions(alist[mid:])

    result, count = merge(left_list, right_list)
    count += left_inv + right_inv
    return result, count

alist = [2, 4, 1, 3, 5]
print(count_Inversions(alist)) # [1, 2, 3, 4, 5], 3

以上是一些例題!

~>.<!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM