数据结构与算法6 - 分治法

分治法:

1. 将问题拆分为几个子问题,并且这些子问题和原问题相似只是量级上小一些。

2. 递归地解决每一个子问题,然后结合这些子问题的解决方案构造出原问题的解决方案。

我们已经遇到过的问题:

  1. 二分搜索

  2. 归并排序

  3. 快速排序

分治法例子:

练习1:快速指数:

  能够快速计算出a的n次方

def Fast_pow(a, n):
    if n == 0:
        return 1.0
    elif n < 0:
        return 1 / Fast_pow(a, -n)
    elif n % 2:                             # 奇数
        return Fast_pow(a * a, n // 2) * a
    else:                                   # 偶数
        return Fast_pow(a * a, n // 2)
print(Fast_pow(2, 5))

练习2:搜索峰值

  数组中没有重复数,但可能存在多个峰值,返回任意一个峰值的index

  You may imagine that num[-1] = num[n] = -∞.

def search_peak(alist):
    return peak_helper(alist, 0, len(alist) - 1)

def peak_helper(alist, start, end):
    if start == end:
        return start
    
    if (start + 1 == end):
        if alist[start] > alist[end]:
            return start
        return end
    
    mid = (start + end) // 2
    if alist[mid] > alist[mid - 1] and alist[mid] > alist[mid + 1]:
        return mid
    if alist[mid - 1] > alist[mid] and alist[mid] > alist[mid + 1]:
        return peak_helper(alist, start, mid - 1)
    return peak_helper(alist, mid + 1, end)

alist = [1, 2, 4, 4, 2, 5, 2]
print(search_peak(alist)+1)

练习3: 两数组交集:

  给出2个大小不一的数组,找出这两个数组的交集

  要求:输出中不能有重复

  give nums1 = [1 2 2 1], nums2 = [2, 2], return[2]

def find_extra_fast(arr1, arr2):
    index = len(arr2)
    # left and right are end points denoting
    # the current range.
    left, right = 0, len(arr2) - 1
    while (left <= right):
        mid = (left + right) // 2;
 
        # If middle element is same of both
        # arrays, it means that extra element
        # is after mid so we update left to mid+1
        if (arr2[mid] == arr1[mid]):
            left = mid + 1
 
        # If middle element is different of the
        # arrays, it means that the index we are
        # searching for is either mid, or before
        # mid. Hence we update right to mid-1.
        else:
            index = mid
            right = mid - 1;
 
    # when right is greater than left our
    # search is complete.
    return index

练习4: 计算逆序对

  对数组做逆序对计数 - 距离数组的排序结果还有多远。如果一个数组已经排好序,那么逆序对个数为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, right):
    result = list()
    i,j = 0,0
    inv_count = 0
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        elif left[i] > right[j]:
            result.append(right[j])
            j += 1
            inv_count += len(left) - i
    result += left[i:]
    result += right[j:]
    return result, inv_count

def count_invert(nums):
    if len(nums) < 2:
        return nums, 0
    mid = len(nums)//2
    left, inv_left = count_invert(nums[:mid])
    right, inv_right = count_invert(nums[mid:])
    merged, count = merge(left, right)
    count += (inv_left + inv_right)
    return merged, count
arr = [1, 20, 4, 6, 5]
print(count_invert(arr))

 练习5:加和值最大的子序列问题

  在一个一维数组中找到连续的子序列,且这个子序列的和加值最大

  例如,一个数组序列为-2,1,-3,4,-1,2,1,-5,4

  则这个序列对应的加和值最大的子序列为4,-1,2,1,加和值为6

def subarray2(alist):    #动态规划
    result = 0
    local = 0
    for i in alist:
        local = max(local + i, i)
        result = max(result, local)
    return result

def subarray1(alist):       #分治法
    return subarray1_helper(alist, 0, len(alist)-1)
def subarray1_helper(alist, left, right):
    if(left == right):
        return alist[left]
    mid = (left + right) // 2
    return max(subarray1_helper(alist, left, mid),
               subarray1_helper(alist, mid+1, right),
               max_cross(alist, left, mid, right))
def max_cross(alist, left, mid, right):
    sum = 0
    left_sum = 0
    for i in range(mid, left-1,-1):
        sum += alist[i]
        if(sum > left_sum):
            left_sum = sum
    sum = 0
    right_sum = 0
    for i in range(mid+1, right+1):
        sum += alist[i]
        if(sum > right_sum):
            right_sum = sum
    return left_sum + right_sum

alist = [-1, 2, -2, 5, -4, 3, 7]
print(subarray1(alist))

练习:6:水槽问题

  给定一个容量为C升的水槽,初始时给这个水槽装满水。每天都会给水槽中加入I升的水,若有溢出,则舍弃多余的水。另外在第i天,水槽中有i升的水会被喝掉,请计算在哪一天水槽的水被用完。

def water_problem(C, I):
    i = 1
    day = 0
    while(C > I):
        C = min(C - i + I, C)
        day += 1
        i += 1
    return day
C, I = 5, 2
print(water_problem(C, I))

练习7:奇-偶数换序问题

  给定一个含有2n个元素的数组,形式如{a1,a2,a3,...,b1,b2,...bn},按照{a1,b1,a2,b2,...,an,bn}排序

  举例:

    输入:arr[] = {1,2,9,15}

    输出:1 9 2 15   

    输入:arr[] = {1,2,3,4,5,6}

    输出:1 4 2 5 3 6

def Shuffle_Array(alist, left, right):
    if(left == right -1):
        return
    mid = (left + right) // 2
    mmid = (left + mid) // 2
    temp = mid + 1
    for i in range(mmid + 1, mid + 1):
        alist[i], alist[temp] = alist[temp], alist[i]
        temp += 1
    Shuffle_Array(alist, left, mid)
    Shuffle_Array(alist, mid+1, right)

a = [1,3,5,7,2,4,6,8]
Shuffle_Array(a,0, len(a)-1)
for i in range(len(a)):
    print(a[i], end=" ")

练习8:用最少的步数收集所有硬币

  给定几摞硬币,这些硬币相邻排列,我们用最少的步数收集所有的硬币,其中每一步可以沿水平线或者垂直线连续收集

  举例:

  输入:height[] = [2 1 2 5 1]

  数组中每个值代表对应摞的高度,此处给了我们5摞硬币,其中第一摞有2个硬币,第二摞有1个硬币,其余依次对应。

  输出:4

def min_Steps(height):
    def min_Steps_helper(height, left, right, h):
        if left >= right:
            return 0
        m = left
        for i in range(left, right):
            if height[i] < height[m]:
                m = i
        return min(right - left,
                   min_Steps_helper(height, left, m, height[m])+
                   min_Steps_helper(height, m + 1, right, height[m])+
                   height[m] - h)
    return min_Steps_helper(height, 0, len(height), 0)
height = [2,1,2,5,1]
print(min_Steps(height))
posted @ 2019-10-08 23:27  吕晓宁  阅读(380)  评论(0编辑  收藏  举报