Java中的排序数组列表


85

我为无法快速找到答案感到困惑。我实质上是在寻找Java中的一种实现java.util.List接口的数据结构,但该结构按顺序存储其成员。我知道您可以使用法线ArrayListCollections.sort()在其上使用,但是我遇到的情况是,我偶尔会添加并经常从列表中检索成员,并且我不想每次检索成员时都对其进行排序,以防万一新增加了一个。谁能指出我在JDK甚至第3方库中都存在的这种东西?

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

总结:我发现所有这些都很有趣,并且学到了很多东西。特别值得一提的是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)打破合同并实现add的排序版本2)让add元素添加到列表的末尾,破坏排序的顺序3 add)通过抛出来抛弃(作为其可选项)一UnsupportedOperationException和实施这增加了在一个有序的物品的另一种方法。

选项3可能是最好的,但是我发现它不适合使用具有不能使用的add方法和不在接口中的另一个sortedAdd方法。

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

  • java.util.PriorityQueue,它可能比我所需要的更接近我的需求。在我的案例中,队列不是对象集合的最精确定义,但是在功能上,它可以完成我需要做的所有事情。
  • net.sourceforge.nite.util.SortedList。但是,此实现通过实现add(Object obj)方法中的排序打破了List接口的协定,并且奇怪的是没有的方法add(int index, Object obj)。总体共识表明,throw new UnsupportedOperationException()在这种情况下可能是更好的选择。
  • Guava的TreeMultiSet一个支持重复项的set实现
  • ca.odell.glazedlists.SortedList 此类在Javadoc中附带警告:Warning: This class breaks the contract required by List

4
如果您偶尔插入并经常阅读,为什么不在插入过程中对它进行排序?
塞格2010年

Answers:


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]

一个好的开始,但是调用add或addall会以未排序的方式添加成员。
克里斯·奈特

是。除了将它们附加到列表之外的任何操作都将直接违反List-interface。看到我更新的答案。
aioobe

@aioobe好点。但是,接口方法的不受支持的操作难道不是代码异味吗?正确的方法可能是不扩展ArrayList而是实现List,但即使那样,List也不是为此目的而设计的。来自Javadoc for List:The user of this interface has precise control over where in the list each element is inserted这不是以有序方式插入元素的最佳描述,您仍然必须处理add(int index, Object obj)接口方法。这些问题可能解释了为什么List尚未以排序方式实现。
克里斯·奈特

好吧,由于某种原因,该操作是可选的。如果.add在SortedArrayList上进行操作时得到UnsupportedExceptionOperation,我不会感到惊讶。是的,相同的推理适用于add的两个版本,addAll和set的两个版本。(根据列表界面,所有这些操作都是可选操作。)
aioobe 2010年

啊,我没有意识到它们是可选操作。剧情变厚了……;)
克里斯·奈特

10

7
那不是列表,即没有随机访问。
Thilo

1
这是基于队列的优先级堆,没有实现List。
zengr 2010年

3
当然,有了一个保持排序顺序的列表,索引一直在变化,因此无论如何可能都不需要随机访问。
Thilo

5
@Qwerky,请注意,确切的答案并不总是最佳答案,或者OP实际追求的答案。
aioobe

3
优先级队列不授予迭代排序顺序。
marcorossi 2011年

6

看看SortedList

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


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


5
看起来不错,但是看起来也很麻烦:没有对addAll的任何版本的覆盖,因此调用这些列表后,列表将不排序。
汤姆·安德森

3
并且添加方法“无效”。如果不能使用它,则应该抛出UnsupportedOperationException。
Thilo

@Tom Anderson @Thilo,你们两个都同意。
Jigar Joshi

1
有趣,但是我对将来有人使用addAll()并认为它将以一种有序的方式将所有要素都考虑在内持谨慎态度。也同意UnsupportedOperationException。
克里斯·奈特

1
添加到此列表的时间复杂度是多少?
shrini1000 2012年

6

您可以尝试番石榴的 TreeMultiSet

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

+1。这是一个很棒的图书馆。MultiSet是A collection that supports order-independent equality, like Set, but may have duplicate elements
Shervin Asgari 2010年

5

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

4

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


2
感谢Jon,但我需要保留副本
克里斯·奈特


1

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

性能不如在正确的位置插入元素的更聪明的实现,但是它可以完成工作。如果很少添加到列表中,则列表中所有操作的摊销成本应该较低。


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);

0

我认为SortedSets / Lists和“常规”可排序集合之间的选择取决于您是否只需要出于演示目的进行排序,还是需要在运行时的几乎所有时间进行排序。使用排序的集合可能会更加昂贵,因为每次插入元素时都会进行排序。

如果您无法在JDK中选择集合,则可以看看Apache Commons Collections。


0

由于当前提出的实现通过破坏Collection API来实现排序列表,具有自己的树实现或类似实现,因此我想知道基于TreeMap的实现将如何执行。(尤其是因为TreeSet也基于TreeMap)

如果有人对此也感兴趣,则可以随意查看:

树列表

它是核心库的一部分,您当然可以通过Maven依赖项进行添加。(Apache许可证)

目前,该实现似乎在同一个级别上比guava SortedMultiSet和Apache Commons库的TreeList相当好。

但是,如果我不只是测试我的实现以确保我没有错过任何重要的事情,我会很高兴。

最好的祝福!


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);
}

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

    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并将值作为数组。我会那样做。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.