《算法导论》学习(三)---- 最大和子数组问题的分治方法


前言

本文讲解了最大和子数组问题,该问题所对应的算法应用范围广泛,在各种邻域都有应用。本文通过买股票的例子来讲解最大和子数组问题。给出了C语言代码和相关的编程细节供大家参考。并且阐释了算法背后的分治思想。


一、分治思想

1.分治策略

分治策略总共分为三步:

1.分解:将问题分解为一些子问题,子问题的形式与原问题一样,只是规模更小
2.解决:递归地求解出子问题。如果子问题的规模足够小,则停止递归,直接求解
3.合并:将子问题的解组合成原问题的解

2.核心思想

分治的核心思想就是将大规模问题拆解成最小规模问题,分别处理后,最后整合成问题的解。
分治策略的关键所在就是将大规模问题拆分成相似的小规模问题,理清递归过程的子问题解决,拆分,合并的逻辑细节,以生成递归函数。

3.优势

1.分治策略相比于传统的多重循环解决策略往往有执行时间少的优势。,这个优势的来源于分治策略所用的思想:空间换时间树的数据结构

2.在采用我们传统的多重循环方式时,我们处理数据都需要去照顾所有数据(因为计算机的顺序存储结构),如果遇到规模大的复杂问题就会造成时间上的面对极大浪费。

3.而采用递归的方式,就会在一定程度上减小这种浪费,当然这种减少是对于一定的大规模输入而言,它是一种函数增长量级上面的减小。但是由于分治更加的复杂,所以当规模小于一定程度,它的速度有可能不如传统方法。

二、最大和子数组问题

1.实际问题背景

我们进行股票交易的时候,可以估计一短时间的股票估值,我们总是喜欢最低价买入,最高价卖出,寻求利益最大化。在现实中我们很有可能遇到最高价在前,最低价在后的情况,这就需要我们不能简单的找最大值和最小值,而是需要对于一段时间的股票价值求最大交易利益。

2.最大和子数组问题

我们把实际问题可以抽象出来:

我们寻找一段时间的最大利润,那就可以抽象出一个数组。然后我们定义第 i i i个数据代表第i天和第 i − 1 i-1 i1天的价格差,数组的第一个数据是第2天的价格与第1天价格的差值。那么问题就转化为寻找数组的最大和的非空连续子数组。我们称这个问题为最大和子数组问题。

3.暴力求解

我们很容易设计出一种暴力求解办法,可以直接计算出每一种组合的和,然后得到和最大的组合以及最大和。时间复杂度为 Ω ( n 2 ) \Omega(n^2) Ω(n2)

4.分治方法

首先我们需要将数组不断二分。这就导致三种情况:

1.中间结点左边的最大子数组
2.中间结点右边的最大子数组
3.包含中间结点的最大子数组

情况1,2我们可以通过不断递归的求解,但是包含中间结点的最大子数组我们需要在合并的时候才有机会求解,那么我们需要另外编写一个求解情况3的函数。

当三种情况全部求解完成后,我们将最大的那个作为递归的返回值,进入到下一轮的递归,直到得到最后的解

三、C语言实现

1.代码

#include<stdio.h>
#include<stdlib.h>
#include<time.h>


#define SIZE 10


//定义一个结构体来表达子数组的边界,总合的信息 
typedef struct subarray{
	int left_l;//子数组的左边界索引 
	int right_l;//子数组的右边界索引 
	int max_s;//子数组的总和,即最大和 
}subarray;


//在所有包含中间点的子数组的范围内,找到最大和子数组 
subarray find_middle_maxarray(int *x,int a,int b,int c)
{
	subarray inf_array;
	int left=0;//子数组的左边界索引 
	int right=0;//子数组的右边界索引 
	/*
	中间存储变量
	*/ 
	int sum1=0; 
	int sum2=0;
	int sum=0;//最大和 
	int i=0;//循环索引
	//以中间结点左边起,寻找最大和子数组左边界 
	for(i=b;i>=a;i--)
	{
		sum+=x[i];
		if(sum>=sum1)
		{
			sum1=sum;
			left=i;
		}
	}
	sum=0;//重新刷新变量 
	//以中间结点左边起,寻找最大和子数组左边界 
	for(i=b+1;i<=c;i++)
	{
		sum+=x[i];
		if(sum>=sum2)
		{
			sum2=sum;
			right=i;
		}
	}
	sum=sum1+sum2;//赋最大和
	/*
	返回数组信息 
	*/
	inf_array.left_l=left;
	inf_array.right_l=right;
	inf_array.max_s=sum;
	return inf_array;
}


//给定一个数组,寻找它的最大和子数组 
subarray find_maxarray(int *x,int a,int b)
{
	subarray inf_array;
	subarray inf1;
	subarray inf2;
	subarray inf3;
	int middle=(a+b)/2;//计算中间结点
	/*
	如果单元素,那么就直接返回它本身 
	*/ 
	if(a==b)
	{
		inf_array.left_l=a;
		inf_array.max_s=x[a];
		inf_array.right_l=a;
		return inf_array;
	}
	/*
	如果是两个元素及以上
	那么就判断它的
	1.跨中间结点的最大和子数组
	2.中间结点左侧的最大和子数组
	3.中间结点右侧的最大和子数组 
	看看这三个哪一个最大,然后返回它。 
	*/
	else
	{
		inf1=find_maxarray(x,a,middle);//中间结点左侧最大和子数组 
		inf2=find_maxarray(x,(middle+1),b);//中间结点右侧最大和子数组 
		inf3=find_middle_maxarray(x,a,middle,b);//跨中间结点的最大和子数组
		/*
		哪种情况最大就返回其子数组信息 
		*/ 
		if((inf1.max_s>=inf2.max_s)&&(inf1.max_s>=inf3.max_s))
		{
			inf_array.left_l=inf1.left_l;
			inf_array.right_l=inf1.right_l;
			inf_array.max_s=inf1.max_s;
			return inf_array;
		}
		else if((inf2.max_s>=inf1.max_s)&&(inf2.max_s>=inf3.max_s))
		{
			inf_array.left_l=inf2.left_l;
			inf_array.right_l=inf2.right_l;
			inf_array.max_s=inf2.max_s;
			return inf_array;
		}
		else if((inf3.max_s>=inf1.max_s)&&(inf3.max_s>=inf2.max_s))
		{
			inf_array.left_l=inf3.left_l;
			inf_array.right_l=inf3.right_l;
			inf_array.max_s=inf3.max_s;
			return inf_array;			
		}
		else
		{
			inf_array.left_l=0;
			inf_array.right_l=0;
			inf_array.max_s=0;
			return inf_array;	
		}
	}
}


int main()
{
	subarray array_max;
	int left_limitation=0;
	int right_limitation=0;
	int max_sum=0;
	int i=0;
	int *x;
	int *y;
	x=(int *)malloc(SIZE*sizeof(int));//动态分配宏定义指定的内存空间 
	y=(int *)malloc(SIZE*sizeof(int));
	srand((unsigned)time(NULL));//生成与时间有关的随机种子 
	for(i=0;i<SIZE;i++)//生成宏定义指定大小的(-100)~~(+100)的随机数 
	{
		x[i]=rand()%100;
		y[i]=rand()%100;
		x[i]=x[i]-y[i];
	}
	for(i=0;i<SIZE;i++)//打印原始数据 
	{
		printf("%d ",x[i]);
	}
	printf("\n");
	//调用函数获取子数组信息 
	array_max=find_maxarray(x,0,(SIZE-1));
	//打印最大和子数组信息 
	printf("left:%d   right:%d   sum:%d\n",array_max.left_l+1,array_max.right_l+1,array_max.max_s);
	free(x);//释放动态生成的内存 
	free(y);//释放动态生成的内存
	return 0;
}

2.执行结果(举三例)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


四、总结

1.算法评价

分治方法显然要比暴力求解更加优秀。但是最大和子数组问题我们有一个线性时间的算法,不需要用到分治方法。

而分治方法的时间复杂度为 Θ ( n l o g n ) \Theta(nlogn) Θ(nlogn)
具体的求解方法在下面的文章中有讲解
链接: 《算法导论》学习(五)---- 分治策略的时间复杂度求解

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
分治算法可以用于求解一个数组中的最大值和最小值。具体的做法是将数组分成两个部分,分别求解左半部分和右半部分的最大值和最小值,然后比较左右两部分的最大值和最小值,得出整个数组最大值和最小值。 具体实现时,可以使用递归的方式,将数组不断地二分,直到只剩下一个元素,此时最大值和最小值都是它本身。然后将左半部分和右半部分的最大值和最小值比较,得出整个数组最大值和最小值。 下面是一个示例代码: ``` #include <iostream> using namespace std; struct Result { int max_val; int min_val; }; Result max_min(int arr[], int left, int right) { Result res; if (left == right) { res.max_val = arr[left]; res.min_val = arr[left]; } else if (left + 1 == right) { if (arr[left] > arr[right]) { res.max_val = arr[left]; res.min_val = arr[right]; } else { res.max_val = arr[right]; res.min_val = arr[left]; } } else { int mid = (left + right) / 2; Result left_res = max_min(arr, left, mid); Result right_res = max_min(arr, mid + 1, right); if (left_res.max_val > right_res.max_val) { res.max_val = left_res.max_val; } else { res.max_val = right_res.max_val; } if (left_res.min_val < right_res.min_val) { res.min_val = left_res.min_val; } else { res.min_val = right_res.min_val; } } return res; } int main() { int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10}; int n = sizeof(arr) / sizeof(arr[0]); Result res = max_min(arr, 0, n - 1); cout << "Max value: " << res.max_val << endl; cout << "Min value: " << res.min_val << endl; return 0; } ``` 在这个示例代码中,我们定义了一个名为 `Result` 的结构体,用来保存最大值和最小值。`max_min` 函数接收一个数组数组的左右下标,返回一个 `Result` 结构体。 在 `max_min` 函数的实现中,我们首先判断数组的长度,如果长度为 1,则最大值和最小值都是该元素本身;如果长度为 2,则比较两个元素的大小,得出最大值和最小值;否则,将数组二分成左右两部分,分别递归求解左右两部分的最大值和最小值,并将左右两部分的最大值和最小值进行比较,得出整个数组最大值和最小值。 在主函数中,我们定义了一个示例数组,然后调用 `max_min` 函数求解数组最大值和最小值,并输出结果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SigmaBull

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值