leetcode刷题是遇到一道按照二进制1的个数排列数组元素的题;
* 给你一个整数数组 arr 。请你将数组中的元素按照其二进制表示中数字 1 的数目升序排序。 * 如果存在多个数字二进制中 1 的数目相同,则必须将它们按照数值大小升序排列。 * 请你返回排序后的数组。
一般我们都是通过函数 num &= num - 1来计算二进制中1的个数,这个肯定是可以的
public int countBit1(int num) { int count = 0; while (num != 0) { num &= num - 1; //去掉一个低位1 count++; } return count; }
但是我做完,再看其他人的解法时,发现有个使用优先队列来存储和排序数组的方法,觉得也十分巧妙
PriorityQueue<Integer> pQ = new PriorityQueue<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if (Integer.bitCount(o1) == Integer.bitCount(o2)) { return o2 - o1; } return Integer.bitCount(o1) - Integer.bitCount(o2); } });
该方法主要使用Integer自带的bitCount方法,来计算1的个数,通过重写Comparator方法,来自定义PriorityQueue的排序原则
对此我对优先队列的源码进行的一些研究
1,优先队列的数据结构
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
transient Object[] queue; //队列容器, 默认是11
private int size = 0; //队列长度
private final Comparator<? super E> comparator; //队列比较器, 为null使用自然排序
//....
}
和常规的队列相比多了一个Comparator方法,通过重写这个方法来自定义排序规则
2,优先队列是线程安全的吗?
我们来看入队的offer方法
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1); //当队列长度大于等于容量值时,自动拓展
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e); //
return true;
}
可以看出PriorityQueue并不是线程安全队列,因为offer都没有对队列进行锁定,如果要拥有线程安全的优先级队列,需要额外进行加锁操作
3,如何进行排序的?
两种方式,指定比较器和使用默认比较器
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x); //指定比较器
else
siftUpComparable(k, x); //没有指定比较器,使用默认的自然比较器
}
这里主要关注自定义比较器,使用选择排序法将入列的元素放左边或者右边.
从源码上看PriorityQueue的入列操作并没对所有加入的元素进行优先级排序。仅仅保证数组第一个元素是最小的即可。
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
所以当我们每次通过poll方法放回第一个元素时,我们就可以获得最小的元素(假设升序排列),依次类推,当所有元素出队列 时,就可以做到全局有序