分治法 样例解析

分治法将问题分割成更小的子问题,然后递归(迭代)地解决这些子问题,最后将它们的解合并起来得到原问题的解。因此,分治法常用于解决一些具有重复性质的问题,例如排序、查找、计算等问题。

分治法通常包括三个步骤:

1.分解(Divide):将原问题分解成若干个规模较小的子问题,这些子问题互相独立且与原问题形式相同。

2.解决(Conquer):递归地解决各个子问题。当子问题的规模足够小时,可以直接求解。

3.合并(Combine):将各个子问题的解合并成原问题的解。

分治法的优点是可以降低算法的时间复杂度,使得原本难以解决的问题可以得到有效的解决。

分治法的缺点是需要额外的空间来存储子问题的解,(递归的过程也会增加函数调用的开销)。

因此,在设计算法时需要权衡时间和空间的使用。

分治法应用:

(1)二分搜素

A.迭代实现

arr表示有序数组,target表示要查找的目标元素。函数binary_search使用两个指针lowhigh来表示搜索范围。在每次循环中,计算中间位置mid,如果arr[mid]等于target,则返回mid

如果arr[mid]小于target,则说明目标元素在mid的右侧,将low指针移到mid的右侧,继续搜索。如果arr[mid]大于target,则说明目标元素在mid的左侧,将high指针移到mid的左侧,继续搜索。如果搜索范围缩小到low > high,则说明目标元素不存在于数组中,返回-1。

def binary_search(arr, target):
    low, high = 0, len(arr) - 1
    while low <= high:
        mid = (low + high) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1
    return -1
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
target = 5
print(binary_search(arr, target))  # 输出:4

B.递归实现

def binary_search_recursive(arr, target, low, high):
    if low > high:
        return -1
    mid = (low + high) // 2
    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        return binary_search_recursive(arr, target, mid + 1, high)
    else:
        return binary_search_recursive(arr, target, low, mid - 1)

(2)找最大最小元素(MINMAX)

arr表示要查找的数组,lowhigh表示搜索范围的下标。在每次递归中,如果搜索范围缩小到只有一个元素,返回该元素作为最小值和最大值。如果搜索范围缩小到只有两个元素,比较这两个元素的大小,返回最小值和最大值。如果搜索范围缩小到超过两个元素,

将搜索范围分成两个部分,分别递归查找左半部分和右半部分的最小值和最大值,然后

比较左半部分的最小值和右半部分的最小值,返回较小值作为最小值,

比较左半部分的最大值和右半部分的最大值,返回较大值作为最大值。

def minmax(arr, low, high):
    if low == high:
        return arr[low], arr[low]
    elif high == low + 1:
        return (arr[low], arr[high]) if arr[low] < arr[high] else (arr[high], arr[low])
    else:
        mid = (low + high) // 2
        left_min, left_max = minmax(arr, low, mid)
        right_min, right_max = minmax(arr, mid + 1, high)
        return (min(left_min, right_min), max(left_max, right_max))
arr = [3, 5, 2, 1, 7, 4, 9, 6, 8]
print(minmax(arr, 0, len(arr) - 1))  # 输出:(1, 9)

 (3)寻找第k小元素

 arr表示要查找的数组,k表示要查找的第k小元素。在每次递归中,随机选择一个元素作为基准点pivot将数组分成三个部分:小于pivot的元素(lows)、等于pivot的元素(pivots)和大于pivot的元素(highs)。

如果lows的元素个数大于等于k,第k小元素在lows中,对lows进行递归查找。

如果lows+pivots的元素个数大于等于k,第k小元素等于pivot

否则,第k小元素在highs中,对highs进行递归查找。

以上,其实也可以说是,进行一次快速排序,然后就可以确定这个第k小元素在基准点的哪一边了

import random

def select(arr, k):
    if len(arr) == 1:
        return arr[0]
    pivot = random.choice(arr)
    lows = [x for x in arr if x < pivot]
    highs = [x for x in arr if x > pivot]
    pivots = [x for x in arr if x == pivot]
    if k < len(lows):
        return select(lows, k)
    elif k < len(lows) + len(pivots):
        return pivots[0]
    else:
        return select(highs, k - len(lows) - len(pivots))

arr = [3, 5, 2, 1, 7, 4, 9, 6, 8]
print(select(arr, 6))  # 输出:6

当然,我们发现直接用排序然后来找也可以,最好的时间复杂度为O(logn)。而使用上述方法,时间复杂度就只有O(n)了。但是在n小于某个阀值时,直接用排序其实更快。(这个阀值可以经过推导的)。

‘’随机选取"也可以换成更加严谨的方式,比如把数组划分为多个元素为5的小数组,然后找他们的中位数,再找出这些中位数的中位数,来当这个基准点。这样可以避免基准点选成了最大最小值这样的影响效率的情况。

(4)大整数乘法

将两个大整数分别拆分成两个部分(一半高位和一半低位),然后通过递归计算四个部分的乘积,最后将四个部分的乘积组合起来得到最终结果。

如下,multiply函数接收两个大整数xy作为参数,并返回它们的乘积。

在函数内部,首先判断输入的大整数是否已经缩小到只有一位,如果是,则直接返回它们的乘积。否则,将两个大整数分别拆分成高位和低位部分,然后通过递归计算四个部分的乘积。最后,将四个部分的乘积组合起来得到最终结果。

def multiply(x, y):
    if len(x) == 1 or len(y) == 1:
        return int(x) * int(y)

    n = max(len(x), len(y))
    m = n // 2

    a = x[:-m]
    b = x[-m:]
    c = y[:-m]
    d = y[-m:]

    ac = multiply(a, c)
    bd = multiply(b, d)
    ad_bc = multiply(str(int(a) + int(b)), str(int(c) + int(d))) - ac - bd

    return 10**(2*m)*ac + 10**m*ad_bc + bd

x = "12345678901234567890"
y = "98765432109876543210"
result = multiply(x, y)
print(result)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值