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

Java 一种高效的数组插入/删除算法,java,arrays,algorithm,performance,random-access,Java,Arrays,Algorithm,Performance,Random Access,我订阅了一个数据提要,并从中使用INSERT/DELETE消息上的索引值创建和维护一个结构。我想问一下组装的认知专家,他们是否知道有任何算法能够以有效的方式处理零碎的更新——通常批量更新包含两到六条这样的消息 阵列的估计大小约为1000个元素 批量更新以按索引排序的消息列表的形式到达,该列表规定在给定索引处插入或删除项。我预计阵列中的大多数搅动将更接近开始而不是结束 我突然想到,通过一些基本处理,我可以确定受批次和总体大小增量影响的范围,因此只需移动一次未受影响的阵列尾部 类似地,我可以在第一个

我订阅了一个数据提要,并从中使用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进行这个实现

欢迎任何提示、提示或建议

干杯


Mike

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

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


为了让您的生活更轻松,除了对单个更新进行排序(正如您已经提到的)之外,还可以更好地使用
阵列列表来尝试和整合内容,我不知道我会有多麻烦。坦率地说,在大范围内,1000个元素算不了什么。我有一个包含2500万个元素的系统,使用简单的批量拷贝,而且(出于我们的目的)远远不够快


因此,我不会戴上“预成熟优化”的帽子,但我可能会先在书架上浏览一下。

使用链接列表(
java.util.LinkedList
)可能是值得研究的。在一个特定的索引中获取一个元素当然是昂贵的,但它可能比执行数组拷贝要好。

始终权衡代码的清晰度和优化。如果现在没有性能问题,只需确保代码清晰即可。如果将来出现性能问题,您将知道它的确切性质。现在做准备是一种猜测

如果您需要进行相当多的操作,那么一个链表可能是值得的

但是,对于简单清晰的代码,我将对原始数组或arraylist使用apache commons collection UTIL,否则:

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

ArrayList mylist=newarraylist(Arrays.asList(myArray));
添加(insertionIndex,newItem);

有一种非常简单的数据结构,名为“笛卡尔树”或“Treaps”,它允许在数组上进行O(logn)拆分、连接、插入和删除(以及许多其他操作)


2-3个树的实现也非常简单(我对一个稍微复杂的工具的实现在第一次编译后只有一个bug),并且适合您的目的。

如果空间不是一个约束,并且您不会有重复的树,那么就使用Set datastructure,特别是Java的
HashSet
。这种数据结构的强大之处在于插入和删除都是在O(1)时间内完成的,如果性能是“标准”,这将最适合您


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

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

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);
}
数组项;
数组更改;//包含具有索引、类型和可选数据成员的结构
数组输出;//空,可能带有ensureCapacity(items.length)
int c=0,delta=0;
//c是当前的变化
//增量跟踪以前的操作如何更改索引
对于(i=0;i
它在输入数组中运行一次,并使用所做的所有更改构建输出数组

请注意,这不会在同一位置处理多个插入。这样做会使代码更复杂一些,但并不难


但是,它将始终在整个阵列中运行,每批运行一次。一个稍微艰难的改变是保持一个临时的状态,并用两个指数变量进行适当的改变;然后,如果你命中了更改列表的末尾,你可以提前退出循环,而不接触列表的其余部分。

< P>如果这确实是你的数据集的样子,你可以考虑用一个集合(如HasMMAP)进行重复跟踪。数组将是带有
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);
}
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); }