算法笔记(2)—— 数据预处理算法:前缀和算法、差分算法

简介: 算法笔记(2)—— 数据预处理算法:前缀和算法、差分算法

一、前缀和算法

算法介绍

        前缀和是一种对已知数据的预处理方法,目的是为了快速查询数据中的某个值的大小或某部分值的和,前缀和是指序列前面所有项的和

【预处理时间复杂度】与被处理数据的维数有关

【查询时间复杂度】O(1)

问题引入

假设现在有一段长为 n 的序列,现在要求其索引为 left 和索引为 right 的值之间所有项的和,我们可以用循环进行遍历求和,如果要求 m 个这样的区间和的值,每次循环的时间复杂度为 O(n),而要求 m 次,那么总的时间复杂度就是 O(nm),显然在求和的过程中序列有部分被重复遍历了 m 次,这是不必要的,如何省去这段重复过程从而加快求和的速度呢?

一维前缀和

【预处理时间复杂度】O(n)

空间复杂度】O(n)

【总时间复杂度】O(n + m)【m为查询次数】

一维前缀和处理就是将序列中的每一项的值重新更改为其与其前面每一项的和(如下)

//处理前
1  2  3  4  5  6  7 
//处理后
1  3  6 10 15 21 28 

查询区间数据之和时,只需求出前缀和序列的数据端点之差,即索引为 left_index 和索引为 right_index 的值之间所有值的和 sum = new[right_index] - new[left_index-1],其中 new 为处理后的序列,特别的,当 left_index = 0,sum = new[right_index]


例如上述序列,求索引为 1 的值(2)到索引为 5 的值(6)之间所有数据的和,对于处理后的前缀和数组,这一值为 new[5]-new[1-1] = 21-1 = 20,,原理如下

new[5]   = old[0] + old[1] + old[2] + old[3] + old[4] + old[5] = 21
new[1-1] = old[0]                                              = 1
sum=new[5]-new[0] = old[1] + old[2] + old[3] + old[4] + old[5] = 20

【图解算法】

一维前缀和

【数学表达式】

定义式

在定义式中, new[i] 表示新数组索引为 k 的元素的值,old[k] 代表原数组索引为 k 的值


递推式

eq.png

在递推式中,new[i] 表示新数组索引为 i 的元素的值,old[i] 代表原数组索引为 i 的值,特别的,当 i=0 时,new[i] = old[i]

【算法实现】

Python实现

class PartialSum:#一维前缀和类
    def __init__(self, lis:list):#构造方法
        #lis:待处理的列表
        self.list = lis#前缀和列表
        self.length = len(lis)#前缀和列表长度
        self.__pretreatment()#调用预处理方法
    def __pretreatment(self):#预处理方法
        for i in range(1,self.length):
            self.list[i] += self.list[i-1]#递推式
    def query(self, left:int, right:int):#查询方法
        #left:左索引 right:右索引
        return self.list[right]-self.list[left-1]*(left>0)

C/C++实现

class PartialSum//一维前缀和类
{
  private:
    int length;//前缀和数组长度
    int *array;//前缀和数组指针
    void pretreatment()//预处理函数
    {
      for(int i=1; i<length; i++)
        *(array+i) += *(array+i-1);//递推式
    }
  public:
    PartialSum(int *arr,int len)//构造函数
    //arr:数组指针 len:数组长度
    {
      array = arr;
      length = len;
      pretreatment();//调用预处理函数
    }
    int query(int left, int right)//查询函数
    //left:左索引 right:右索引
    {
      return *(array+right)-*(array+left-1)*(left>0);
    }
};

二维前缀和

【预处理时间复杂度】O(n²)

【空间复杂度】O(n²)

【总时间复杂度】O(n² + m)【m为查询次数】

二维的前缀和与一维的差不多,也是前面所有项的和,只不过二维的前面项是以矩阵的形式一块一块地呈现

 //处理前
 1  2  3  4  5
 2  3  4  5  6
 3  4  5  6  7
 4  5  6  7  8
 //处理后
 1  3  6 10 15
 3  8 15 24 35
 6 15 27 42 60
10 24 42 64 90

【图解算法】

二维前缀和

【数学表达式】

定义式

在定义式中, new[i][j] 表示新数组索引为 k 和 j 的元素的值,old[p][q] 代表原数组索引为 p 和 q 的值


递推式

eq.png

在递推式中,new[i][j] 表示新数组索引为 i 和 j 的元素的值,old[i][j] 代表原数组索引为 i 和 j 的值,


特别的,

当 i = 0 而 j != 0 时,new[i][j] = new[i][j-1]+old[i][j],


当 j = 0 而 i != 0 时,new[i][j] = new[i-1][j]+old[i][j],


当 i=0 且 j=0 时,new[i][j] = old[i][j]


【算法实现】

Python实现

class PartialSum2:#二维前缀和类
    def __init__(self, lis:list):#构造方法
        #lis:待处理的列表
        self.list = lis#前缀和列表
        self.x = len(lis)#前缀和列表x维长度
        self.y = len(lis[0])#前缀和列表y维长度
        self.__pretreatment()#调用预处理方法
    def __pretreatment(self):#预处理方法
        for i in range(self.x):
            for j in range(self.y):
                self.list[i][j] += self.list[i][j-1]*(j>0)+self.list[i-1][j]*(i>0)-self.list[i-1][j-1]*(i*j>0)#递推式
    def query(self, x1:int, y1:int, x2:int, y2:int):#查询方法
        #(x1,y1)为第1个索引坐标 (x2,y2)为第2个索引坐标
        return self.list[x2][y2]+self.list[x1-1][y1-1]*(x1*y1>0)-self.list[x1-1][y2]*(x1>0)-self.list[x2][y1-1]*(y1>0)

C/C++实现

class PartialSum2//二维前缀和类
{
  private:
    int x;//二维前缀和数组x维长度
    int y;//二维前缀和数组y维长度
    int *array;//二维前缀和数组
    void pretreatment()//预处理函数
    {
      for(int i=0;i<x;i++)
        for(int j=0;j<y;j++)
          *(array+i*y+j) += *(array+i*y+j-1)*(j>0)+*(array+(i-1)*y+j)*(i>0)-*(array+(i-1)*y+j-1)*(i*j>0);//递推式
    }
  public:
    PartialSum2(int *arr, int len_x, int len_y)//构造函数
    //arr:数组指针 len_x:数组x维长度 len_y:数组y维长度
    {
      array = arr;
      x = len_x;
      y = len_y;
      pretreatment();//调用预处理函数
    }
    int query(int x1, int y1, int x2, int y2)//查询函数
    //(x1,y1)为第1个索引坐标 (x2,y2)为第2个索引坐标
    {
      return *(array+x2*y+y2)+*(array+(x1-1)*y+y1-1)*(x1*y1>0)-*(array+(x1-1)*y+y2)*(x1>0)-*(array+x2*y+y1-1)*(y1>0);
    }
};

练手好题

洛谷 P1387 最大正方形

洛谷 P3397 地毯

二、差分算法

算法介绍

差分算法也是一种对已知数据的预处理操作,目的是为了快速修改数据里的某个值或者某部分值,差分是指前缀和序列相邻项之间的差值

【预处理时间复杂度】与被处理数据的维数有关

【修改时间复杂度】O(1)

问题引入

假设现在有一段长为 n 的序列,现在要求其索引为 left 和索引为 right 的值之间所有项都减去一个值,我们可以用循环进行遍历操作,如果要进行 m 次这样的操作,每次循环的时间复杂度为 O(n),而要操作 m 次,那么总的时间复杂度就是 O(nm),显然在修改的过程中序列有部分被重复遍历了 m 次,这是不必要的,如何省去这段重复过程从而加快修改的速度呢?

一维差分

【预处理时间复杂度】O(n)

【空间复杂度】O(n)

【总时间复杂度】O(n + m)【m为修改次数】

一维差分处理就是将每个位置的值修改为其与其前面一项的差(如下)

//处理前
1  3  6 10 15 21 28
//处理后
1  2  3  4  5  6  7

看到这里,细心的读者肯定已经发现,差分实际上就是前缀和的逆运算


但是啊,很显然,前缀和算法要比差分算法更容易理解一些,对差分数组里的元素操作的结果就是使原数组中同索引位置及之后的值全部进行相同的操作,原理很简单,差分数组的值是相邻两项之差,改变这个差值,后面所有的元素与前面的差值就都会有相同的改变

【图解算法】

一维差分

【数学表达式】

定义式

特别的,当 i=0 时,new[i] = old[i]

逆推式

特别的,当 i=0 时,new[i] = new[i]

【算法实现】

Python实现

class AdjacentDifference:#一维差分类
    def __init__(self, lis:list):#构造方法
        #lis:待处理列表
        self.list = lis#差分列表
        self.length = len(lis)#差分列表长度
        self.__pretreatment()#调用预处理方法
    def __pretreatment(self):#预处理方法
        for i in range(self.length-1,0,-1):
            self.list[i] -= self.list[i-1]#逆推式
    def modify(self, left:int, right:int, value:int):#修改方法
        #left:左索引 right:右索引 value:修改值
        self.list[left] += value
        if right+1 < self.length:
            self.list[right+1] -= value
    def inverse_operation(self):#返回方法
        #返回处理后的原列表
        lis = self.list[:]
        for i in range(1,self.length):
            lis[i] += lis[i-1]
        return lis

C/C++实现

class AdjacentDifference//一维差分类
{
    private:
        int length;//差分数组长度
        int *array;//差分数组指针
    void pretreatment()//预处理函数
    {
      for(int i=length-1;i>0;i--)
        *(array+i) -= *(array+i-1);//逆推式
    }
  public:
    AdjacentDifference(int *arr, int len)//构造函数
    {
      array = arr;
      length = len;
      pretreatment();//调用预处理函数
    }
    void modify(int left, int right, int value)//修改函数
    //left:左索引 right:有索引 value:修改值
    {
      *(array+left) += value;
      if(right+1<length)*(array+right+1) -= value;
    }
    int *inverse_operation()//返回函数
    //返回逆运算后原数组的首项指针
    {
      for(int i=1;i<length;i++)
        *(array+i)=*(array+i-1)+*(array+i);
      return array;
    }
};

二维差分

【预处理时间复杂度】O(n²)

【空间复杂度】O(n²)

【总时间复杂度】O(n² + m)【m为修改次数】

二维差分和一维差分类似,原理不再赘述

 //处理前
 1  3  6 10 15
 3  8 15 24 35
 6 15 27 42 60
10 24 42 64 90
 //处理后
 1  2  3  4  5
 2  3  4  5  6
 3  4  5  6  7
 4  5  6  7  8

【图解算法】

二维差分

【数学表达式】

定义式

特别的,


当 i = 0,j != 0 时,new[i][j] = old[i][j] - old[i][j-1]


当 j = 0,i != 0 时,new[i][j] = old[i][j] - old[i-1][j]


当 i = 0,j = 0 时,new[i][j] = old[i][j]

逆推式

特别的,

当 i = 0,j != 0 时,new[i][j] = new[i][j] - new[i][j-1]


当 j = 0,i != 0 时,new[i][j] = new[i][j] - new[i-1][j]


当 i = 0,j = 0 时,new[i][j] = new[i][j]


【算法实现】

Python实现

class AdjacentDifference2:#二维差分类
    def __init__(self, lis:list):#构造方法
        #lis:待处理列表
        self.list = lis#差分列表
        self.x = len(lis)#差分列表x维长度
        self.y = len(lis[0])#差分列表y维长度
        self.__pretreatment()#调用预处理方法
    def __pretreatment(self):#预处理方法
        for i in range(self.x-1,-1,-1):
            for j in range(self.y-1,-1,-1):
                self.list[i][j]+=self.list[i-1][j-1]*(i*j>0)-self.list[i-1][j]*(i>0)-self.list[i][j-1]*(j>0)#逆推式
    def modify(self, x1:int, y1:int, x2:int, y2:int, value:int):#修改方法
        #(x1,y1)为第1个索引坐标 (x2,y2)为第2个索引坐标 value:修改值
        self.list[x1][y1] += value
        if x2+1 < self.x:self.list[x2+1][y1] -= value
        if y2+1 < self.y:self.list[x1][y2+1] -= value
        if x2+1 < self.x and y2+1 < self.y:self.list[x2+1][y2+1] += value
    def inverse_operation(self):#返回方法
        #返回逆运算后的原列表
        lis = eval(str(self.list))
        for i in range(self.x):
            for j in range(self.y):
                lis[i][j] += lis[i][j-1]*(j>0)+lis[i-1][j]*(i>0)-lis[i-1][j-1]*(i*j>0)
        return lis

C/C++实现

class AdjacentDifference2//二维差分类
{
  private:
    int x;//差分数组x维长度
    int y;//差分数组y维长度
    int *array;//二维差分数组
    void pretreatment()//预处理函数
    {
      for(int i=x-1;i>-1;i--)
        for(int j=y-1;j>-1;j--)
          *(array+i*y+j) += *(array+(i-1)*y+j-1)*(i*j>0)-*(array+(i-1)*y+j)*(i>0)-*(array+i*y+j-1)*(j>0);//逆推式
    }
  public:
    AdjacentDifference2(int *arr, int len_x, int len_y)//构造函数
    {
      array = arr;
      x = len_x;
      y = len_y;
      pretreatment();//调用预处理函数
    }
    void modify(int x1, int y1, int x2, int y2, int value)//修改函数
    //(x1,y1)为第1个索引坐标 (x2,y2)为第2个索引坐标 value:修改值
    {
      *(array+x1*y+y1) += value;
      if(x2+1<x)*(array+(x2+1)*y+y1) -= value;
      if(y2+1<y)*(array+x1*y+y2+1) -= value;
      if(x2+1<x&&y2+1<y)*(array+(x2+1)*y+y2+1) += value;
    }
    int *inverse_operation()//返回函数
    //返回逆运算后原数组的首项指针
    {
      for(int i=0;i<x;i++)
        for(int j=0;j<y;j++)
          *(array+i*y+j)+=*(array+i*y+j-1)*(j>0)+*(array+(i-1)*y+j)*(i>0)-*(array+(i-1)*y+j-1)*(i*j>0);
      return array;
    }
};

三、总结

 前缀和算法与差分算法都属于对数据预处理的方法,前缀和算法是为了快速地查询数据中的某个值或某个区间值的和,而差分算法是为了快速修改数据中的某个值或某区间内的值,两者互为逆运算!虽然本文章只涉及到了一维和二维的两种算法,但相信大家在看懂了算法的原理后,这两种算法的更高维度版本也可以轻而易举地写出来!

目录
相关文章
|
2月前
|
算法 索引
❤️算法笔记❤️-(每日一刷-141、环形链表)
❤️算法笔记❤️-(每日一刷-141、环形链表)
51 0
|
2月前
|
算法 API 计算机视觉
人脸识别笔记(一):通过yuface调包(参数量54K更快更小更准的算法) 来实现人脸识别
本文介绍了YuNet系列人脸检测算法的优化和使用,包括YuNet-s和YuNet-n,以及通过yuface库和onnx在不同场景下实现人脸检测的方法。
78 1
|
2月前
|
JSON 算法 数据可视化
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
77 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
|
2月前
|
算法
❤️算法笔记❤️-(每日一刷-160、相交链表)
❤️算法笔记❤️-(每日一刷-160、相交链表)
18 1
|
2月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
21 0
|
2月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
34 0
|
2月前
|
算法
❤️算法笔记❤️-(每日一刷-26、删除有序数组的重复项)
❤️算法笔记❤️-(每日一刷-26、删除有序数组的重复项)
27 0
|
18天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
|
24天前
|
机器学习/深度学习 算法 Serverless
基于WOA-SVM的乳腺癌数据分类识别算法matlab仿真,对比BP神经网络和SVM
本项目利用鲸鱼优化算法(WOA)优化支持向量机(SVM)参数,针对乳腺癌早期诊断问题,通过MATLAB 2022a实现。核心代码包括参数初始化、目标函数计算、位置更新等步骤,并附有详细中文注释及操作视频。实验结果显示,WOA-SVM在提高分类精度和泛化能力方面表现出色,为乳腺癌的早期诊断提供了有效的技术支持。
|
4天前
|
供应链 算法 调度
排队算法的matlab仿真,带GUI界面
该程序使用MATLAB 2022A版本实现排队算法的仿真,并带有GUI界面。程序支持单队列单服务台、单队列多服务台和多队列多服务台三种排队方式。核心函数`func_mms2`通过模拟到达时间和服务时间,计算阻塞率和利用率。排队论研究系统中顾客和服务台的交互行为,广泛应用于通信网络、生产调度和服务行业等领域,旨在优化系统性能,减少等待时间,提高资源利用率。