最近又恰好遇到了一些让人兴奋的问题,那就是之前在线性代数的n阶行列式计算学习到由序列中三项的递推关系式求出序列的通项公式的线性代数方法即差分方程,将他应用到用C语言的斐波那契数列的通项公式实现计算斐波那契数列中第n项的问题的时候,紧随其后我就在想,由三项间差分方程到通项公式,假如别人所给的序列不是序列中三项间的关系,假如说是序列中k项(k>5)间的关系的时候用特征方程再去实现便不那么好求我就在想能不能尝试“用数列中的F[i-1]+F[i-2]+...+F[i-k]赋给F[i]”这样就是说做一个“区间求和”然后输出,但最后发现这其实多此一举,也不像用质因数分解法求gcd和lcm那么具有“元”意义,我的其他文章中我会说明这个想法的鸡肋之处,我会在写完这一篇文章之后专门紧随其后就出一期关于“递推、递归、迭代、差分方程”之间的联系与讨论的文章(就是这篇附上超链接的文章的最后对这个鸡肋之处进行了说明,有兴趣的朋友可以点击浏览。)这里我先来搞定从上面研究的过程中引申出来的最基础的算法一维数组范围内的“前缀和算法”、“区间求和算法”、高效更新数组arr的[l,r]区间的两种方法与背后的数学逻辑以及高效更新区间的应用意义。
一、先来讨论“前缀和算法”与“容斥原理”从而顺水推舟得到“区间和算法”,本篇文章的研究范围先只讨论一维数组的实现:
“前缀和算法” 代码实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//#include "algo_sumofprefix_.h"//求区间和 差分数组 才用到的我定义的头文件,里面存放的算法步骤
#define Maxsize 50
int main()
{
srand((unsigned int)time(NULL));
int arr[Maxsize];//数组定义内一定要是常量表达式,否则不具跨平台特性
int posa=0;
int l, r;//定义左游标和右游标
printf("请输入您要多少个数据:");
scanf("%d", &posa);
for (int i = 0; i < posa; i++)
{
arr[i] = rand() % 50;//真随机获取50(不包含50)的随机数
}
printf("arr数组中的数据为:");
for (int i = 0; i < posa; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", arr[i]);
}
int sum[Maxsize];//该数组中的第i项存放arr数组中的前i项和
int poss = 0;
//求arr[i]前缀和放入sum[i]中
for (int i=0; i < posa; i++)
{
if (i == 0)
{
sum[0] = arr[0];
poss++;
}
else
{
sum[i] = sum[i - 1] + arr[i];//arr前缀和核心代码
poss++;
}//核心代码
}
printf("\n前n项和的数组中的数据为:");
for (int i = 0; i < poss; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", sum[i]);
}
}
好了我们现在得到了arr的前缀和数组sum,下面我就要来实现“区间求和”算法了,在这之前我想先介绍一下数学当中的“容斥原理”:
“区间和算法”代码片段实现:
//接着上面的main函数中的sum、arr数组
printf("\n请输入您要计算arr数组(从0开始)的区间和的左边界与右边界:");
printf("\nleft="); scanf("%d", &l);
printf("right="); scanf("%d", &r);
int lrsum = 0;
lrsum=sum[r] - sum[l - 1];//arr[l]+arr[l+1]+...+arr[r];容斥原理
printf("arr中第%d个位置到第%d个位置的和为:%d",l,r,lrsum);//测试区间求和代码
二、差分数组的定义与用它来实现对arr的[l,r]区间修改:
若存在一个数组diff[Maxsize],使得diff[i]==arr[i]-arr[i-1],i>=1,i属于自然数,i=0是补充定义diff[0]=arr[0],定义diff[Maxsize]为arr[Maxsize]的差分数组。
下面给出对arr的[l,r]区间比循环更快速的修改的方法一:若要对arr数组[l,r]区间修改比如加上val则仅需要对diff[l]+val同时diff[r+1]-val再求新的diff数组的前缀和即为区间修改后的arr的结论和实验结果,以及紧随其后我对这个结论的理解(附加说明:以上对arr数组[l,r]区间加上val的过程可以记作差分标记:[l,r]+val):
修改方法一的代码实现:
需求:
欲使arr中[2,7]加上5
欲使arr中[5,9]减去7
欲使arr中[3,4]加上3
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//#include "algo_sumofprefix_.h"//我直接在代码片段中写为全局函数了,就不给文件的整体了
#define Maxsize 50
void Interval_add(int d[],int l, int r, int v)//区间修改操作
{
d[l] += v;
d[r + 1] -= v;
}
int main()
{
srand((unsigned int)time(NULL));
int arr[Maxsize];//数组定义内一定要是常量表达式,否则不具跨平台特性
int posa=0;
int l, r;//定义左游标和右游标
printf("请输入您要多少个数据:");
scanf("%d", &posa);
for (int i = 0; i < posa; i++)
{
arr[i] = rand() % 50;//真随机获取50(不包含50)的随机数
}
printf("arr数组中的数据为:");
for (int i = 0; i < posa; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", arr[i]);
}
int diff[Maxsize];
int posd = 0;
for (int i = 0; i < posa; i++)
{
if (i == 0)diff[i] = arr[0];
else diff[i] = arr[i] - arr[i - 1];//arr对应的差分数组定义
posd++;
}
printf("\narr对应的差分数组中的数据为:");
for (int i = 0; i < posd; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", diff[i]);
}
//需求:对arr进行区间修改操作
//对diff的区间修改操作
Interval_add(diff, 2, 7, 5);//欲使arr中[2,7]加上5
Interval_add(diff, 5, 9, -7);//欲使arr中[5,9]减去7
Interval_add(diff,3,4,3);//欲使arr中[3,4]加上3
//对diff求前缀和放入sumdiff与arr对比,查看是否进行了arr的区间修改
int sumdiff[Maxsize];//diff的前缀和数组
int possd = posd;
for (int i = 0; i < possd; i++)//对diff求前缀和放入sumdiff
{
if (i == 0)
{
sumdiff[0] = diff[0];
}
else
{
sumdiff[i] = sumdiff[i - 1] + diff[i];
}//核心代码
}
printf("\narr对应的差分数组区间修改后求前缀和放入的sumdiff为:");
for (int i = 0; i < possd; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", sumdiff[i]);
}
}
测试结果:
对arr的[l,r]区间比循环更快速的修改的方法二:若要对arr数组[l,r]区间修改比如加上val则仅需要另外定义一个全零数组d[Maxsize]对d[l]+val同时d[r+1]-val再求新的d数组的前缀和放入sumd,sumd[i]再和arr[i]的元素对应相加即为区间修改后的arr:
修改方法二的代码实现:
需求:
欲使arr中[2,7]加上5
欲使arr中[5,9]减去7
欲使arr中[3,4]加上3
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//#include "algo_sumofprefix_.h"
#define Maxsize 50
void Interval_add(int d[],int l, int r, int v)//区间修改操作
{
d[l] += v;
d[r + 1] -= v;
}
int main()
{
srand((unsigned int)time(NULL));
int arr[Maxsize];//数组定义内一定要是常量表达式,否则不具跨平台特性
int posa=0;
int l, r;//定义左游标和右游标
printf("请输入您要多少个数据:");
scanf("%d", &posa);
for (int i = 0; i < posa; i++)
{
arr[i] = rand() % 50;//真随机获取50(不包含50)的随机数
}
printf("arr数组中的数据为:");
for (int i = 0; i < posa; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", arr[i]);
}
int diff[Maxsize];
int posd = 0;
for (int i = 0; i < posa; i++)
{
if (i == 0)diff[i] = arr[0];
else diff[i] = arr[i] - arr[i - 1];//arr对应的差分数组定义
posd++;
}
printf("\narr对应的差分数组中的数据为:");
for (int i = 0; i < posd; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", diff[i]);
}
//使用初始化为0的d数组,对d数组作区间修改(对d求前缀和)而后d与arr中的每个值相加即为区间更改后的arr
int d[Maxsize] = { 0 };
int posnd = posa;
//对全为0的数组进行区间修改
Interval_add(d, 2, 7, 5);//欲使arr中[2,7]加上5
Interval_add(d, 5, 9, -7);//欲使arr中[5,9]减去7
Interval_add(d, 3, 4, 3);//欲使arr中[3,4]加上3
int sumd[Maxsize];//d的前缀和数组
int possumd = posd;
for (int i = 0; i < possumd; i++)//对d求前缀和放入sumd
{
if (i == 0)
{
sumd[0] = d[0];
}
else
{
sumd[i] = sumd[i - 1] + d[i];
}//核心代码
}
printf("\n经过区间修改(且求过前缀和的)数组sumd中的数据:");
for (int i = 0; i < possumd; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", sumd[i]);
}
for (int i = 0; i < posa; i++)
{
arr[i] += sumd[i];//arr与sumd中数据相加
}
printf("\narr经过与d相加进行区间修改后的结果:");
for (int i = 0; i < posa; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", arr[i]);
}
}
测试结果:
对于上面输出的sumd中的数据的本质,我们考察其中的一个位置来研究,结果是对于sumd中的每一个i位置都是需求中所有更新区间作用的叠加结果,请看举例说明原理:
三、高效更新区间的应用意义
在实际应用中,以满足股市交易和航班系统“对庞大的数据的频繁的修改需求”,比如说,对n个数据会进行q次询问,每次询问会进行m个操作,那么当这个q极大的时候我们选择循环遍历并修改值的时间复杂度为O(q*n*m),而当我们采用差分数组的方式,用空间换时间的方式只需进行一次遍历时间复杂度就会下降为线性级别O(q*m+n)。对于庞大的且更新快的实时系统以上的算法看似简单但是是十分有意义的。
附录:
(整个思考过程中形成的所有代码与文件)
“algo_sumofprefix_.h”:
#pragma once
//用空间换时间
//sun[i]=arr[0]+arr[1]+...+a[i];即包括arr[i]本身
int sum_l_to_r(int sum[],int l,int r)
{
return sum[r] - sum[l - 1];//arr[l]+arr[l+1]+...+arr[r];容斥原理
}
void Interval_add(int d[],int l, int r, int v)//区间修改操作
{
d[l] += v;
d[r + 1] -= v;
}
main:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "algo_sumofprefix_.h"
#define Maxsize 50
int main()
{
srand((unsigned int)time(NULL));
int arr[Maxsize];//数组定义内一定要是常量表达式,否则不具跨平台特性
int posa=0;
int l, r;//定义左游标和右游标
printf("请输入您要多少个数据:");
scanf("%d", &posa);
for (int i = 0; i < posa; i++)
{
arr[i] = rand() % 50;//真随机获取50(不包含50)的随机数
}
printf("arr数组中的数据为:");
for (int i = 0; i < posa; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", arr[i]);
}
int diff[Maxsize];
int posd = 0;
for (int i = 0; i < posa; i++)
{
if (i == 0)diff[i] = arr[0];
else diff[i] = arr[i] - arr[i - 1];//arr对应的差分数组定义
posd++;
}
printf("\narr对应的差分数组中的数据为:");
for (int i = 0; i < posd; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", diff[i]);
}
需求:对arr进行区间修改操作
对diff的区间修改操作
//Interval_add(diff, 2, 7, 5);//欲使arr中[2,7]加上5
//Interval_add(diff, 5, 9, -7);//欲使arr中[5,9]减去7
//Interval_add(diff,3,4,3);//欲使arr中[3,4]加上3
对diff求前缀和放入sumdiff与arr对比,查看是否进行了arr的区间修改
//int sumdiff[Maxsize];//diff的前缀和数组
//int possd = posd;
//for (int i = 0; i < possd; i++)//对diff求前缀和放入sumdiff
//{
// if (i == 0)
// {
// sumdiff[0] = diff[0];
// }
// else
// {
// sumdiff[i] = sumdiff[i - 1] + diff[i];
// }//核心代码
//}
//
//printf("\narr对应的差分数组区间修改后求前缀和放入的sumdiff为:");
//for (int i = 0; i < possd; i++)
//{
// if (i % 5 == 0)printf("\n");//5个一行打印输出
// printf("%d ", sumdiff[i]);
//}
//
//使用初始化为0的d数组,对d数组作区间修改(对d求前缀和)而后d与arr中的每个值相加即为区间更改后的arr
int d[Maxsize] = { 0 };
int posnd = posa;
//对全为0的数组进行区间修改
Interval_add(d, 2, 7, 5);//欲使arr中[2,7]加上5
Interval_add(d, 5, 9, -7);//欲使arr中[5,9]减去7
Interval_add(d, 3, 4, 3);//欲使arr中[3,4]加上3
int sumd[Maxsize];//d的前缀和数组
int possumd = posd;
for (int i = 0; i < possumd; i++)//对d求前缀和放入sumd
{
if (i == 0)
{
sumd[0] = d[0];
}
else
{
sumd[i] = sumd[i - 1] + d[i];
}//核心代码
}
printf("\n经过区间修改(且求过前缀和的)数组sumd中的数据:");
for (int i = 0; i < possumd; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", sumd[i]);
}
for (int i = 0; i < posa; i++)
{
arr[i] += sumd[i];//arr与sumd中数据相加
}
printf("\narr经过与d相加进行区间修改后的结果:");
for (int i = 0; i < posa; i++)
{
if (i % 5 == 0)printf("\n");//5个一行打印输出
printf("%d ", arr[i]);
}
//int sum[Maxsize];//该数组中的第i项存放arr数组中的前i项和
//int poss = 0;
//
//
//for (int i=0; i < posa; i++)
//{
//
// if (i == 0)
// {
// sum[0] = arr[0];
// poss++;
// }
// else
// {
// sum[i] = sum[i - 1] + arr[i];
// poss++;
// }//核心代码
//}
//printf("\n前n项和的数组中的数据为:");
//for (int i = 0; i < poss; i++)
//{
// if (i % 5 == 0)printf("\n");//5个一行打印输出
// printf("%d ", sum[i]);
//}
//容斥原理
//printf("\n请输入您要计算arr数组(从0开始)的区间和的左边界与右边界:");
//printf("\nleft="); scanf("%d", &l);
//printf("right="); scanf("%d", &r);
//int lrsum = 0;
//lrsum=sum[r] - sum[l - 1];//arr[l]+arr[l+1]+...+arr[r];容斥原理
//printf("arr中第%d个位置到第%d个位置的和为:%d",l,r,lrsum);//测试区间求和代码
//我要求arr序列形成一个新序列F,符合F(n)=F(n-1)+F(n-2)+F(n-3)即Fn=前三项和
//int F[Maxsize];
//int posf = 0;
//F[0] = arr[0];
//F[1] = arr[1];
//F[2] = arr[2];
//posf = 3;
//for (int i = 3; i < Maxsize; i++)//基本方法
//{
// if (F[posf - 1] < 0)
// {
// posf = posf - 1;//将那个负数数给逻辑删除
// break;
// }
// else
// {
// F[i] = F[i - 1] + F[i - 2] + F[i - 3];//计算对应每个i,arr[i]的前三项和arr[i-1]+arr[i-2]+arr[i-3]形成F数组。
// posf++;
// }
//}
//printf("\nF数组中的数据为:");
//for (int i = 0; i < posf; i++)
//{
// if (i % 5 == 0)printf("\n");//5个一行打印输出
// printf("%d ", F[i]);
//}
//用容斥原理;青蛙跳台阶问题//你还得先求差分数组再求前缀和,要不然还需先形成目标数组,故不予考虑
//int F1[Maxsize];
//int posf1 = 0;
//F1[0] = arr[0];
//F1[1] = arr[1];
//F1[2] = arr[2];
//posf1 = 3;
//for (int i = 3; i < poss; i++)
//{
// if(i - 3 == 0)
// {
// F1[i] = F1[0]+F1[1]+F1[2];
// posf1++;
// continue;
// }
// else
// {
// F1[posf1] = sum_l_to_r(, posf1 - 3, posf1 - 1);
// posf1++;
// }
//}
//printf("\nF1数组中的数据为:");
//for (int i = 0; i < posf1; i++)
//{
// if (i % 5 == 0)printf("\n");//5个一行打印输出
// printf("%d ", F1[i]);
//}
return 0;
}