用分治策略求最大子数组

本文介绍了一种利用分治技术寻找数组中最大子数组的方法。通过对数组进行递归划分,分别找出左侧、右侧及跨中点的最大子数组,最终确定整个数组的最大子数组及其和。

最大子数组: 有一数组A,求出A的和最大 的子数组,称这个连续子数组称为最大子数组。

因为一个数组可能有多个子数组达到最大和,在这里是只求出一个最大子数组。

如下面的一个数组。



假定我们查找A[low...high]中的最大子数组,使用分治技术我们可以分成两个规模尽量相等的子数组,

也就是说找到子数组的中央; 即:

mid = low + (high - low) / 2;<span style="font-family: Arial, Helvetica, sans-serif;"> </span>
那么最大子数组A[i...j]可能所有三种情况 

1. 完全位于左子数组 A[low...mid] ;  low <=i <=j <= high;

2.完全位于右子数组 A[mid+1...high]   mid<i <=j <=high;

3.跨越中点,因此 low <= i< j <= high.

经过分析, A[low...high]的一个最大子数组所处的位置,必然是上述三种情况之一。

实际上,就是三种情况下,求出的最大的那个。

通过递归求解 A[low...mid], A[]mid+1...high]中的最大子数组,因为这两个子问题仍是最大的子数组问题。

然后再求跨越中点的最大子数组,然后在这再种情况下选取最大值。


首先先完全求出跨越中点的最大子数组。

int find_max_cross_maxsum(int *a, int low, int mid, int high){
	int left_sum = -65535;  //存放一个数组 a 不可能出现的最小值,用于比较。
	int right_sum = -65535;
	int sum = 0;
	int i = 0;
	for(i = mid; i>=low; i--){  
		sum += a[i]; 
		if( sum > left_sum ){
			left_sum = sum;
		}
	}
	sum = 0;
	for(i=mid+1; i<=high; i++){
		sum+=a[i];
		if( sum > right_sum){
			right_sum = sum;
		}
	}

	return left_sum + right_sum;
}	


递归求出最大子数组。

int find_max_subarray(int *a, int low, int high){
	int left_sum, right_sum, cross_sum, mid;

	if(low == high){
		return a[low]; // 递归结束条件。 
	}
	else{
		mid = low + (high-low) / 2;
		left_sum = find_max_subarray(a, low, mid); //求出左子数组最大子数组。 
		right_sum = find_max_subarray(a, mid+1, high);//求出右子数组最大子数组。
		cross_sum = find_max_cross_maxsum(a, low, mid, high); //求出跨越中点的最大子数组
        }
//三种情况,最大的那个就是最大子数组。
	if(left_sum >= right_sum && left_sum >=cross_sum){
		return left_sum;
	}else if(right_sum >=left_sum && right_sum >= cross_sum){
		return right_sum;
	}else{
		return cross_sum;
	}
}


测试代码:

int main(){
	int a[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7 , 12, -5, -22 , 15};
	int max_sum = find_max_subarray(a, 0, sizeof(a)/sizeof(a[0]) - 1);
	printf("%d\n", max_sum);
}
结果返回  43, 即 18, 20 ,-7 , 12 这四个连续位置的和,

如果想具体知道是那个下标到那个下标,可以自行修改。



### 分治法解决最大子数组和问题 #### 什么是最大子数组和问题? 最大子数组和问题是寻找一个整数数组中的连续子数组,使其元素之和达到最大值。此问题可以通过多种方法解决,其中分治法是一种经典的方法之一。 --- #### 分治法的核心思想 分治法通过将原问题分解成更小的子问题来逐步解决问题。对于最大子数组和问题,可以将其划分为三个部分: 1. **左半边的最大子数组**:完全位于中间位置左侧的部分。 2. **右半边的最大子数组**:完全位于中间位置右侧的部分。 3. **跨越中间位置的最大子数组**:包含中间位置及其两侧的一部分。 最终的结果将是上述三种情况中的最大值[^1]。 --- #### C语言实现代码示例 以下是基于分治最大子数组和问题的C语言实现: ```c #include <stdio.h> #include <limits.h> // 找到跨过中间点的最大子数组和 int findMaxCrossingSubarray(int arr[], int low, int mid, int high) { int leftSum = INT_MIN; int sum = 0; // 向左扩展计算左边的最大和 for (int i = mid; i >= low; i--) { sum += arr[i]; if (sum > leftSum) { leftSum = sum; } } int rightSum = INT_MIN; sum = 0; // 向右扩展计算右边的最大和 for (int j = mid + 1; j <= high; j++) { sum += arr[j]; if (sum > rightSum) { rightSum = sum; } } return leftSum + rightSum; } // 主函数实现分治法 int maxSubArrayRecursive(int arr[], int low, int high) { if (low == high) { // 只有一个元素的情况 return arr[low]; } else { int mid = (low + high) / 2; int leftSum = maxSubArrayRecursive(arr, low, mid); // 左侧最大子数组和 int rightSum = maxSubArrayRecursive(arr, mid + 1, high); // 右侧最大子数组和 int crossSum = findMaxCrossingSubarray(arr, low, mid, high); // 跨越中间的最大子数组和 if (leftSum >= rightSum && leftSum >= crossSum) { return leftSum; } else if (rightSum >= leftSum && rightSum >= crossSum) { return rightSum; } else { return crossSum; } } } // 接口函数调用 int maxSubArray(int arr[], int size) { return maxSubArrayRecursive(arr, 0, size - 1); } ``` --- #### 代码解释 1. 函数 `findMaxCrossingSubarray` 计算跨越中间位置的最大子数组和。它分别向左和向右遍历数组,记录下最大的累加和并返回两者的总和。 2. 函数 `maxSubArrayRecursive` 是递归实现的主要逻辑。当数组只有一个元素时,直接返回该元素;否则,继续划分左右两个子数组,并比较它们各自的最大子数组和以及跨越中间位置的最大子数组和。 3. 函数 `maxSubArray` 提供了一个简单的接口,方便外部调用。 --- #### 时间复杂度分析 分治法的时间复杂度为 \(O(n \log n)\),因为每次都将数组分成两半处理,并且在合并阶段需要线性时间扫描整个数组以找到跨越中间位置的最大子数组和。 --- #### 动态规划对比 虽然分治法能够有效解决问题,但在实际应用中,Kadane 算法通常更为高效,因为它仅需一次遍历即可完成任务,时间复杂度为 \(O(n)\)[^2]。然而,在学习算法设计模式时,理解分治法仍然非常重要。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值