【算法导论学习】分治法求最大子数组

分治法

所谓分治法,就是把问题不断分割变小,常见的是把数组分割为两部分,然后分别计算,将大问题变成小问题。

问题分析方向

在这里插入图片描述
这一类问题主要用的是递归的方法,所以时间复杂度普遍为O(nlogn)

对时间复杂度的分析

在这里插入图片描述

样例分析

接下来分析最大子数组问题

问题分析

分解问题

求最大子数组无非就是求数组内某段连续的数相加,那么通过分治的思想,我们把问题拆开。

将一个数组变成两个数组,然后分别找两个数组中最大的子数组,然后再比较大小。

这么一想就会觉得问题简单了不少。但是仔细一想不对啊,要是最大子数组在两个部分中都有怎么办呢?,也就是在
于是这样我们就把问题分解为三个部分:
在这里插入图片描述

解决问题

对于仅位于两边的子数组来说好解决

  1. 在分解后的数组长度大于1的时候继续递归
  2. 要是到了递归尾了,那就直接输出最后剩下那一个数组元素就好了。

但是要是是中间那种情况怎么办呢?
这个好解决,要这么想,既然是经过最中间的元素,那么最大子数组里面必然包括最中间那个元素,那么直接从中间向两边遍历就好了。

通过不断累加计算寻找两边是否有更好的情况。
在这里插入图片描述

合并问题

对于某一个子数组来说,递归所返回的子数组就是最大子数组,其中返回的子数组就是目前这个某数组的最大子数组,因为返回的子数组就是从他的左边选出来的数组和右边选出来的子数组以及中间选出来的子数组中挑选的最大数组。
同理对于他的父数组来说也是这样。应该是类似于数学归纳法的想法。

对代码的设想

中间部分的处理
  1. 伪代码(来自算法导论)
    在这里插入图片描述
  2. C语言实现
mid_message find_mid(int A[], int low, int mid, int high)
{
	mid_message midx;
	int left_sum = -inf;
	int sum = 0;
	for(int i = mid ; i >= low ; i--)
	{
		sum = sum + A[i];
	    if (sum > left_sum)
	    {
	    	left_sum = sum;
	        midx.max_left = i;
		} 
	}
	
	int right_sum = -inf;
	sum = 0;
	for(int j = mid + 1 ; j <= high ; j++)
	{
		sum = sum + A[j];
		if (sum > right_sum)
	    {
	    	right_sum = sum;
	        midx.max_right = j;
		}
	}
	midx.sum = left_sum + right_sum; 
	return midx;
}
递归部分

递归部分很简单,无非就是设置一下递归出口以及选择子数组的条件

  1. 伪代码(来自算法导论)
    在这里插入图片描述

  2. C语言实现

mid_message find_max(int A[], int low, int high)
{
	mid_message midx;
	if (high == low)
	{
		midx.max_left = low;
		midx.max_right = high;
		midx.sum = A[low];
		return midx;
	}
	else
	{
		int mid = (low+high) / 2;
	    mid_message midxl = find_max(A, low, mid);
	    mid_message midxr = find_max(A, mid+1, high);
	    mid_message midxm = find_mid(A, low, mid, high);
	    int max = Max(midxr.sum, midxl.sum, midxm.sum);
	    if(midxl.sum == max)
	    	return midxl;
		else if(midxr.sum == max)
			return midxr;
	    else
	        return midxm;
	}	    
}
时间复杂度分析

按照分治法的时间复杂度分析,可以写出下列方程
在这里插入图片描述
化简得出符合O(nlogn)

完整代码

我加了注释,基本上复制到自己的编辑器就可以看懂

#include<stdio.h>

//设置一个无限大值 
#define inf 999999

//定义的一个匿名结构体(我也不大记得匿名结构体怎么用了) 
typedef struct mid_message{
	//最大子数组的左边界,右边界,最大子数组的总值 
	int max_left;
	int max_right;
	int sum;
}midk;

//自己写的一个求3个数中最大值的函数 
int Max(int a, int b, int c)
{
	int max = a;
	if(b > max)
		max = b;
	if(c > max)
		max = c;
	return max;
}

//求经过中点的最大子数组,返回结构体 
mid_message find_mid(int A[], int low, int mid, int high)
{
	mid_message midx;
	
	//初始化中点左边的子数组值 
	int left_sum = -inf;
	int sum = 0;
	
	//通过循环不断向左加,然后一直与left_sum比较 
	for(int i = mid ; i >= low ; i--)
	{
		sum = sum + A[i];
	    if (sum > left_sum)
	    {
	    	left_sum = sum;
	        midx.max_left = i;
		}
	        
	}
	
	//与左边的操作相同 
	int right_sum = -inf;
	sum = 0;
	for(int j = mid + 1 ; j <= high ; j++)
	{
		sum = sum + A[j];
		if (sum > right_sum)
	    {
	    	right_sum = sum;
	        midx.max_right = j;
		}
	}
	   
	midx.sum = left_sum + right_sum; 
	return midx;
}

mid_message find_max(int A[], int low, int high)
{
	mid_message midx;
	
	//递归出口 
	if (high == low)
	{
		midx.max_left = low;
		midx.max_right = high;
		midx.sum = A[low];
		
		return midx;
	}
	else
	{
		int mid = (low+high) / 2;
		//左右中,左右用递归,中用 find_mid
	    mid_message midxl = find_max(A, low, mid);
	    mid_message midxr = find_max(A, mid+1, high);
	    mid_message midxm = find_mid(A, low, mid, high);
	    
	    int max = Max(midxr.sum, midxl.sum, midxm.sum);
	    
	    //返回最大的子数组 
	    if(midxl.sum == max)
	    	return midxl;
		else if(midxr.sum == max)
			return midxr;
	    else
	        return midxm;
	}	    
}

int main() 
{
	//测试数据(从别人那复制来的) 
	int A[] = {-500,10,5,5,2,-3,-29,-10,-50,22,-23,10,150,1,9,-900,-26,3,99,7};
	mid_message max = find_max(A, 0, 19);
	printf("%d\n",max.max_left);//11
	printf("%d\n",max.max_right);//14
	printf("%d\n",max.sum);//170
}
  • 10
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值