Java 改进了两次遍历数组(同一数组上的嵌套循环)

Java 改进了两次遍历数组(同一数组上的嵌套循环),java,performance,algorithm,loops,big-o,Java,Performance,Algorithm,Loops,Big O,我有一大组数据,我想循环使用,以确定从时间点“D1”到未来时间点“D2”的数据集的各种统计数据。基本上,每次值之间的差值大于10时,我都希望添加到数据库中。例如: Datum[] data = x; for( Datum d1 : data ){ Datum[] tail = y; //From d1 up to 10 elements ahead for( Datum d2 : tail ){ //Calculate difference if(

我有一大组数据,我想循环使用,以确定从时间点“D1”到未来时间点“D2”的数据集的各种统计数据。基本上,每次值之间的差值大于10时,我都希望添加到数据库中。例如:

Datum[] data = x;
for( Datum d1 : data ){
    Datum[] tail = y; //From d1 up to 10 elements ahead
    for( Datum d2 : tail ){
        //Calculate difference
        if( (d2.val - d1.val) > 10 ){
            //Insert into database
        }
    }
}
我的问题是,有没有更好的算法/方法?既然tail中的9个元素在外循环的下一次迭代中被重用,我能从中受益吗?我的目标是把它降到比(大O符号)O(n2)小得多的水平,但我不能把我的脑袋放在它上面。通常,找到满足标准的D1、D2对意味着下一个D1元素也有更大的匹配机会。我能利用这个优势吗


因为数据集太大了,所以我试图让它尽可能地高效。

基于索引的for循环可能比迭代器执行得更好,因为您可以直接为原始数组编制索引,并避免复制到新数组。你会有更好的内存位置,更少的错误共享机会,等等。

在你看来,我要做的第一件事是分析一个典型的数据集,并找出时间的去向。这应该会给你一些关于优化工作重点的提示

假设计算与减法/比较一样简单,并且可以快速访问数组,那么您关于优化保存到数据库的建议应该是下一个优先事项。例如,与单个insert语句相比,写出文本文件并使用大容量插入可以提供非常快的性能。如果您坚持使用单独的插入,并且使用JDBC,那么批处理更新将是一个很大的帮助,因为它们避免了与数据库通信的延迟

如果还不够快,请考虑将数组划分为N个分区,并用一个单独的线程处理每个分区。如果处理受CPU限制,这将特别有效

最后,寻找代码级优化,例如通过使用索引避免迭代器。如果写入数据库的项目数与迭代的元素数相比很小,那么迭代器的创建可能是一个瓶颈

如果元素的数量大于10,并且严重超过了cpu缓存所能容纳的数量,那么将扫描分解为更小的块将更加有效。例如,与其扫描数据2中的1000个元素,不如将其分解为(比如)10次100个元素的扫描,10次扫描中的每一次都使用不同的d1值。这类似于矩阵多应用程序以块方式实现,并更好地利用cpu缓存


虽然您使用的是两个循环,这通常是一个O(N^2)算法,但第二个循环的大小是固定的-10个元素,因此这将减少到一个简单的常数因子-您大约要多做10个工作

您拥有的是一个经典的扫掠线算法,它是O(k*n),其中k是“重叠”或内部循环运行的部分。在你的情况下,不管n是多少,它的最大值是10

Datum[] data = x;
for(int i=0;i<data.length;i++ ){
    Datum d1=data[i];
    Datum[] tail = y; //From d1 up to 10 elements ahead
    for(int j=i+1;j<Math.min(i+10,data.length);i++){
        d2 = data[j];
        //Calculate difference
        if( (d2.val - d1.val) > 10 ){
            //Insert into database

            break;//inner loop
        }
    }
}
Datum[]数据=x;
对于(int i=0;i)有一种渐近较快的方法来解决这个问题,但我对它是否会在实践中运行更快有严重的怀疑,因为您的窗口大小(10)太小了。如果您想增加这个大小,我将称之为k-较大,那么您可能会考虑选择如下方法。

使用此算法时,需要维护一个包含k个元素的窗口,该窗口支持两种操作:

  • 插入新元素,逐出最旧的元素
  • 返回大于某个值的所有元素
  • 一种方法是将所有元素存储在一个数据结构中,该数据结构结合了一个平衡二叉搜索树和一个队列。该队列包含所有k个元素,这些元素按照它们在原始序列中出现的顺序存储,并使用这些元素以便我们能够记住在需要添加新元素时要逐出哪个元素存储按排序顺序存储的每个元素的副本。这意味着您可以执行以下操作:

  • 插入新元素,逐出最旧的元素:将新元素添加到队列和BST。然后,从队列中出列以获取最旧的元素,然后将其从BST中删除。运行时:O(日志k),因为BST中有k个元素
  • 返回大于某个值的所有元素:使用BST,在O(logn)时间内找到至少与该值相同大的最小元素。然后,扫描BST并列出至少与该元素相同大的所有元素。这需要O(z)时间,其中z是找到的匹配总数
  • 总的来说,如果您有n个元素和总共z对需要插入到数据库中,则此算法将花费O(n log k+z)时间。要看到这一点,请注意,我们总共执行了n个操作(1)副本,每个副本花费O(log k)时间。我们还执行了n个操作(2)副本,这需要O(n log k)时间来找到后续操作,然后执行O(z) 列出所有匹配对的所有迭代的总时间

    与您最初发布的O(nk)算法相比,该算法的渐进运行时间很好。假设匹配数不是“非常大”(比如,在nk的数量级上),随着n和k的增加,这将快得多

    如果您存储的值是小范围内的整数(例如,0-10000),您可以通过使用针对整数优化的数据结构(如a)替换平衡BST来进一步加快存储速度,从而将其减少到O(n log k+z).同样,这只是渐进地更快,如果你将k常数保持在10,这几乎肯定是不值得的

    希望这有帮助!

    我不知道这是如何开始的O(n^2)。您正在遍历arr的每个元素