14-Si

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  7 随笔 :: 0 文章 :: 0 评论 :: 408 阅读

《算法导论》中讲分治策略的第一道例题就是一道"最大子数组问题",抽象出数学模型,题意是想求出数组中的连续子数组和的最大值。
很明显,暴力求解不加任何优化是O(n^2)的时间复杂度(暴力枚举左和右的组合,然后进行求和),在大数据范围时会有超时的风险,所以我们要进行算法的优化。

使用分治策略的求解方法

假设我们要寻找的子数组A[low, high]的最大子数组。使用分治技术意味着我们要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置mid,然后考虑求解A[low, mid]和A[mid+1, high]两个子数组。
A[low, high]的最大子数组必然是以下三种情况之一:

1.完全位于A[low, mid]当中
2.完全位于A[mid+1, high]当中
3.跨越了中点

也就是分别求出他们三种情况的最大值后找出三种情况的最大值就是A[low, high]的最大子数组。由于前两种情况可以简单进行递归求解,所以我们只考虑第三种情况的计算。
第三种情况单独列出来也并不难求,只要分别得出以mid为终点的最大子数组和以mid+1为起点的最大子数组,就可以在O(n)的时间复杂度求出满足条件的最大值。

接下来列出书中的伪代码:

FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
    left-sum = -∞
    sum = 0
    for i = mid downto low
        sum = sum + A[i]
        if sum > left-sum
            left-sum = sum
            max-left = i
    right-sum = -∞
    sum = 0
    for j = mid + 1 to high
        sum = sum + A[i]
        if sum > right-sum
            right-sum = sum
            max-right = j
    return (max-left, max-right, left-sum + right-sum)

FIND-MAXIMUM-SUBARRAY(A, low, high)
    if high == low
        return (low, high, A[low])
    else mid = ⌊(low+high)/2⌋
        (left-low, left-hight, left-sum) = 
            FIND-MAXIMUM-SUBARRAY(A, low, mid)
        (right-low, right-hight, right-sum) = 
            FIND-MAXIMUM-SUBARRAY(A, mid + 1, high)
        (cross-low, cross-high, cross-sum) = 
            FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
        if left-sum >= right-sum and left-sum >= cross-sum
            return (left-low, left-high, left-sum)
        elseif right-sum >= left-sum and right-sum >= cross-sum
            return (right-low, right-high, right-sum)
        else return (cross-low, cross-high, cross-sum)

将伪代码改成能直接表示出思路的注释:

typedef struct triad{
	int l, r, sum;
}triad;

triad FindMaxCrossingSubarray(int *A, int low, int mid, int high){
	//定义变量记录左半部分到mid的和的最大值并初始化
	//定义变量记录当前位置到mid的和并初始化为0
	//从mid到low依次相加,中间记录和的最大值以及对应的坐标
	//定义变量记录mid+1到右半部分的和的最大值并初始化
	//定义变量记录mid+1到当付钱位置的和并初始化为0
	//从mid到high依次相加,中间记录和的最大值以及对应的坐标
	//返回左半部分记录的坐标,右半部分记录的坐标和两部分的求和
}

triad FindMaximumSubarray(int *A, int low, int high){
	//当左右指针相遇,说明已经触底,可以直接返回左右坐标以及剩余数的值
	//当左右指针没有相遇时,说明还可以继续向下递归
	//找到当前子数组的中值mid
	//递归求A[low, mid]的最大子数组和
	//递归求A[mid+1, high]的最大子数组和
	//调用FindMaxCrossingSunarray函数求出通过中点的子数组和的最大值
	//返回三种情况中的最大值
}

接下来我们将注释对应的代码填入来完成函数:

typedef struct triad{
        /*
          l:左值
          r:右值
          sum:求和
        */
	int l, r, sum;
}triad;

triad FindMaxCrossingSubarray(int *A, int low, int mid, int high){
        /*
        A:数组
        low:左端点
        mid:中间点
        high:右端点
        */
	int leftSum = -1e9;
	int sum = 0;
	int maxLeft, maxRight;
	for(int i = mid-1; i >= low; i--){
		sum += A[i];
		if(sum > leftSum){
			leftSum = sum;
			maxLeft = i;
		}
	}
	int rightSum = -1e9;
	sum = 0;
	for(int i = mid; i < high; i++){
		sum += A[i];
		if(sum > rightSum){
			rightSum = sum;
			maxRight = i;
		}
	}
	triad res;
	res.l = maxLeft;
	res.r = maxRight;
	res.sum = leftSum+rightSum;
	return res;
}

triad FindMaximumSubarray(int *A, int low, int high){
        /*
        A:数组
        low:左端点
        high:右端点
        */
	triad res;
	if(high - 1 == low){
		res.l = low;
		res.r = high;
		res.sum = A[low];
		return res;
	}
	else{
		int mid = (low + high) / 2;
		triad resLeft, resRight, resCross;
		resLeft = FindMaximumSubarray(A, low, mid);
		resRight = FindMaximumSubarray(A, mid, high);
		resCross = FindMaxCrossingSubarray(A, low, mid, high);
		if(resLeft.sum >= resRight.sum && resLeft.sum >= resCross.sum){
			return resLeft;
		}
		else if(resRight.sum >= resLeft.sum && resRight.sum >= resCross.sum){
			return resRight;
		}
		else{
			return resCross;
		}
	}
}
posted on   14-Si  阅读(170)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示