前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Data Structures and Algorithms Basics(005):Divid conquer

Data Structures and Algorithms Basics(005):Divid conquer

作者头像
用户5473628
发布2019-08-08 15:02:02
4930
发布2019-08-08 15:02:02
举报
文章被收录于专栏:MiningAlgorithmsMiningAlgorithms

分治算法:

在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如二分搜索,排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)......

任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。

分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

  分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

  如果原问题可分割成k个子问题,1<k≤n,且这些子问题都可解并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。

目录:

1,查找第K大元素

2,快速指数

3, 搜索峰值

4, 查找差异位置index

5, 加总值最大的子序列

6, 查找逆序对

7,奇-偶数换序

8, 元素右边最小的元素

9, 查找两个元素合并后的中位数

10,快速整数乘法

11, 多项式乘法的快速傅里叶变换

12, 水槽问题

13,用最少步数收集所有硬币

代码语言:javascript
复制
# 1,查找第K大元素:
import time
# O(nlgn):
def findKthLargest1(nums, k):
    start = time.time()
    rst = sorted(nums, reverse=True)
    t = time.time() - start
    return rst[k-1], len(rst), t

# O(nk) time, bubble sort idea, TLE
def findKthLargest2(nums, k):
    start = time.time()
    for i in range(k):
        for j in range(len(nums)-i-1):
            if nums[j] > nums[j+1]:
                # exchange elements, time consuming
                nums[j], nums[j+1] = nums[j+1], nums[j]
    t = time.time() - start
    return nums[len(nums)-k], len(nums), t

# O(n) time, quick selection
def findKthLargest(nums, k):
    # convert the kth largest to smallest
    start = time.time()
    rst = findKthSmallest(nums, len(nums)+1-k)
    t = time.time() - start
    return rst, len(nums), t
    
def findKthSmallest(nums, k):
    if nums:
        pos = partition(nums, 0, len(nums)-1)
        if k > pos+1:
            return findKthSmallest(nums[pos+1:], k-pos-1)
        elif k < pos+1:
            return findKthSmallest(nums[:pos], k)
        else:
            return nums[pos]
 
# choose the right-most element as pivot   
def partition(nums, l, r):
    low = l
    while l < r:
        if nums[l] < nums[r]:
            nums[l], nums[low] = nums[low], nums[l]
            low += 1
        l += 1
    nums[low], nums[r] = nums[r], nums[low]
    return low

if __name__ == '__main__':
  import random
  from random import randint
  import sys
  import matplotlib.pyplot as plt
  def generate_random_array(n):
      return [randint(1, 3 * n) for e in range(n)]

  # 单元检测   
  l = generate_random_array(1000000)
  r = findKthLargest(l, len(l)//2)
  print(r)

  # 画图:时间复杂度
  # random_lists = [generate_random_array(1000 * n) for n in range(1, 21)]
  # rst = [findKthLargest(l, len(random_lists)//2) for l in random_lists]
  # x = list(zip(*rst))[1]
  # y = list(zip(*rst))[2]
  # plt.plot(x, y)
  # plt.show()

# 2,快速指数:
def fast_power1_flaw(x, n):
    if n <= 0:
        return 1
    elif n == 1:
        return x
    elif n % 2:
        return fast_power1_flaw(x * x, n // 2) * x
    else:
        return fast_power1_flaw(x * x, n // 2)

def fast_power2(x, n):
    if n == 0:
        return 1.0
    elif n < 0:
        return 1 / fast_power2(x, -n)
    elif n % 2:
        return fast_power2(x * x, n // 2) * x
    else:
        return fast_power2(x * x, n // 2)

if __name__ == '__main__':
  print(fast_power1_flaw(5, 3))
  print(fast_power2(5, 3))

# 3, 搜索峰值:数组没有重复值,但可能存在多个峰值,返回任意一个峰值的index
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)

# 4, 给定两个排好序的数组, 只有一个不同的地方:在第一个数组某个位置上多一个元素,查找该元素index
# Input : {3, 5, 7, 9, 11, 13} {3, 5, 7, 11, 13},Output : 3
def find_extra(arr1, arr2):
    for i in range(len(arr2)):
        if (arr1[i] != arr2[i]):
            return i 
    return len(arr1)-1

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

if __name__ == '__main__':
  ar1 = [3, 5, 7, 9, 11, 13]
  ar2 = [3, 5, 7, 11, 13]
  print(find_extra(ar1, ar2))
  print(find_extra_fast(ar1, ar2))

# 5, 加和值最大的子序列:
# O(n^2)
def subarray1(alist):
    result = -sys.maxsize
    for i in range(0, len(alist)):
        sum = 0
        for j in range (i, len(alist)):
            sum += alist[j]
            if sum > result:
                result = sum
    return result

# O(n lgn)
def subarray2(alist):
    return subarray2_helper(alist, 0, len(alist)-1)

def subarray2_helper(alist, left, right):
    if (left == right):
        return alist[left]
    mid = (left + right) // 2
    return max(subarray2_helper(alist, left, mid), 
               subarray2_helper(alist, mid+1, right), 
               maxcrossing(alist, left, mid, right))

def maxcrossing(alist, left, mid, right):
    sum = 0
    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

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

if __name__ == '__main__':
  alist = [-2,-3,4,-1,-2,1,5,-3]
  print(subarray1(alist))
  print(subarray2(alist))
  print(subarray3(alist))

# 6, 查找逆序对: 如果有两个元素a[i], a[j],如果a[i] > a[j] 且 i < j,那么a[i], a[j]构成一个逆序对
# 法1:O(n^2)
def countInv(arr):
    n = len(arr)
    inv_count = 0
    for i in range(n):
        for j in range(i+1, n):
            if (arr[i] > arr[j]):
                inv_count += 1
 
    return inv_count

# 法2:
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 right[j] < left[i]:
            result.append(right[j])
            j += 1
            inv_count += (len(left)-i)
    result += left[i:]
    result += right[j:]
    return result,inv_count

# O(nlgn)
def countInvFast(array):
    if len(array) < 2:
        return array, 0
    middle = len(array) // 2
    left,inv_left = countInvFast(array[:middle])
    right,inv_right = countInvFast(array[middle:])
    merged, count = merge(left,right)
    count += (inv_left + inv_right)
    return merged, count

if __name__ == '__main__': 
  arr = [1, 20, 6, 4, 5]
  print("Number of inversions are", countInv(arr))
  print("Number of inversions are", countInvFast(arr))

# 7,奇-偶数换序 :input:{ a1, a2, a3, a4, ….., an, b1, b2, b3, b4, …., bn },output:{a1, b1, a2, b2, a3, b3, ……, an, bn }
# O(n^2):
def shuffleArray1(a, n): 
    # Rotate the element to the left
    i, q, k = 0, 1, n
    while(i < n):             
        j = k 
        while(j > i + q):
            # print(i, j, q, k)
            a[j - 1], a[j] = a[j], a[j - 1]
            j -= 1
        # for ii in range(0, 2 * n): 
        #     print(a[ii], end = " ")
        # print()
        i += 1
        k += 1
        q += 1
    return a

# O(n log n):
def shuffleArray2(a, left, right): 
    # If only 2 element, return
    if (right - left == 1):
        return 
    # Finding mid to divide the array
    mid = (left + right) // 2
 
    # Using temp for swapping first
    # half of second array
    temp = mid + 1
 
    # Mid is use for swapping second
    # half for first array
    mmid = (left + mid) // 2
 
    # Swapping the element
    for i in range(mmid + 1, mid + 1):
        (a[i], a[temp]) = (a[temp], a[i])
        temp += 1
 
    # Recursively doing for 
    # first half and second half
    shuffleArray2(a, left, mid)
    shuffleArray2(a, mid + 1, right)
    return a

def shuffleArray3(a):
    n = len(a) // 2
    start = n + 1
    j = n + 1
    done = 0
    
    while (done < 2 * n - 2):
        #print(done, start, j)
        if (start == j):
            start = start - 1
            j = j - 1
        done += 1
        
        i = j - n if j > n else j
        j = 2 * i if j > n else 2 * i - 1
        
        a[start], a[j] = a[j], a[start]
    return a

if __name__ == '__main__':
  a = [1, 3, 5, 7, 2, 4, 6, 8] 
  print(shuffleArray1(a, len(a) // 2))
  print(shuffleArray2(a, 0, len(a) - 1))

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

# 8, 元素右边最小的元素:input: [9, 5, 3, 1, 26], output: [1, 1, 1, 26, 0]
# O(n^2):
def countSmaller1(nums):   # 法1
    n = len(nums)
    count = [0] * n
    for i in range(n):
        for j in range(i + 1, n):
            if nums[i] > nums[j]:
                count[i] += 1
                
    return count

# O(n^2):
def countSmaller2(nums):      # 法2
    snums = []
    ans = [0] * len(nums)

    for i in range(len(nums) - 1, -1, -1):
        index = findIndex(snums, nums[i])
        ans[i] = index
        snums.insert(index, nums[i]) 
    return ans

def findIndex(snums, target):
    start = 0
    end = len(snums) - 1

    if len(snums) == 0: 
        return 0

    while start <= end:
        mid = start + (end - start) // 2
        if snums[mid] < target:
            start=mid + 1
        else:
            end = mid - 1
    return start

# O(nlgn):
def countSmaller3(nums):
    def sort(enum):
        half = len(enum) // 2
        if half:
            left, right = sort(enum[:half]), sort(enum[half:])
            m, n = len(left), len(right)
            i = j = 0
            while i < m or j < n:
                if j == n or i < m and left[i][1] <= right[j][1]:
                    smaller[left[i][0]] += j
                    enum[i+j] = left[i]
                    i += 1
                else:
                    enum[i+j] = right[j]
                    j += 1
        #     print("left: ", left)
        #     print("right: ", right)
        #     print("smaller: ", smaller)
        # print("enum: ", enum)
        return enum
    smaller = [0] * len(nums)
    sort(list(enumerate(nums)))
    return smaller
if __name__ == '__main__':
  nums1 = [5, 2, 6, 1]
  #nums2 = [9, 5, 3, 1, 26]
  print(countSmaller1(nums1))
  print(countSmaller2(nums1))
  print(countSmaller3(nums1))
  print(list(enumerate(nums1)))  # 遍历列表元素

# 9, 查找两个元素合并后的中位数:要求时间复杂度为O(log (m+n)):
def findMedianSortedArrays1(A, B):
    l = len(A) + len(B)
    if l % 2 == 1:
        return kth1(A, B, l // 2)
    else:
        return (kth1(A, B, l // 2) + kth1(A, B, l // 2 - 1)) / 2.   
    
def kth1(a, b, k):
    if not a:
        return b[k]
    if not b:
        return a[k]
    ia, ib = len(a) // 2 , len(b) // 2
    ma, mb = a[ia], b[ib]
    
    # when k is bigger than the sum of a and b's median indices 
    if ia + ib < k:
        # if a's median is bigger than b's, b's first half doesn't include k
        if ma > mb:
            return kth1(a, b[ib + 1:], k - ib - 1)
        else:
            return kth1(a[ia + 1:], b, k - ia - 1)
    # when k is smaller than the sum of a and b's indices
    else:
        # if a's median is bigger than b's, a's second half doesn't include k
        if ma > mb:
            return kth1(a[:ia], b, k)
        else:
            return kth1(a, b[:ib], k)

def find2(nums1, s1, e1, nums2, s2, e2, k):
    if e1 < s1:
        return nums2[k + s2]
    if e2 < s2:
        return nums1[k + s1]
    
    if k < 1:
        return min(nums1[k + s1], nums2[k + s2])
    
    ia, ib = (s1 + e1) // 2 , (s2 + e2) // 2
    ma, mb = nums1[ia], nums2[ib]
    if (ia - s1) + (ib - s2) < k:
        if ma > mb:
            return find2(nums1, s1, e1, nums2, ib + 1, e2, k - (ib - s2) - 1)
        else:
            return find2(nums1, ia + 1, e1, nums2, s2, e2, k - (ia - s1) - 1)
    else:
        if ma > mb:
            return find2(nums1, s1, ia - 1, nums2, s2, e2, k)
        else:
            return find2(nums1, s1, e1, nums2, s2, ib - 1, k)

def findMedianSortedArrays2(nums1, nums2):
    l = len(nums1) + len(nums2)
    if l % 2 == 1:
        return find2(nums1, 0, len(nums1) - 1, nums2, 0, len(nums2) - 1, l // 2)
    else:
        return (find2(nums1, 0, len(nums1) - 1, nums2, 0, len(nums2) - 1, l // 2) 
                + find2(nums1, 0, len(nums1) - 1, nums2, 0, len(nums2) - 1, l // 2 - 1)) / 2.0

if __name__ == '__main__':
  A = [1, 12, 15, 26, 38]
  B = [2, 13, 17]

  print(findMedianSortedArrays1(A, B))
  print(findMedianSortedArrays2(A, B))
  
  # 10,快速整数乘法:
# 法一:
def karatsuba(x,y):
    """Function to multiply 2 numbers in a more efficient manner than the grade school algorithm"""
    if len(str(x)) == 1 or len(str(y)) == 1:
        return x*y
    else:
        n = max(len(str(x)),len(str(y)))
        nby2 = n // 2

        a = x // 10**(nby2)
        b = x % 10**(nby2)
        c = y // 10**(nby2)
        d = y % 10**(nby2)

        ac = karatsuba(a,c)
        bd = karatsuba(b,d)
        ad_plus_bc = karatsuba(a+b,c+d) - ac - bd

            # this little trick, writing n as 2*nby2 takes care of both even and odd n
        prod = ac * 10**(2*nby2) + (ad_plus_bc * 10**nby2) + bd

        return prod
 
 # 法二:
 # third_grade_algorithm.py
import functools
def prod(x, y):
    # x, y are strings --> returns a string of x*y
    return str(eval("%s * %s" % (x, y)))

def plus(x, y):
    # x, y are strings --> returns a string of x+y
    return str(eval("%s + %s" % (x, y)))

def one_to_n_product(d, x):
    """d is a single digit, x is n-digit --> returns a string of d*x
    """
    #print(d, x)
    result = ""
    carry = "0"
    for i, digit in enumerate(reversed(x)):
        #print("d: ", d, "  digit: ", digit)
        r = plus(prod(d, digit), carry)
        #print("r: ", r)
        if (len(r) == 1):
            carry = '0'
        else:
            carry = r[:-1]
        digit = r[-1]
        #print("   c: ", carry, "  d: ", digit)
        result = digit + result
    
    
    return carry + result

def sum_middle_products(middle_products):
    # middle_products is a list of strings --> returns a string
    max_length = max([len(md) for md in middle_products])
    for i, md in enumerate(middle_products):
        middle_products[i] = "0" * (max_length - len(md)) + md
 
    #print(middle_products)
    carry = "0"
    result = ""
    for i in range(1, max_length + 1):
        row = [carry] + [md[-i] for md in middle_products]
        r = functools.reduce(plus, row)
        carry, digit = r[:-1], r[-1]
        result = digit + result
    return carry + result


def algorithm(x, y):
    x, y = str(x), str(y)
    middle_products = []
    for i, digit in enumerate(reversed(y)):
        middle_products.append(one_to_n_product(digit, x) + "0" * i)
    #print(middle_products)
    return int(sum_middle_products(middle_products))

if __name__ == '__main__':
    print(karatsuba(1090, 324098))
    print(algorithm(1090, 324098))

# 11, 多项式乘法的快速傅里叶变换:
def mults(A, B):
    m, n = len(A), len(B)
    result = [0] * (m + n - 1)
    for i in range (m):
        for j in range(n):
            result[i + j] += A[i] * B[j]
    return result

def printPoly(poly):
    n = len(poly)
    show = ""
    for i in range(n-1, -1, -1):
        show += str(poly[i])
        if (i != 0):
            show = show + "x^" + str(i)
        if (i != 0):
            show = show + " + "
    print(show)

if __name__ == '__main__':
    A = [5, 0, 10, 6]
    B = [1, 2, 4]
    r = mults(A, B)
    printPoly(r)

    from numpy import convolve
    A = [5, 0, 10, 6]
    B = [1, 2, 4]
    print(convolve(A, B))

# 12, 水槽问题:
# Utility method to get
# sum of first n numbers
def getCumulateSum(n):
    return (n * (n + 1)) // 2
 
 
# Method returns minimum number of days
# after  which tank will become empty
def minDaysToEmpty(C, l):
 
    # if water filling is more than 
    # capacity then after C days only
    # tank will become empty
    if (C <= l) : return C 
 
    # initialize binary search variable
    lo, hi = 0, 1e4
 
    # loop until low is less than high
    while (lo < hi): 
        mid = int((lo + hi) / 2)
 
        # if cumulate sum is greater than (C - l) 
        # then search on left side
        if (getCumulateSum(mid) >= (C - l)): 
            hi = mid
         
        # if (C - l) is more then 
        # search on right side
        else:
            lo = mid + 1   
     
    # Final answer will be obtained by 
    # adding l to binary search result
    return (l + lo)

import math  # 法二:
def solve(a, b, c):
    r = pow(b, 2) - 4 * a * c
    if (r < 0):
        raise ValueError("No Solution") 
    return (-b + math.sqrt(r)) / (2 * a)

def minDaysToEmpty2(C, l):
    co = -2 * (C - l)
    return  math.ceil(solve(1, 1, co)) + l

if __name__ == '__main__':
    C, l = 5, 2
    print(minDaysToEmpty(C, l))

    C, l = 5, 2
    print(minDaysToEmpty2(C, l))


# 13,用最少步数收集所有硬币:
def minSteps(height):
    
    def minStepHelper(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, 
                   minStepHelper(height, left, m, height[m]) +
                   minStepHelper(height, m + 1, right, height[m]) +
                   height[m] - h)
    
    return minStepHelper(height, 0, len(height), 0)

if __name__ == '__main__':
    height = [3, 1, 2, 5, 1]
    print(minSteps(height))
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 MiningAlgorithms 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档