将PriorityQueue与Java中的任何Comparator一起使用

时间:2017-07-14 17:47:29

标签: java

常见问题:如何使用自定义类的不同比较器对PriorityQueue中的对象进行排序?

我尝试在适当的优先级对中使用这些比较器,并在下一个代码中使用预期类似排序结果的对象列表:

class User{
  private Integer id;
  private String name;
  public User(Integer i, String n){
    this.id=i;
    this.name=n;
  }
  public Integer getId() {return id;}
  public String getName() {return name;}
  @Override
  public boolean equals(Object obj) {
    if (this == obj)return true;
    if (obj == null)return false;
    if (getClass() != obj.getClass())return false;
    User other = (User) obj;
    if(id == null){
      if (other.id != null)return false;
    }else if(!id.equals(other.id))return false;
    return true;
  }
  @Override
  public String toString() {return "[id:" + id + ", name:" + name + "]";}
}

public class MyPriorityQueue {

  public static Comparator<User> cmpId = Comparator.comparingInt(x -> x.getId());
  public static Comparator<User> cmpNameLength = Comparator.comparingInt(x -> x.getName().length());

  public static void main(String[] args) {
    List<User> users = new ArrayList<User>(10);
    users.add(new User(1,"11111"));
    users.add(new User(3,"333"));
    users.add(new User(5,"5"));
    users.add(new User(4,"44"));
    users.add(new User(2,"2222"));

    Queue<User> ids = new PriorityQueue<User>(10, cmpId); //use first comparator
    users.forEach(x-> ids.offer(x));

    Queue<User> names = new PriorityQueue<User>(10, cmpNameLength); //use second comparator
    names.addAll(users);

    System.out.println("Variant_1.1:"); 
    ids.forEach(System.out::println);

    System.out.println("Variant_2.1:"); 
    names.forEach(System.out::println);

    System.out.println("Variant_1.2:"); 
    users.sort(cmpId);  //use first comparator
    users.forEach(System.out::println);

    System.out.println("Variant_2.2:"); 
    users.sort(cmpNameLength);  //use second comparator
    users.forEach(System.out::println);
  }
}

输出:

Variant_1.1: //Failed sorted queue by user.id with using comporator cmpId
  [id:1, name:11111]
  [id:2, name:2222]
  [id:5, name:5]
  [id:4, name:44]
  [id:3, name:333]
Variant_2.1: //Failed sorted queue by length of the user.name with cmpNameLength
  [id:5, name:5]
  [id:4, name:44]
  [id:3, name:333]
  [id:1, name:11111]
  [id:2, name:2222]
Variant_1.2: // OK: correctly sorted list by user.id with cmpId comporator
  [id:1, name:11111]
  [id:2, name:2222]
  [id:3, name:333]
  [id:4, name:44]
  [id:5, name:5]
Variant_2.2: //OK: for list by length of the user.name with cmpNameLength
  [id:5, name:5]
  [id:4, name:44]
  [id:3, name:333]
  [id:2, name:2222]
  [id:1, name:11111]

我期待的是:

  • 变体1.1和2.1的结果;
  • 变体1.2和2.2的结果;

将是相同的,但它们是不同的。

我的问题:我在订购priorytyqueue /比较器和如何获得优先级的排序结果和我的示例中的相应列表时做错了什么?

2 个答案:

答案 0 :(得分:1)

在您的代码示例中,您使用Iterable#forEach来遍历队列。

ids.forEach(System.out::println);

names.forEach(System.out::println);

forEach最终委托Iterable#iterator。但是,重要的是要注意PriorityQueue#iterator中的子类覆盖具有不同的JavaDocs,并附有关于排序的特殊注释。

  

返回此队列中元素的迭代器。迭代器不会以任何特定顺序返回元素。

换句话说,无法保证迭代PriorityQueue将使用您的Comparator。如果您通过反复调用PriorityQueue#poll而更改了代码以排空队列,那么我希望您能够根据自定义Comparator查看排序结果。

深入研究OpenJDK源代码,我们可以看到PriorityQueue内的内部数据结构是binary heap。这由一个数组支持,并且当调用者添加和删除队列的元素时,代码在内部维护堆不变量。

/**
 * Priority queue represented as a balanced binary heap: the two
 * children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The
 * priority queue is ordered by comparator, or by the elements'
 * natural ordering, if comparator is null: For each node n in the
 * heap and each descendant d of n, n <= d.  The element with the
 * lowest value is in queue[0], assuming the queue is nonempty.
 */
transient Object[] queue; // non-private to simplify nested class access

但是,内部Iterator实现只是使用整数游标向前扫描该数组,而不考虑元素优先级或堆布局。

            return (E) queue[lastRet = cursor++];

答案 1 :(得分:1)

你没有做错任何事,只是PriorityQueue的迭代器是:

  

无法保证以任何特定顺序(Javadoc

遍历优先级队列的元素

forEach方法在内部使用迭代器,因此存在同样的问题。

这是因为底层数据结构是这样的,它会在你deque项目时“排序”。如果实现者想要按排序顺序返回项目,他们必须首先收集项目,然后在返回之前对它们进行排序。这会导致性能损失,因此(我认为)决定将其无序返回,因为PriorityQueue主要是一个队列,而不是一个已排序的集合,并且用户总是可以自己对项目进行排序(这是它有效率)。

为了获得订购的元素,请执行以下操作:

  while(pq.peek() != null){
      System.out.println(pq.poll());
  }