数据结构与算法分析 2-算法分析-以“最大的子序列和”为例

一、数学基础

1.定义

 2.定义的解释

3.级别的判断方法

 4.结论

 二、以“最大的子序列和”为例

1.运行时间的计算规定

    为了简化分析,因此忽略前导常数(leading constants),同时抛弃低阶项(低阶项对于算法的整体运行时间来说影响较小)。

前导常数是指在进行运行时间计算时,忽略的常数因子。具体来说,当我们分析算法的运行时间时,我们通常只关注算法的增长率,而不关注具体的常数值。比如:

执行一个固定数量的操作,例如执行1000次循环,那么前导常数是1000。

执行一次操作需要1秒钟,那么前导常数是1。

进行一次数据读取需要10毫秒,那么前导常数是10。

    声明不计时间。赋值、加减乘除、初始化、测试条件、return都记1个单位时间。

2.运行时间的计算法则

for循环

for (i = 1; i <= N; i++)
	p += i * i * i;

②嵌套for循环

    在一组嵌套循环内部的一条语句总的运行时间为该语句的运行时间乘以该组所有的for循环的大小的乘积。

for (i = 0; i < N; i++)
	for (j = 0; j < N; j++)
	k++;

③顺序语句

将各个语句的运行时间求和即可(其中的最大值就是所得的运行时间)

for (i = 0; i < N; i++)
	A[i] = 0;
for (i = 0; i < N; i++)
	for (j = 0; j < N; j++)
		A[i] += A[j] + i + j;

 

if-else语句

    运行时间取if/else中运行时间较长的。

⑤递归

简单的递归可以化成for循环:

int Factorial(int N)
{
	if (N <= 1)
		return 1;
	else
		return N * Factorial(N - 1);
}

 

int Fib(int N)
{
	if (N <= 1)
		return 1;
	else
		return Fib(N - 1) + Fib(N - 2);
}

 

3.四种算法的具体实现与对比

方法① 三重嵌套循环

#include <stdio.h>
int MaxSubsequenceSum(const int A[], int N)
{
	int ThisSum, MaxSum, i, j, k;
	MaxSum = 0;
	for (i = 0; i < N; i++)
	{
		for (j = i; j < N; j++)
		{
			ThisSum = 0;
			for (k = i; k <= j; k++)
				ThisSum += A[k];
			if (ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
	}
	return MaxSum;
}
int main()
{
	int n,maxsum=0;
	int arr[] = { -2,11,-4,13,-5,-2 };
	n = sizeof(arr) / sizeof(arr[0]);
	maxsum = MaxSubsequenceSum(arr, n);
	printf("%d", maxsum);
	return 0;
}

 使用const int A[]的原因是为了指明函数MaxSubsequenceSum中的参数A是一个常量数组,即在函数内部不会改变数组中的元素的值。这有助于编译器进行优化,并避免无意中修改传入的数组。这样做是为了确保函数不会意外地修改调用者传入的数组内容,增强代码的安全性和可维护性。

方法② 两层嵌套循环

int MaxSubsequenceSum(const int A[], int N)
{
	int ThisSum, MaxSum, i, j, k;
	MaxSum = 0;
	for (i = 0; i < N; i++)
	{
		ThisSum = 0;
		for (j = i; j < N; j++)
		{
			ThisSum += A[j];
			if (ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
	}
	return MaxSum;
}

方法①设置了两层起始点,i为j的起始点,然后再让k遍历完一个整体,而j又会再次遍历,这其中就有重复的,这是不必要的。方法②中仅设置一个起始点i,j直接开始遍历,减少了一个for循环。

方法③ 递归

#include <stdio.h>

int Max3(int a, int b, int c)
{
	return (a > b) ? (a > c ? a : c) : (c > b ? c : b);
}

static int MaxSubSum(const int A[], int left, int right)
{
	int maxleftsum, maxrightsum, maxleftbordersum = 0, maxrightbordersum = 0;  // 左/右子数组的最大和,左/右边界子数组的最大和
	int leftbordersum = 0, rightbordersum = 0, center, i;  // 当前左/右边界子数组的和,中间元素的位置,循环变量

	if (left == right)  // 基准情形
	{
		if (A[left] > 0)
			return A[left];  // 如果只有一个元素,非负时就返回当前的值
		else
			return 0;  // 否则返回0
	}

	center = (left + right) / 2;  // 中心
	maxleftsum = MaxSubSum(A, left, center);  // 左边递归,从left到center
	maxrightsum = MaxSubSum(A, center + 1, right);  // 右边递归,从center+1到right

	for (i = center; i >= left; i--)  // 计算从center开始,向左的最大和
	{
		leftbordersum += A[i];
		if (leftbordersum > maxleftbordersum)
			maxleftbordersum = leftbordersum;  // 只要向左求和还在变大,就将其和leftbordersum重新赋值给maxleftbordersum
	}
	for (i = center + 1; i <= right; i++)  // 计算从center开始,向右的最大和
	{
		rightbordersum += A[i];
		if (rightbordersum > maxrightbordersum)
			maxrightbordersum = rightbordersum;  // 只要向右求和还在变大,就将其和rightbordersum重新赋值给maxrightbordersum
	}
	return Max3(maxleftsum, maxrightsum, maxleftbordersum + maxrightbordersum);
	
}

int MaxSubsequenceSum(const int A[], int N)
{
	return MaxSubSum(A, 0, N - 1);
}

整体思想是:最大子序列只可能在三处位置出现:①全在左半部分②全在右半部分③跨越中心。(分而治之)

对于①②情况,可以直接递归求解。

对于③情况,因为必有中心两侧的数字在最大子序列中(否则变成①②情况),既然已知这两个数在最大子序列中,可以向前/后求得前半/后部分的最大和,加起来从而得到整体的最大和。

依旧以-2,11,-4,13,-5,-2为例。

左侧-2,11,-4最大为-2+11=9

右侧13,-5,-2最大为13

跨越中心的为-4,13。从-4向左,最大为11-4=7。从13向右,最大的是13。从而7+13=20。

比较9,13,20,三者最大的是20。

方法④ 动态规划 

int MaxSubsequenceSum(const int A[], int N)
{
	int ThisSum, MaxSum, j;
	ThisSum = MaxSum = 0;
	for (j = 0; j < N; j++)
	{
		ThisSum += A[j];
		if (ThisSum > MaxSum)
			MaxSum = ThisSum;
		else if (ThisSum < 0)
			ThisSum = 0;
	}
	return MaxSum;
}

方法⑤ 数学分析 

int MaxSubsequenceSum(const int A[], int N)
{
	int MinSum, MaxSum, Sum, i;
	MaxSum = A[0];
	MinSum = Sum = 0;
	for (i = 0; i < N; i++)
	{
		Sum += A[i];
		MaxSum = MaxSum > (Sum - MinSum) ? MaxSum : (Sum - MinSum);
		MinSum = MinSum < Sum ? MinSum : Sum;
	}
	return MaxSum;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值