高效的数组插入/删除算法

时间:2010-09-01 19:16:26

标签: java arrays algorithm performance random-access

我订阅了一个数据源,并使用INSERT / DELETE消息上的索引值创建并维护一个结构。我想问一下他们是否知道任何能够以有效方式处理零碎更新的算法 - 通常批量更新包含两到六个这样的消息。

阵列的估计大小约为1000个元素。

批量更新以索引排序的消息列表形式到达,该列表规定在给定索引处插入或删除项目。我预计数组中的大部分流失都会比它的结束更接近它。

我发现,通过一些基本处理,我可以确定受批处理影响的范围和整体大小 - 增量,因此只需移动一次未受影响的数组尾部。

同样,我可以在第一个元素之前和最后一个元素之后保留一定量的可用空间来进行最少量的复制。

其他优化措施包括识别以下更新:

DELETE 10, INSERT 10 - effectively a replace which requires no copying  
INSERT 10, DELETE 11 - as above  
DELETE 10, DELETE 10, DELETE 10 - bulk deletion can be optimised into one copy operation  
INSERT 11, INSERT 12, INSERT 13 - bulk insertion can be optimised into one copy operation  

等等。

但是,我担心执行识别步骤的开销 - 它具有前瞻和跟踪功能,这可能比简单地执行复制花费更多时间。

鉴于数组的预期大小,树结构似乎是重量级的:一些基本性能测试表明二进制或自平衡树(在这种情况下是红黑树列表实现)仅在15K左右后开始显示性能优势 - 20K元素:阵列副本在较小的尺寸下明显更快。我应该补充一点,我正在使用Java进行此实现。

欢迎任何提示,提示或建议。

干杯

麦克

8 个答案:

答案 0 :(得分:2)

始终权衡代码清晰度与优化。如果现在没有性能问题,请确保代码清晰。如果将来存在性能问题,那么您将了解其确切性质。现在做好准备是一种猜测工作。

如果你需要操作很多,链表可能是值得的。

但是,对于简单明了的代码,我会使用apache commons collection utils作为原始数组或arraylist,否则:

myArray = ArrayUtils.add(myArray, insertionIndex, newItem);

OR

ArrayList<> mylist = new ArrayList<>(Arrays.asList(myArray));
myList.add(insertionIndex, newItem);

答案 1 :(得分:2)

通常,如果您按索引顺序列出了更改,则可以构建一个仅复制一次的简单循环。这是一些伪代码:

array items;
array changes; // contains a structure with index, type, an optional data members
array out; // empty, possibly with ensureCapacity(items.length)
int c = 0, delta = 0;
// c is the current change
//delta tracks how indexing has changed by previous operations
for (i = 0; i < items.length; i++) {
    if c < changes.length {
        curchange = changes[c]
        if (i + delta) == curchange.index {
            c++;
            if (curchange.type == INSERT) {
                out.add(curchange.data)
                delta--;
            } else {
                delta++;
                continue; // skip copying i
            }
        }
    }
    out.add(items[i])
}
for (; c < changes.length; c++) { // handle trailing inserts
    assert(c.index == out.length && c.type == INSERT)
    out.add(c.data);
}

通过输入数组运行一次​​,并使用所做的所有更改构建输出数组。

请注意,这不会处理同一位置的多个插入。它会使代码更精细地做到这一点,但它并不太难。

但是,它将始终在数组中一直运行,每批次一次。一个稍微强硬的变化是保持一个临时的并使用两个索引变量就地进行更改;然后,如果您点击更改列表的末尾,您可以提前退出循环而不触及列表的其余部分。

答案 2 :(得分:0)

最简单的方法是在应用更新时运行更新并将数组复制到新数组中。

1000不是那么大,可能不值得进一步优化。

为了让您的生活更轻松,请使用ArrayList

答案 3 :(得分:0)

除了对各个更新进行排序(如您已经提到的那样)以尝试整合内容,我不知道我会烦恼。坦率地说,1000个元素在大范围的事物中都没有。我有一个25M元素的系统,使用简单的批量拷贝,而且(对于我们的目的)远远超过了足够快的速度。

所以,我不会把“预成熟优化”放在帽子上,但我可能会先在书架上看一眼。

答案 4 :(得分:0)

使用链接列表(java.util.LinkedList)可能需要考虑。在某个特定索引处获取元素当然很昂贵,但它可能比执行数组副本更好。

答案 5 :(得分:0)

有一个非常简单的实现数据结构,名为“笛卡尔树”或“Treaps”,它允许在数组上进行O(log N)拆分,连接,插入和删除(以及更多内容)。

2-3个树也很容易实现(我在第一次编译后实现了一个稍微复杂的设施只有1个bug)并且符合你的目的。

答案 6 :(得分:0)

如果空间不是约束,并且您不会有重复项,请转到设置数据结构,特别是Java的HashSet。这种数据结构的强大之处在于插入和删除是在O(1)时间内完成的,如果性能是“标准”,那么最适合你。

此外,除了快速检索之外,每当你谈到阵列时,你都会遇到许多阵列副本的严重限制,这些阵列副本不仅会占用空间(用于阵列增长)而且效率也会很差。插入/删除可能需要O(n)时间。

答案 7 :(得分:0)

如果这确实是您的数据集的样子,您可能会考虑使用Collection(如HashMap)进行重复跟踪。数组将是您的有序列表,每个活动按顺序列出,您的收集将是数组的索引。

例如:

class EventQueue
{
  Vector eventQueue;
  HashMap eventMap;

  public synchronized Event getNextEvent()
  {
      Event event = eventQueue.remove(0);
      eventMap.remove(event.getId());  // this would be 10 from 'INSERT 10' 
                                       // in the sample from the OP
  }

  public synchronized addEvent(Event e)
  {
     if( eventMap.containsKey(e.getId())
     {
        // replace events that already exist
        int idx = eventMap.get(e.getId());
        eventQueue.removeElementAt(idx);
        eventQueue.add(idx, e);
     } else {
        // add new events
        eventQueue.add(e);
        eventMap.add(e.getId(), eventQueue.size()); // may be off by one...
     }
  }

  public boolean isReady()
  {
    return eventQueue.size() > 0;
  }
}

class FeedListener extends Thread {
 EventQueue queue;
 EventFeed feed;
 ...
 public void run()
 {
    while(running) {
       sleep(sleepTime);
       if( feed.isEventReady() ) {
         queue.addEvent(feed.getEvent());
       }
    }
 }
}

abstract class EventHandler extends Thread {
 EventQueue queue;
 ...
 public void run()
 {
   while(running)
   {
     sleep(sleepTime);
     if( queue.isReady() )
     {
       Event event = queue.getNextEvent();
       handleEvent(event);
     }
   }
 }

 public abstract void handleEvent(Event event);
}