Java中的排序数组列表

时间:2010-10-27 09:12:59

标签: java data-structures sorted

我很难过,我找不到快速的答案。我基本上在Java中寻找一个实现java.util.List接口的数据结构,但它以排序顺序存储其成员。我知道您可以使用普通ArrayList并使用Collections.sort(),但我有一个场景,我偶尔会添加并经常从列表中检索成员,我不想排序每次我检索一个成员,以防添加新的成员。任何人都可以指出我在JDK甚至第三方库中存在这样的事情吗?

编辑:数据结构需要保留重复项。

答案摘要:我发现所有这些都非常有趣并且学到了很多东西。 Aioobe尤其值得一提,因为他坚持不懈地努力实现上述要求(主要是支持重复的有序java.util.List实现)。我已经接受了他的答案,因为我提出的问题是最准确的,而且最让我发现的是我正在寻找的内容的影响,即使我问的不完全是我所需要的。

我要求的问题在于List接口本身以及接口中可选方法的概念。引用javadoc:

  

此界面的用户可以精确控制列表中每个元素的插入位置。

插入排序列表无法精确控制插入点。然后,你必须考虑如何处理一些方法。以add为例:

  

public boolean add(Object o)

 Appends the specified element to the end of this list (optional operation).

你现在处于令人不安的境地 1)打破合同并实现添加的排序版本 2)让add将一个元素添加到列表的末尾,打破您的排序顺序 3)抛出add(作为其可选项)抛出UnsupportedOperationException并实现另一种按排序顺序添加项目的方法。

选项3可能是最好的,但我觉得有一个你不能使用的添加方法和另一个不在界面中的sortedAdd方法令人讨厌。

其他相关解决方案(无特定顺序):

  • java.util.PriorityQueue这可能比我要求的最接近我所要求的。在我的情况下,队列不是对象集合的最精确定义,但从功能上来说,它可以完成我需要的所有内容。
  • net.sourceforge.nite.util.SortedList。但是,此实现通过在add(Object obj)方法中实现排序来破坏List接口的契约,并且奇怪地对add(int index, Object obj)采用无效方法。普遍共识表明throw new UnsupportedOperationException()在这种情况下可能是更好的选择。
  • Guava's TreeMultiSet支持重复的集合实现
  • ca.odell.glazedlists.SortedList此课程附带javadoc中的警告:Warning: This class breaks the contract required by List

12 个答案:

答案 0 :(得分:62)

简约解决方案

这是一个“最小化”的解决方案。

class SortedArrayList<T> extends ArrayList<T> {

    @SuppressWarnings("unchecked")
    public void insertSorted(T value) {
        add(value);
        Comparable<T> cmp = (Comparable<T>) value;
        for (int i = size()-1; i > 0 && cmp.compareTo(get(i-1)) < 0; i--)
            Collections.swap(this, i, i-1);
    }
}

插入以线性时间运行,但无论如何都是使用ArrayList得到的(插入元素右侧的所有元素都必须以这种或那种方式移动)。

在ClassCastException中插入不可比较的结果。 (这也是PriorityQueue采用的方法:依赖于自然排序的优先级队列也不允许插入不可比较的对象(这样做可能会导致ClassCastException)。)<登记/>

覆盖List.add

请注意,覆盖List.add(或List.addAll)以排序方式插入元素将是直接违反接口规范。您可以做的是覆盖此方法以抛出UnsupportedOperationException

来自List.add的文档:

  

boolean add(E e)
  将指定的元素追加到此列表的末尾(可选操作)。

同样的推理适用于add的两个版本,addAllset的两个版本。 (根据列表界面,所有这些都是可选操作。)


一些测试

SortedArrayList<String> test = new SortedArrayList<String>();

test.insertSorted("ddd");    System.out.println(test);
test.insertSorted("aaa");    System.out.println(test);
test.insertSorted("ccc");    System.out.println(test);
test.insertSorted("bbb");    System.out.println(test);
test.insertSorted("eee");    System.out.println(test);

...打印:

[ddd]
[aaa, ddd]
[aaa, ccc, ddd]
[aaa, bbb, ccc, ddd]
[aaa, bbb, ccc, ddd, eee]

答案 1 :(得分:11)

答案 2 :(得分:6)

查看SortedList

  

此类实现排序列表。它由比较器构成,可以比较两个对象并相应地对对象进行排序。将对象添加到列表时,它将插入到正确的位置。根据比较器相等的对象将按照它们添加到此列表的顺序位于列表中。仅添加比较器可以比较的对象。


  

当列表已经包含根据比较器相等的对象时,新对象将紧接在这些其他对象之后插入。

答案 3 :(得分:6)

您可以尝试Guava's TreeMultiSet

 Multiset<Integer> ms=TreeMultiset.create(Arrays.asList(1,2,3,1,1,-1,2,4,5,100));
 System.out.println(ms);

答案 4 :(得分:4)

列表通常会保留添加项目的顺序。你肯定需要一个列表,或者排序的(例如TreeSet<E>)对你有用吗?基本上,您是否需要保留重复项?

答案 5 :(得分:4)

Aioobe的方法是要走的路。我想建议对他的解决方案进行以下改进。

class SortedList<T> extends ArrayList<T> {

    public void insertSorted(T value) {
        int insertPoint = insertPoint(value);
        add(insertPoint, value);
    }

    /**
     * @return The insert point for a new value. If the value is found the insert point can be any
     * of the possible positions that keeps the collection sorted (.33 or 3.3 or 33.).
     */
    private int insertPoint(T key) {
        int low = 0;
        int high = size() - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = (Comparable<T>) get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else {
                return mid; // key found
            }
        }

        return low;  // key not found
    }
}
使用大型列表时,aioobe的解决方案变得非常慢。使用列表排序的事实允许我们使用二进制搜索找到新值的插入点。

我也会使用构图而不是继承,这是

的内容
SortedList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

答案 6 :(得分:2)

对你来说可能有点过于沉重,但GlazedLists有一个SortedList非常适合用作表格或JList的模型

答案 7 :(得分:1)

您可以继承ArrayList,并在添加任何元素后调用Collections.sort(this) - 您需要覆盖两个版本的add和两个addAll来执行此操作。

性能不如在正确位置插入元素的智能实现那么好,但它可以完成这项工作。如果列表中的添加很少,则列表上所有操作的摊销成本应该很低。

答案 8 :(得分:1)

只需创建一个新的类,如下所示:

public class SortedList<T> extends ArrayList<T> {

private final Comparator<? super T> comparator;

public SortedList() {
    super();
    this.comparator = null;
}

public SortedList(Comparator<T> comparator) {
    super();
    this.comparator = comparator;
}

@Override
public boolean add(T item) {
    int index = comparator == null ? Collections.binarySearch((List<? extends Comparable<? super T>>)this, item) :
            Collections.binarySearch(this, item, comparator);
    if (index < 0) {
        index = index * -1 - 2;
    }
    super.add(index+1, item);
    return true;
}

@Override
public void add(int index, T item) {
    throw new UnsupportedOperationException("'add' with an index is not supported in SortedArrayList");
}

@Override
public boolean addAll(Collection<? extends T> items) {
    boolean allAdded = true;
    for (T item : items) {
        allAdded = allAdded && add(item);
    }
    return allAdded;
}

@Override
public boolean addAll(int index, Collection<? extends T> items) {
    throw new UnsupportedOperationException("'addAll' with an index is not supported in SortedArrayList");
}

}

您可以像这样测试它:

    List<Integer> list = new SortedArrayList<>((Integer i1, Integer i2) -> i1.compareTo(i2));
    for (Integer i : Arrays.asList(4, 7, 3, 8, 9, 25, 20, 23, 52, 3)) {
        list.add(i);
    }
    System.out.println(list);

答案 9 :(得分:0)

我认为SortedSets / Lists和'normal'可排序集合之间的选择取决于您是否需要仅为了演示目的而在运行时或几乎每个点进行排序。使用已排序的集合可能要贵得多,因为每次插入元素时都会进行排序。

如果你不能在JDK中选择一个集合,你可以看一下Apache Commons Collections

答案 10 :(得分:0)

由于当前提出的实现通过破解Collection API实现排序列表,有一个树或类似的自己的实现,我很好奇如何基于TreeMap的实现将执行。 (特别是因为TreeSet也基于TreeMap)

如果有人对此感兴趣,他或她可以随意调查:

TreeList

它是core library的一部分,你当然可以通过Maven依赖添加它。 (Apache许可证)

目前,实现似乎在与guava SortedMultiSet和Apache Commons库的TreeList相同的级别上进行了比较。

但是如果不仅仅是我会测试实现以确保我没有错过重要的东西,我会很高兴。

祝你好运!

答案 11 :(得分:0)

我遇到了同样的问题。所以我获取了java.util.TreeMap的源代码并编写了 IndexedTreeMap 。它实现了我自己的 IndexedNavigableMap

public interface IndexedNavigableMap<K, V> extends NavigableMap<K, V> {
   K exactKey(int index);
   Entry<K, V> exactEntry(int index);
   int keyIndex(K k);
}

实现基于更改红黑树中的节点权重。权重是给定节点下的子节点数加一个自身。例如,当树向左旋转时:

    private void rotateLeft(Entry<K, V> p) {
    if (p != null) {
        Entry<K, V> r = p.right;

        int delta = getWeight(r.left) - getWeight(p.right);
        p.right = r.left;
        p.updateWeight(delta);

        if (r.left != null) {
            r.left.parent = p;
        }

        r.parent = p.parent;


        if (p.parent == null) {
            root = r;
        } else if (p.parent.left == p) {
            delta = getWeight(r) - getWeight(p.parent.left);
            p.parent.left = r;
            p.parent.updateWeight(delta);
        } else {
            delta = getWeight(r) - getWeight(p.parent.right);
            p.parent.right = r;
            p.parent.updateWeight(delta);
        }

        delta = getWeight(p) - getWeight(r.left);
        r.left = p;
        r.updateWeight(delta);

        p.parent = r;
    }
  }

updateWeight只是更新权重到根:

   void updateWeight(int delta) {
        weight += delta;
        Entry<K, V> p = parent;
        while (p != null) {
            p.weight += delta;
            p = p.parent;
        }
    }

当我们需要通过索引找到元素时,这是使用权重的实现:

public K exactKey(int index) {
    if (index < 0 || index > size() - 1) {
        throw new ArrayIndexOutOfBoundsException();
    }
    return getExactKey(root, index);
}

private K getExactKey(Entry<K, V> e, int index) {
    if (e.left == null && index == 0) {
        return e.key;
    }
    if (e.left == null && e.right == null) {
        return e.key;
    }
    if (e.left != null && e.left.weight > index) {
        return getExactKey(e.left, index);
    }
    if (e.left != null && e.left.weight == index) {
        return e.key;
    }
    return getExactKey(e.right, index - (e.left == null ? 0 : e.left.weight) - 1);
}

还可以非常方便地找到密钥的索引:

    public int keyIndex(K key) {
    if (key == null) {
        throw new NullPointerException();
    }
    Entry<K, V> e = getEntry(key);
    if (e == null) {
        throw new NullPointerException();
    }
    if (e == root) {
        return getWeight(e) - getWeight(e.right) - 1;//index to return
    }
    int index = 0;
    int cmp;
    index += getWeight(e.left);

    Entry<K, V> p = e.parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        while (p != null) {
            cmp = cpr.compare(key, p.key);
            if (cmp > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    } else {
        Comparable<? super K> k = (Comparable<? super K>) key;
        while (p != null) {
            if (k.compareTo(p.key) > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    }
    return index;
}

您可以在http://code.google.com/p/indexed-tree-map/

找到这项工作的结果

TreeSet / TreeMap(以及它们与indexed-tree-map项目的索引对应项)不允许重复键,您可以使用1个键作为值数组。如果需要带有重复项的SortedSet,请使用TreeMap,将值作为数组。我会这样做。