【问题标题】:How can I get Sorted List behavior in Java without using Collections.sort()?如何在不使用 Collections.sort() 的情况下在 Java 中获得排序列表行为?
【发布时间】:2016-01-23 19:28:49
【问题描述】:

我知道由于各种概念原因,Java 没有排序列表,但考虑到我需要一个类似于优先级队列但也允许我随机访问(可索引)的集合的情况,换句话说,我需要一个遵循特定顺序的列表。我希望使用Collections.sort()

优选的操作约束:

检索 - O(1)(基于索引的随机访问)
搜索 - O(log n)
插入 - O(log n)
删除 - O(log n)

集合上的迭代器应该为我提供排序顺序中的所有元素(基于在数据结构实例化期间提供的预定义Comparator

我更喜欢使用 Java 的内置库来实现这一点,但也可以随意建议外部库。

编辑: TreeSet 不会,因为基于索引的访问很困难,使用包装器集合也不是我的最佳选择,因为删除意味着我需要从两个集合中删除。

EDIT2:我找不到indexable skip list 的实现和/或文档,这似乎有点相关,谁能帮我找到它?也欢迎任何支持或反对所提出的数据结构的 cmets。

EDIT3:虽然这可能不是最完美的答案,但我想添加我编写的这段代码,以便任何有类似问题需要排序列表的人都可以在发现它有用时使用它。

检查错误(如果有),并提出改进建议(尤其是sortedSubList 方法)

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;

public class SortedList<E> extends ArrayList<E> {
    private final Comparator<? super E> comparator;

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

    public SortedList(int initialCapacity, Comparator<? super E> comparator) {
        super(initialCapacity);
        this.comparator = comparator;
    }

    @Override
    public boolean add(E e) {
        if (comparator == null)
            return super.add(e);
        if (e == null)
            throw new NullPointerException();
        int start = 0;
        int end = size() - 1;
        while (start <= end) {
            int mid = (start + end) / 2;
            if (comparator.compare(get(mid), e) == 0) {
                super.add(mid, e);
                return true;
            }
            if (comparator.compare(get(mid), e) < 0) {
                end = mid - 1;
            }
            else {
                start = mid + 1;
            }
        }
        super.add(start, e);
        return true;
    }

    @Override
    public boolean contains(Object o) {
        if (comparator == null)
            return super.contains(o);
        if (o == null)
            return false;
        E other = (E) o;
        int start = 0;
        int end = size() - 1;
        while (start <= end) {
            int mid = (start + end) / 2;
            if (comparator.compare(get(mid), other) == 0) {
                return true;
            }
            if (comparator.compare(get(mid), other) < 0) {
                end = mid - 1;
            }
            else {
                start = mid + 1;
            }
        }
        return false;
    }

    @Override
    public int indexOf(Object o) {
        if (comparator == null)
            return super.indexOf(o);
        if (o == null)
            throw new NullPointerException();
        E other = (E) o;
        int start = 0;
        int end = size() - 1;
        while (start <= end) {
            int mid = (start + end) / 2;
            if (comparator.compare(get(mid), other) == 0) {
                return mid;
            }
            if (comparator.compare(get(mid), other) < 0) {
                end = mid - 1;
            }
            else {
                start = mid + 1;
            }
        }
        return -(start+1);
    }

    @Override
    public void add(int index, E e) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        throw new UnsupportedOperationException();
    }

    @Override
    public E set(int index, E e) {
        throw new UnsupportedOperationException();
    }

    public SortedList<E> sortedSubList(int fromIndex, int toIndex) {
        SortedList<E> sl = new SortedList<>(comparator);
        for (int i = fromIndex; i < toIndex; i++)
            sl.add(get(i));
        return sl;
    }
}

【问题讨论】:

  • TreeSet 是你想要的
  • 你应该澄清“允许我随机访问”意味着你希望它是可索引的,假设这就是你的意思。
  • 很难同时获得 O(1) 索引和 O(log n) 插入/删除。 O(1) 索引将需要某种类似数组的存储,而 O(log n) 插入和删除意味着您无法在每次更新数据结构时移动一半的元素。
  • @ctomek:不是骗子。此问题具有其他问题中未包含且其答案未满足的特定、不寻常的要求。

标签: java collections sortedcollection


【解决方案1】:

构建一个由 ArrayList 和 TreeSet 支持的自定义集合。将随机访问委托给 ArrayList 并将搜索委托给 TreeSet。当然,这意味着每次写入操作都会非常昂贵,因为每次都必须对 ArrayList 进行排序。但是读取应该非常有效。

【讨论】:

    【解决方案2】:

    很难在同一个数据结构中获得 O(1) 索引和 O(log n) 插入/删除。 O(1) 索引意味着我们无法承担索引树、列表、跳过列表或其他基于链接的数据结构所涉及的链接跟踪,而 O(log n) 修改意味着我们无法承担一半的移位每次插入时数组的元素。不知道能不能同时满足这些要求。

    如果我们放宽其中一项要求,事情就会变得容易得多。例如,所有操作的 O(log n) 可以通过indexable skip list 或自平衡 BST 来实现,其节点跟踪以该节点为根的子树的大小。但是,这些都不能建立在 Java 标准库中的跳过列表或 BST 之上,因此您可能需要安装另一个库或编写自己的数据结构。

    O(1) 索引、O(log n) 搜索以及 O(n) 插入和删除可以通过保持排序的 ArrayList 并使用 Collections.binarySearch 搜索元素或插入/删除位置来完成。您永远不需要调用Collections.sort,但您仍然需要调用 ArrayList 的 O(n) 插入和删除方法。这可能是在 Java 的内置工具之上构建的最简单的选择。请注意,在最近的 Java 版本中,Collections.sort 是一种自适应合并排序,它将花费 O(n) 时间来对只有最后一个元素未排序的数组进行排序,因此您可能会摆脱依赖 Collections.sort。但是,这是替代 Java 实现不必遵循的实现细节。

    【讨论】:

      【解决方案3】:

      如果您的主要目标是索引查找 (get()) 的主要目标是 O(1),那么您可以使用 Arrays.binarySearch() 实现自己的实现 List 的类,并由数组支持。

      retrieve: get(int)         - O(1)     - array index
      search:   contains(Object) - O(log n) - binarySearch
                indexOf(Object)  - O(log n) - binarySearch
      insert:   add(E)           - O(n)     - binarySearch + array shift
      delete:   remove(int)      - O(n)     - array shift
                remove(Object)   - O(n)     - binarySearch + array shift
      

      add(E) 方法违反了List 定义(追加),但与Collection 定义一致。

      以下方法应该抛出UnsupportedOperationException

      add(int index, E element)
      addAll(int index, Collection<? extends E> c)
      set(int index, E element)
      

      如果不允许重复值,这可能是一个逻辑限制,请考虑同时实现NavigableSet,即SortedSet

      【讨论】:

      • "实现List" - 这似乎不是特别有用。如果交给这个数据结构,大多数期望 List 的代码都会中断。 Collection 应该实现,但是尽管实现了get(int)indexOf(Object)remove(int),这个类可能不应该实现List
      • @user2357112 为什么“大多数代码”会中断?那是什么代码?请注意,除了add(E) 方法外,所有其他方法都可以 100% 实现规范。
      猜你喜欢
      • 1970-01-01
      • 2014-06-25
      • 2017-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-17
      • 1970-01-01
      • 2018-02-04
      相关资源
      最近更新 更多