【问题标题】:Java Priority Queue Build Heap Process Time ComplexityJava 优先级队列构建堆处理时间复杂度
【发布时间】:2021-02-12 10:52:19
【问题描述】:

构建堆的时间复杂度为 O(n)。问题是Java Priority Queue能否实现? 我们以最小堆为例: ...

ArrayList<Integer> list=new ArrayList<Integer>(Arrays.asList(5,6,7,0,1,33));
Queue<Integer> queue=new PriorityQueue<Integer>(list); // Seems O(n) if it uses efficient build heap

while(!queue.isEmpty()) {
    System.out.println(queue.poll()); //log(n)
}

...

现在我们可以看到它会按自然顺序(即递增顺序)进行排序。 Java 没有采用列表和比较器的构造函数。 如果我想创建 Max Heap,那么它会是这样的:

...

ArrayList<Integer> list=new ArrayList<Integer>(Arrays.asList(5,6,7,0,1,33));
Queue<Integer> queue=new PriorityQueue<Integer>(new Comparator<Integer>() {
    @Override
    public int compare(Integer arg0, Integer arg1) {
        return arg1.compareTo(arg0);
    }});
for(Integer val:list) {
    queue.offer(val);
}
// nlog(n) in above for loop. Can't use build heap
while(!queue.isEmpty()) {
    System.out.println(queue.poll());
}

...

如果我想通过 PriorityQueue 使用排序自定义类,那么它将永远无法实现构建堆 O(n)。或者我们可以吗?

【问题讨论】:

    标签: java collections priority-queue


    【解决方案1】:

    当我运行以下程序时

    System.out.println("Elements    Via constructor      Via offer()");
    System.out.println("               Add Then-remove       Add Then-remove");
    
    for(int size: new int[] { 10, 100, 1_000, 10_000, 100_000, 1_000_000 }) {
        int[] comparisons = { 0 };
        class Element implements Comparable<Element> {
            int x;
            Element(int x) { this.x = x; }
            public int compareTo(Element o) {comparisons[0]++;return Integer.compare(x,o.x);}
            @Override public String toString() { return String.valueOf(x); }
        }
        List<Element> samples = IntStream.range(0, size)
            .mapToObj(Element::new)
            .collect(Collectors.toCollection(ArrayList::new));
        Collections.shuffle(samples, ThreadLocalRandom.current());
    
        System.out.printf("%8d", samples.size());
    
        PriorityQueue<Element> q1 = new PriorityQueue<>(samples);
        System.out.printf("%10d", comparisons[0]);
        comparisons[0] = 0;
        while(!q1.isEmpty()) q1.remove();
        System.out.printf("%12d", comparisons[0]);
    
        comparisons[0] = 0;
        PriorityQueue<Element> q2 = new PriorityQueue<>(Comparator.naturalOrder());
        for(Element e: samples) q2.offer(e);
        System.out.printf("%10d", comparisons[0]);
        comparisons[0] = 0;
        while(!q2.isEmpty()) q2.remove();
        System.out.printf("%12d", comparisons[0]);
        System.out.println();
    }
    

    我反复得到结果

    Elements    Via constructor      Via offer()
                   Add Then-remove       Add Then-remove
          10        13          27        13          27
         100       169         850       205         850
        1000      1863       14949      2264       14947
       10000     18820      216653     22696      216736
      100000    188046     2831466    228471     2831779
     1000000   1881455    34913307   2281512    34914028
    

    表明通过offer 添加元素确实执行了更多比较,但仍然具有 O(n) 时间复杂度。由于检索元素的复杂度为 O(n log n),因此构造上的差异相形见绌。

    进一步注意,如果没有随机播放,即使用预先排序的数据,这两种方法根本没有区别。在实践中,数据可能介于排序和完全随机之间。

    所以对于大多数实际目的来说,答案是这无关紧要。

    如果您真的需要在受控环境中发挥最大性能,您可以使用 hack:

    Comparator<Element> comparator = Comparator.naturalOrder();
    PriorityQueue<Element> q = new PriorityQueue<>(
        new PriorityQueue<>(comparator) { // not really recommended
            @Override
            public Object[] toArray() {
                return samples.toArray();
            }
        });
    

    PriorityQueue 将从输入 PriorityQueue 中获取比较器,但在 getClass() != PriorityQueue.class 时不信任 toArray() 结果中元素的顺序,如本例所示,并回退到将任意集合传递给构造函数时使用的相同例程。最终结果是设置比较器并使用任意集合输入进行初始化的行为。

    当然,对于您打算提供给其他人的任何代码,不建议依赖此类实现细节。但除非未来版本添加 PriorityQueue(Collection&lt;? extends E&gt; c, Comparator&lt;? super E&gt; comparator) 构造函数,否则这是将两者都放入构造函数的唯一选择。

    但如本答案的第一部分所示,您可能并不需要此 hack,因为通过 offer 添加所有元素也具有合理的性能。

    【讨论】:

    • 我同意在实践中差异很少会有大问题,但是当你教算法并需要向学生解释 Java 标准库没有添加简单的构造函数时,会有很多额外的为他们工作以使用 PriorityQueue。 Oracle 应该感到尴尬的是,他们还没有修复这个错误,因为它在 2005 年被注册。bugs.java.com/bugdatabase/view_bug.do?bug_id=6356745
    猜你喜欢
    • 2017-12-05
    • 1970-01-01
    • 1970-01-01
    • 2018-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多