python分治法、暴力法、动态规划求最大子数组

本文通过实验对比了暴力法、分治法和动态规划三种算法解决最大子数组问题的效率,结果显示动态规划最快,分治法次之,暴力法则最慢。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《算法导论》第四章后习题4.1-3

交叉点的计算通过20次求两种方法的最小时间差,然后取平均值求得。
以50000个随机元素的列表为例:
暴力法需要约两分钟,分治法需要0.19s,而动态规划只需要0.013s。(单次数据)

import math
import time
import random


def force(profit):
    """
    use forceful method
    :param profit: profit list
    :return: the max profit
    """
    maxprofit = 0
    for i in range(len(profit)):
        for j in range(i + 1, len(profit)):
            if profit[j] - profit[i] > maxprofit:
                maxprofit = profit[j] - profit[i]
    return maxprofit


def fluctuated(profit):
    """
    calculate profit list to fluctuated list
    :param profit: profit list
    :return: fluctuated list
    """
    a = []
    for i in range(len(profit) - 1):
        a.append(profit[i + 1] - profit[i])
    return a


def dp(v):
    """
    calculate profit list to fluctuated list
    :param v: fluctuated list
    :return: the max profit
    """
    add = v[0]
    res = add
    for i in range(1, len(v)):
        if add > 0:
            add += v[i]
        else:
            add = v[i]
        res = max(res, add)
    return res


# class Result:
#     """
#     ans : max sum
#     sub : max subarray
#     low
#     high
#     """
#     ans = 0
#     low = high = 0
#
#     def __init__(self, ans, low, high):
#         self.low = low
#         self.high = high
#         self.ans = ans


def find_cross_subarray(v, low, high):
    """
    find cross subarray
    :param v: volatility list
    :param low: lower-bound
    :param high: upper-bound
    :return: an object includes subarray and max sum
    """
    lsum = -9999999999
    add = 0
    mid = math.floor((low + high) / 2)
    max_right = max_left = 0
    for i in range(mid, low - 1, -1):
        add += v[i]
        if add > lsum:
            lsum = add
            max_left = i
    rsum = -99999999999
    add = 0
    for j in range(mid + 1, high):
        add += v[j]
        if add > rsum:
            rsum = add
            max_right = j
    return lsum + rsum, max_left, max_right


def find_max_sub(v, low, high):
    """
    :param v: a fluctuated list
    :param low: list's lower-bound
    :param high: list's upper-bound
    :return: the max subarray-info
    """

    if high == low:
        return v[low], low, high
    else:
        mid = math.floor((low + high) / 2)
        (left_sum, left_low, left_high) = find_max_sub(v, low, mid)
        (right_sum, right_low, right_high) = find_max_sub(v, mid + 1, high)
        (cross_sum, cross_low, cross_high) = find_cross_subarray(v, low, high)
        # compare
        if left_sum >= right_sum and left_sum >= cross_sum:
            return left_sum, left_low, left_high
        elif right_sum >= left_sum and right_sum >= cross_sum:
            return right_sum, right_low, right_high
        else:
            return cross_sum, cross_low, cross_high

def get_random_list(size, lower, upper):
    """
    generate a random array
    :param size: size
    :param lower: bound
    :param upper: bound
    :return: random array
    """
    arr = []
    i = 0
    while i < size:
        arr.append(random.randint(lower, upper))
        i += 1
    return arr


def exe_time(size, times):
    """
    generate a <size>-length random array,and test two method by <times> times;
    :param size: random array's size
    :param times: execute times
    :return: two methods' time diff
    """
    random_arr = get_random_list(size, 10000, 1000000)
    f_arr = fluctuated(random_arr)
    force_start = time.time()
    for i in range(times):
        force(random_arr)
    force_end = time.time()
    force_last = force_end - force_start
    div_start = time.time()
    for i in range(times):
        find_max_sub(f_arr, 0, len(f_arr) - 2)
    div_end = time.time()
    div_last = div_end - div_start
    # print("Force-last:" + "%s" % force_last)
    # print("div-last:" + "%s" % div_last)
    return abs(div_last - force_last)


def test_intersection(times):
    """
    find two methods' crosspoint
    execute 20 times and calculate the averager
    :return: intersection
    """
    i = 0
    add_times = 0
    while i < 20:
        j = 60
        inter = j
        min_diff = exe_time(j, times)
        while j > 10:
            j -= 1
            diff = exe_time(j, times)
            if diff < min_diff:
                min_diff = diff
                inter = j
            # print("diff:%-30s" % diff, "times:%s" % i)
        print("intersections:", inter)
        add_times += inter
        i += 1
    return add_times / 20


def main():
    print("Average:", test_intersection(70))

    # print(exe_time(10, 10000))
    # RandomArr = get_random_list(50000, 10000, 1000000)
    # FArr = fluctuated(RandomArr)
    # print(RandomArr)
    # t_start = time.time()
    # Max = force(RandomArr)
    # t_end = time.time()
    # print("%-15s" % "Method", "%-15s" % "Result", "Time")
    # print("%-15s" % "Force", "%-15s" % Max, t_end - t_start)
    #
    # t_start = time.time()
    # (res, l, h) = find_max_sub(FArr, 0, len(RandomArr) - 2)
    # t_end = time.time()
    # print("%-15s" % "Div_Recursion", "%-15s" % res, t_end - t_start)
    #
    # t_start = time.time()
    # Max = dp(FArr)
    # t_end = time.time()
    # print("%-15s" % "DP", "%-15s" % Max, t_end - t_start)


if __name__ == '__main__':
    main()

<think>好的,我现在需要帮助用户理解如何用分治法解决最大子数组和问题。首先,我要回忆一下分治法的基本思想,也就是将问题分解成更小的子问题,解决后再合并结果。最大子数组和问题通常是指在一个数组中找到一个连续的子数组,使其和最大。 用户可能已经知道这个问题可以用暴力或者动态规划来解决,但分治法实现可能不太清楚。我需要先理清楚分治法在这个问题上的步骤。根据引用内容,分治法需要将数组分成左右两部分,分别解左右的最大子数组,然后处理跨越中点的最大子数组,最后比较三者取最大。 接下来,我需要详细解释每个步骤。首先是分解,把数组从中间分成两部分。左右子数组最大子数组可以通过递归继续分解,直到基本情况,也就是只有一个元素时,最大子数组就是该元素本身。然后是合并步骤,需要处理跨越中点的子数组。这里可能需要详细说明如何计算跨越中点的最大子数组,比如从中点向左和向右扩展,分别找到最大和,然后合并起来。 用户可能需要具体的例子来理解这个过程,比如像示例中的数组[-3, 1, 5, -2, 3, -5]。分治法的递归过程如何分解这个数组,如何计算左右和跨中点的最大值,最后合并得到结果。此外,时间复杂度的分析也很重要,分治法的时间复杂度通常是O(n log n),需要解释为什么是这个结果。 同时,要注意代码实现的细节。比如,分治法的递归终止条件,如何处理左右子数组的递归调用,以及如何实现跨中点的情况。可能需要参考引用中的Java或Python代码结构,但用户可能需要Python实现,所以需要转换成Python代码。 还要考虑到用户可能的疑问,比如为什么分治法的时间复杂度比动态规划的O(n)更差,但分治法可能在特定情况下有用,比如并行处理或者作为教学示例。同时,需要指出分治法在处理跨越中点的情况时的关键点,确保用户理解合并步骤的正确性。 最后,生成相关问题时要围绕分治法的其他应用、优化方、与其他算法的比较等,这样用户能进一步扩展知识。</think>### 分治法解决最大子数组和问题的原理及实现 #### 1. 问题定义 最大子数组和问题要在给定数组 $A$ 中找到一个连续的子数组,使得其元素之和最大。例如,数组 $[-3, 1, 5, -2, 3, -5]$ 的最大子数组为 $[1, 5, -2, 3]$,和为 $7$。 #### 2. 分治法核心思想 分治法将数组划分为左右两部分,递归解左半部分、右半部分的最大子数组和,同时计算**跨越中点**的最大子数组和,最终取三者中的最大值[^1][^2]。 #### 3. 算法步骤 1. **分解** 将数组从中间位置 $mid$ 划分为左右两个子数组:$A[low, mid]$ 和 $A[mid+1, high]$。 2. **递归解** - 左子数组最大和:$left\_max = \text{findMaxSubarray}(A, low, mid)$ - 右子数组最大和:$right\_max = \text{findMaxSubarray}(A, mid+1, high)$ 3. **跨越中点的最大子数组和** - **向左扩展**:从中点向左累加,记录最大和 $left\_sum$ - **向右扩展**:从中点向右累加,记录最大和 $right\_sum$ - 跨越中点的最大和为 $cross\_max = left\_sum + right\_sum$ 4. **合并结果** 最终结果为 $\max(left\_max, right\_max, cross\_max)$。 #### 4. 关键代码实现Python) ```python def max_subarray(nums): def find_max_crossing(low, mid, high): # 向左扩展 left_sum = float('-inf') current = 0 for i in range(mid, low-1, -1): current += nums[i] if current > left_sum: left_sum = current # 向右扩展 right_sum = float('-inf') current = 0 for i in range(mid+1, high+1): current += nums[i] if current > right_sum: right_sum = current return left_sum + right_sum def helper(low, high): if low == high: return nums[low] mid = (low + high) // 2 left_max = helper(low, mid) right_max = helper(mid+1, high) cross_max = find_max_crossing(low, mid, high) return max(left_max, right_max, cross_max) return helper(0, len(nums)-1) ``` #### 5. 时间复杂度分析 - **递推关系**:$T(n) = 2T(n/2) + O(n)$(递归分解左右子问题 + 线性时间处理跨中点情况) - **最终复杂度**:$O(n \log n)$,优于暴力的 $O(n^2)$,但弱于动态规划的 $O(n)$[^3]。 #### 6. 示例分析 以数组 $[-3, 1, 5, -2, 3, -5]$ 为例: 1. 分解到最小单元后,递归计算左半部分 $[-3, 1, 5]$ 和右半部分 $[-2, 3, -5]$ 的最大子数组和。 2. 计算跨越中点(例如 $mid=2$)的最大子数组,即从 $5$ 向左扩展到 $1$,向右扩展到 $-2, 3$,总和为 $1+5+(-2)+3=7$。 3. 最终合并结果为 $\max(左=6, 右=3, 跨中点=7)=7$。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值