Heap堆
堆的本质是用Array来实现的完全二叉树,这个树的根节点代表整个堆里面的最大值或者最小值,根据是最大值还是最小值分为大根堆和小根堆,根节点的左右子树同样是大根堆或者小根堆。
一般来说在,堆的常用api有以下三个:
- peek() 查看堆顶元素 O1
- poll()拿出堆顶元素,O(logN)
- offer 添加元素 O(logN)
优先队列和堆
在Java 中有一个集合为PriorityQueue,它底层使用的就是堆
以下讨论大根堆的建立过程。
插入操作
- 在堆的最后建立一个节点
- 将数值赋予新节点
- 将这个节点与其父节点进行比较
- 如果新节点的数值比父节点值大,调换父子节点位置
- 重复3、4步骤,知道满足最大堆的特性。
poll出队列操作
- 移除根节点
- 将最后一个元素移到根节点
- 左右子节点的最大值与父亲节点比较
- 如果父节点数值比子节点小,交换父子节点
- 重复3、4步骤,知道满足要求
用数组来实现最大堆
public class MaxHeap{
private int capacity;
private int size = 0;
private int[] array;
public MaxHeap(int capacity){
this.capacity = capacity;
this.array = new int[capacity];
}
//获取左节点索引
private int getLeftIndex(int parentIndex){
return 2 * parentIndex + 1;
}
//获取右节点索引
private int getRightIndex(int parentIndex){
return 2 * parentIndex + 2;
}
//获取父节点索引
private int getParentIndex(int childIndex){
return (childIndex - 1) / 2;
}
//是否有左节点
public boolean hasLeft(int index){
return getLeftIndex(index) < size;
}
//是否有右节点
public boolean hasRight(int index){
return getRightIndex(index) < size;
}
//是否有父节点
public boolean hasParent(int index){
return getParentIndex(index) >= 0;
}
//获取左节点数据
private int leftChild(int parentIndex){
return array[getLeftIndex(parentIndex)];
}
//获取右节点数据
private int rightChild(int parentIndex){
return array[getRightIndex(parentIndex)];
}
//获取父节点数据
private int getParent(int childIndex){
return array[getParentIndex(childIndex)];
}
}
插入操作
//插入操作
public void insert(int value){
if(size == capacity){
array = Arrays.copyOf(array,capacity * 2);
capacity = capacity << 1;
}
array[size] = value;
size++;
heapUp();
}
//上浮操作
private void heapUp(){
int index = size - 1;
while (hasParent(index) && getParent(index) < array[index]){
swap(getParentIndex(index),index);
index = getParentIndex(index);
}
}
//交换两个index上的数据
public void swap(int left,int right){
int temp = array[left];
array[left] = array[right];
array[right] = temp;
}
出队列操作poll()
//出队列poll操作
private int poll(){
if(size == 0){
throw new NoSuchElementException("队列为空");
}
int value = array[0];
array[0] = array[size - 1];//把最后一个元素放到上面
size --;
heapDown();
return value;
}
private void heapDown(){
int index = 0;
while (hasLeft(index)){
int largerChildIndex = getLeftIndex(index);
if(hasRight(index) && rightChild(index) > leftChild(index)){
largerChildIndex = getRightIndex(index);
}
if(array[index] < array[largerChildIndex]){
swap(index,largerChildIndex);
}else{
break;
}
index = largerChildIndex;
}
}
堆在LeetCode中解题应用
案例1 LeetCode 215 top K问题
在一个数组中找到第k大的元素
思考:可以建立一个小顶堆,然后遍历元素,如果遍历到的元素大于堆顶元素,就把当前堆顶元素删除,并这个元素添加到堆顶。最后的堆顶元素就是要找的值
如果让求第K小的数字,就建立大根堆,如果元素比堆顶小就加入
public int findKthLargest(int[] nums, int k) {
Queue<Integer> pq = new PriorityQueue<>();
for (int num : nums) {
if(pq.size() < k){
pq.add(num);
}
else if(pq.peek() < num){
pq.remove();
pq.add(num);
}
}
return pq.peek();
}
案例2LeetCode 23合并k个升序链表
可以建立一个小根堆,每个链表的头结点入队,然后每次出队一个最小的节点,然后将对应链表的下一个节点入队,直到队列为空。
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
ListNode dummyHead = new ListNode(0);
ListNode curr = dummyHead;
PriorityQueue<ListNode> pq = new PriorityQueue<>((o1,o2) ->{
return o1.val - o2.val;
});
for (ListNode list : lists) {
if (list == null) {
continue;
}
pq.add(list);
}
while (!pq.isEmpty()) {
ListNode nextNode = pq.poll();
curr.next = nextNode;
curr = curr.next;
if (nextNode.next != null) {
pq.add(nextNode.next);
}
}
return dummyHead.next;
}