在端点和元素之前/之后以恒定时间插入的数据结构?

时间:2018-07-20 17:21:34

标签: java performance list

我正在寻找一种数据结构,

  • 大小无限制。
  • 保持其元素的插入顺序。
  • 在列表的开头和结尾(最好是固定时间)有效插入。
  • 在现有元素之前或之后(最好在固定时间内)有效插入。

我排除了ArrayList,因为插入列表的开头效率不高。

表面LinkedList应该是一个完美的选择,但实际上Java实现在插入现有元素之前或之后效率不高(即,它遍历整个列表以查找插入位置)。

(我个人不需要存储重复的元素,但其他人可能会存储)


动机:我正在建立一个事件队列,该队列允许偶尔作弊(在现有事件之前或之后插入)。

5 个答案:

答案 0 :(得分:8)

老实说,我认为LinkedList的自定义实现将是一种方法:

  • 具有无限大小。
    • 是的。
  • 保持其元素的插入顺序。
    • 是的。
  • 在列表的开头和结尾高效插入(最好在固定时间内插入)。
    • 是,是O(1)。
  • 在现有元素之前或之后(最好在固定时间内)高效插入。
    • 如果在插入/删除元素时保持Map<?, Node>,则可以在恒定时间内访问Node(及其上一个/下一个节点),并以这种方式插入/删除。

根据事件的总数(事件可以作弊的频率),还可以考虑线性时间,从而允许您使用API​​的LinkedList实现。

答案 1 :(得分:0)

AVL树怎么样?由于树始终是平衡的,因此它的高度为O(log(N)),其中N是树中的节点数。这将导致最坏的情况 还插入O(log(N))。

我相信您所寻找的东西将尽可能接近固定的时间。抱歉,如果这不是答案,我的声誉不足,还没有发表评论:/

答案 2 :(得分:0)

  

我正在建立一个事件队列,该队列允许偶尔作弊(在现有事件之前或之后插入)。

如果作弊很少见,请使用您自己的基于数组的结构。 java.util.ArrayDeque处理O(1)的两端,但不能在中间插入。可以轻松添加它们,但是复杂度为O(n)。

请注意,LinkedList的大多数操作(由于其糟糕的数据位置)比ArrayListArrayDeque慢得多。


让我们假设,您非常需要在中间插入固定时间。像

insertAfter(existingElement, newElement);

java.util.LinkedList的问题在于它没有这样的操作。它只有ListIterator,它允许您在中间插入,但这仅在您首先定位迭代器(O(n))时才有用。

因此,您需要自己的链接列表。基本实现相当简单,您还需要直接访问其节点(包含nextprev链接的内容,而不是您add的内容)。然后维护一个Map<MyThing, MyListNode>,使您可以找到existingElement的节点。现在,您只需为newElement创建一个新节点,正确地链接它(基本上是微不足道的,但是有一些极端情况)并更新地图。


要消除边界情况(第一个/最后一个/唯一节点),请将两个虚拟节点(prefirstpostlast)添加到链接列表。显然,这些是不会存储在地图中的唯一节点。它们使实现变得非常简单,例如

void insertAfter(MyThing existingElement, MyThing newElement) {
   Node found = map.get(existingElement);
   if (found == null) throw ...
   Node newNode = new Node(newElement);
   map.put(newElement, newNode);

   // Link newNode where it belongs to.
   newNode.next = found.next;
   newNode.prev = found;

   // Fix links.
   newNode.prev.next = newNode;
   newNode.next.prev = newNode;   
}

void insertFirst(MyThing newElement) {
   Node found = prefirst;

   // All the remaining code is simply copied from above.
   // Factor it out.

   Node newNode = new Node(newElement);
   map.put(newElement, newNode);

   // Link newNode where it belongs to.
   newNode.next = found.next;
   newNode.prev = found;

   // Fix links.
   newNode.prev.next = newNode;
   newNode.next.prev = newNode;   
}

答案 3 :(得分:0)

使用LinkedHashMap

  • 保留广告订单
  • 速度很快(因为基本上是使用键在O(1)中找到元素的。)
  • 允许随时随地插入元素(为此,您需要考虑一个好钥匙-设计一个对您的排序逻辑有意义的hashCode()equals())。
  

Map接口的哈希表和链表实现,带有   可预测的迭代顺序。此实现不同于HashMap   因为它维护了遍历其所有内容的双向链表   条目。此链表定义了迭代顺序,即   通常,将键插入地图的顺序   (插入顺序)。

我不赞成自定义集合,因为您将花费大量时间重新发明轮子,然后最终得出一个结论,那就是最好使用LinkedHashMap

请注意,此集合不是线程安全的。

答案 4 :(得分:-1)

我通常将c ++ 11的forward_list用于需要在任何位置进行最佳插入的操作。它基于单链表。参见forward_list

  

转发列表是序列容器,允许在序列中的任何位置进行恒定时间的插入和擦除操作。请参见forward_list的第一句话

如果您最关心的是优化,那么为什么不切换到c ++。我不确定Java中是否存在这样的功能。请注意,尽管forward_list提供恒定的时间插入,但是您不能直接通过位置访问元素。

一般来说,当您说要插入某个位置时,对于链表。 有两种情况:

  1. 您已引用该位置的节点。在这种情况下,时间复杂度为O(1)

  2. 您只有索引。时间复杂度将为O(n)