【问题标题】:Getting the indices of an unsorted double array after sorting排序后获取未排序双精度数组的索引
【发布时间】:2014-11-06 09:49:13
【问题描述】:

这个问题伴随着这个one 出现,它认为双数组的排序最快。

现在我想获取未排序数组对应的 top-k 索引。

我已经实现了这个版本,它(不幸地)使用自动装箱和HashMap,正如一些答案中所建议的那样,包括这个one

HashMap<Double, Integer> map = new HashMap<Double, Integer>();
for(int i = 0; i < numClusters; i++) {
    map.put(scores[i], i);
}
Arrays.sort(scores);
HashSet<Integer> topPossibleClusters = new HashSet<Integer>();
for(int i = 0; i < numClusters; i++) {
    topPossibleClusters.add(map.get(scores[numClusters - (i+1)]));
}

如您所见,这使用了 HashMap,其键是原始数组的 Double 值,值是原始数组的索引。 因此,在对原始数组进行排序后,我只需从 map 中检索它。

我也使用HashSet,因为我有兴趣使用.contains() 方法确定int 是否包含在此集合中。 (我不知道这是否会有所不同,因为正如我在另一个问题中提到的那样,我的数组很小-50 个元素-)。如果这没有区别,请指出。

我对价值本身不感兴趣,只对指数感兴趣。

我的问题是是否有更快的方法可以使用它?

【问题讨论】:

  • 我会使用TreeMap 更明显 - 这样您就可以简单地获得HeadMap。但更中肯的一点是,Map 中的键是唯一的!如果你有重复的值,你的结果最终会有点奇怪。
  • 我不认为我有重复,所以这不会是一个问题。我猜TreeMap 会隐式进行排序。我会考虑你的提议。

标签: java arrays sorting indices


【解决方案1】:

这种相互链接/互锁的集合使其自身易于产生脆弱、容易损坏、难以调试、不可维护的代码。

改为创建一个对象:

class Data {
    double value;
    int originalIndex;
}

创建一个存储原始值和索引的 Data 对象数组。

使用查看 data.value 并按降序排序的自定义比较器对它们进行排序。

现在数组中的前 X 项是您想要的,您可以根据需要查看 valueoriginalIndex

【讨论】:

  • 好吧,你的意思明白了,但是我猜方法速度的主要部分不会有影响,对吧?
  • @Eypros 它在开始/结束时会稍微慢一些,因为它需要分配和构造对象,然后在使用后对它们进行垃圾收集。虽然不需要维护/构建 HashMap 也不需要 HashMap 查找,但处理时会快得多。
【解决方案2】:

作为Tim points out 链接多个集合相当容易出错。我建议使用TreeMap,因为这将允许一个独立的解决方案。

假设您有double[] data,首先将其复制到TreeMap

final TreeMap<Double, Integer> dataWithIndex = new TreeMap<>();
for(int i = 0; i < data.length; ++i) {
    dataWithIndex.put(data[i], i);
}

注意您可以将 dataWithIndex 声明为 NavigableMap 以减少具体情况,但它要长得多,而且实际上并没有增加太多,因为 JDK 中只有一个实现。

这将在O(n lg n) 时间填充Map,因为每个putO(lg n) - 这与排序的复杂性相同。实际上它可能会慢一点,但它会以相同的方式扩展

现在,假设您需要第一个 k 元素,您需要首先找到 kth 元素 - 这是O(k)

final Iterator<Double> keyIter = dataWithIndex.keySet().iterator();
double kthKey;
for (int i = 0; i < k; ++i) {
    kthKey = keyIter.next();
}

现在您只需要获取包含所有条目的子映射,直到第 k 个条目:

final Map<Double, Integer> topK = dataWithIndex.headMap(kthKey, true);

如果您只需要这样做一次,那么使用 Java 8 您可以执行以下操作:

List<Entry<Double, Integer>> topK = IntStream.range(0, data.length).
        mapToObj(i -> new SimpleEntry<>(data[i], i)).
        sorted(comparing(Entry::getKey)).
        limit(k).
        collect(toList());

即将IntStream 用于datamapToObj 的索引到data[i] =&gt; iEntry(使用AbsractMap.SimpleEntry 实现)。现在使用Entry::getKey 对其进行排序,并将Stream 的大小限制为k 条目。现在只需将结果收集到List。这样做的好处是不会破坏data 数组中的重复条目。

这几乎正是 Tim 在他的回答中所建议的,但使用的是现有的 JDK 类。

这个方法也是O(n lg n)。问题是,如果 TreeMap 方法被重用,那么它是 O(n lg n) 来构建 Map 但只有 O(k) 才能重用它。如果您想重用 Java 8 解决方案,那么您可以这样做:

List<Entry<Double, Integer>> sorted = IntStream.range(0, data.length).
        mapToObj(i -> new SimpleEntry<>(data[i], i)).
        sorted(comparing(Entry::getKey)).
        collect(toList());

即不要将大小限制为k 元素。现在,要获得第一个 k 元素,您只需:

List<Entry<Double, Integer>> subList = sorted.subList(0, k);

它的神奇之处在于它是O(1)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-19
    • 2019-04-27
    • 2013-07-13
    • 1970-01-01
    • 1970-01-01
    • 2013-07-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多