堆(Heap)与优先队列
堆的基础知识
堆是一个完全二叉树,可以采用数组形式存储
下标从0开始,i位置的节点:
左孩子坐标为:2 * i + 1;
右孩子坐标为:2 * i + 2;
父亲节点坐标为:(i - 1) / 2;
Java中的PriorityQueue类(优先队列)是堆的实现类
大顶堆:
性质:
在二叉树任意的三元子中,根节点都大于两个子节点的堆
最大值在堆顶,第二大的值在根节点的左节点或右节点中。
兄弟之间无大小关系
堆适合维护:集合最值
小顶堆:
性质:
在二叉树任意的三元子中,根节点都小于两个子节点的堆
最小值在堆顶,第二小的值在根节点的左节点或右节点中。
兄弟之间无大小关系
堆—尾部插入调整(插入元素)
将新节点不断地与其父节点比较,新节点大于父节点则与父节点交换位置。
堆—头部弹出调整(删除元素)
大顶堆:弹出的是堆中的最大值
小顶堆:弹出的是堆中的最小值
在弹出堆顶元素之后,数组长度应该减一。原0下标的元素因为被弹出,所以为空,那么我们就把原数组中末尾的元素**(为了方便将其记为a)插入到下标0的位置上去。随后我们要进行堆性质的维护,每一次都从三元组中选择一个最大的节点与a交换,直到元素a**是三元组中最大的节点。
堆排序
—待完善
堆–优先队列的一种实现方式
普通队列 | (最大/最小)堆 |
---|---|
尾部入队 | 尾部可以插入 |
头部出队 | 头部可以弹出 |
先进先出 | 每次出队权值(最大/最小的元素) |
数组实现 | 数组实现,逻辑上看成一个堆 |
堆的代码实现
大顶堆:
import java.util.Arrays;
class BigHeap{
int size;
int[] bigHeap;
public BigHeap() {
this.size = 0;
this.bigHeap = new int[10];//默认是10大小
}
public BigHeap(int n) {
this.size = 0;
this.bigHeap = new int[n];
}
//返回堆大小的方法
public int size(){
return size;
}
//返回堆顶元素的方法
public int top(){
return bigHeap[0];
}
//向堆中插入元素的方法
public void push(int num){
if(size == bigHeap.length) return;//数组满了无法插入
bigHeap[size] = num;
int index = size++;//堆中元素数量加一,index为最后一个元素的下标
while(index > 0 && bigHeap[(index - 1) / 2] < num){//保证下标不越界并且可以比较大小
swap(bigHeap,index,(index - 1) / 2);
index = (index - 1) / 2;
}
}
//删除堆顶元素的方法
public int pop(){
if(size == 0)
return 0;
int num = bigHeap[0];
bigHeap[0] = bigHeap[size - 1];
bigHeap[size - 1] = 0;//这里不写也是可以的,写了主要是为了toString观看方便,因为插入新值的时候会把老值覆盖掉
size -= 1;
int n = size - 1;//n为数组中最后一个元素的下标
int index = 0;//这是要向下进行置换的元素
while((index * 2 + 1) <= n){//判断是否越界
int temp = index;
if(bigHeap[temp] < bigHeap[index * 2 + 1]) temp = index * 2 + 1;
if((index * 2 + 2) <= n && bigHeap[temp] < bigHeap[index * 2 + 2]) temp = index * 2 + 2;
swap(bigHeap,temp,index);
if(index == temp) break;
index = temp;
}
return num;
}
//交换两个元素的方法
public void swap(int[] nums,int index1,int index2){
int number = nums[index1];
nums[index1] = nums[index2];
nums[index2] = number;
}
@Override
public String toString() {
return "BigHeap{" +
"bigHeap=" + Arrays.toString(bigHeap) +
'}';
}
}
测试:
public class Main{
public static void main(String args[]){
BigHeap bigHeap = new BigHeap();
bigHeap.push(4);
bigHeap.push(5);
bigHeap.push(1);
bigHeap.push(9);
bigHeap.push(97);
bigHeap.push(3);
while (bigHeap.size() != 0){
System.out.println("当前堆顶为: " + bigHeap.pop() + " 出栈后堆为" + bigHeap.toString());
}
}
}
当前堆顶为: 97 出栈后堆为BigHeap{bigHeap=[9, 5, 3, 4, 1, 0, 0, 0, 0, 0]}
当前堆顶为: 9 出栈后堆为BigHeap{bigHeap=[5, 4, 3, 1, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 5 出栈后堆为BigHeap{bigHeap=[4, 1, 3, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 4 出栈后堆为BigHeap{bigHeap=[3, 1, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 3 出栈后堆为BigHeap{bigHeap=[1, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 1 出栈后堆为BigHeap{bigHeap=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
小顶堆:
import java.util.Arrays;
class SmallHeap {
int size;
int[] smallHeap;
public SmallHeap() {
this.size = 0;
this.smallHeap = new int[10];//默认是10大小
}
public SmallHeap(int n) {
this.size = 0;
this.smallHeap = new int[n];
}
//返回堆大小的方法
public int size(){
return size;
}
//返回堆顶元素的方法
public int top(){
return smallHeap[0];
}
//向堆中插入元素的方法
public void push(int num){
if(size == smallHeap.length) return;//数组满了无法插入
smallHeap[size] = num;
int index = size++;//堆中元素数量加一,index为最后一个元素的下标
while(index > 0 && smallHeap[(index - 1) / 2] > num){//保证下标不越界并且可以比较大小
swap(smallHeap,index,(index - 1) / 2);
index = (index - 1) / 2;
}
}
//删除堆顶元素的方法
public int pop(){
if(size == 0)
return 0;
int num = smallHeap[0];
smallHeap[0] = smallHeap[size - 1];
smallHeap[size - 1] = 0;//这里不写也是可以的,写了主要是为了toString观看方便,因为插入新值的时候会把老值覆盖掉
size -= 1;
int n = size - 1;//n为数组中最后一个元素的下标
int index = 0;//这是要向下进行置换的元素
while((index * 2 + 1) <= n){//判断是否越界
int temp = index;
if(smallHeap[temp] > smallHeap[index * 2 + 1]) temp = index * 2 + 1;
if((index * 2 + 2) <= n && smallHeap[temp] > smallHeap[index * 2 + 2]) temp = index * 2 + 2;
swap(smallHeap,temp,index);
if(index == temp) break;
index = temp;
}
return num;
}
//交换两个元素的方法
public void swap(int[] nums,int index1,int index2){
int number = nums[index1];
nums[index1] = nums[index2];
nums[index2] = number;
}
@Override
public String toString() {
return "SmallHeap{" +
"smallHeap=" + Arrays.toString(smallHeap) +
'}';
}
}
测试:
public class Main{
public static void main(String args[]){
SmallHeap smallHeap = new SmallHeap();
smallHeap.push(4);
smallHeap.push(5);
smallHeap.push(1);
smallHeap.push(9);
smallHeap.push(97);
smallHeap.push(3);
while (smallHeap.size() != 0){
System.out.println("当前堆顶为: " + smallHeap.pop() + " 出栈后堆为" + smallHeap.toString());
}
smallHeap.push(1);
smallHeap.push(5);
smallHeap.push(-3);
while (smallHeap.size() != 0){
System.out.println("当前堆顶为: " + smallHeap.pop() + " 出栈后堆为" + smallHeap.toString());
}
}
}
当前堆顶为: 1 出栈后堆为SmallHeap{smallHeap=[3, 5, 4, 9, 97, 0, 0, 0, 0, 0]}
当前堆顶为: 3 出栈后堆为SmallHeap{smallHeap=[4, 5, 97, 9, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 4 出栈后堆为SmallHeap{smallHeap=[5, 9, 97, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 5 出栈后堆为SmallHeap{smallHeap=[9, 97, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 9 出栈后堆为SmallHeap{smallHeap=[97, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 97 出栈后堆为SmallHeap{smallHeap=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: -3 出栈后堆为SmallHeap{smallHeap=[1, 5, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 1 出栈后堆为SmallHeap{smallHeap=[5, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 5 出栈后堆为SmallHeap{smallHeap=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
堆的基础应用
用堆去解决这些问题不一定是最好的,但是一切都是为了锻炼使用堆的思维。
LeetCode剑指 Offer 40. 最小的k个数
维护一个大顶堆,大顶堆的大小为k,遍历数组,每次都将遍历到的元素与堆顶元素相比较,如果该元素小于顶堆元素则堆顶元素弹出,该元素进入堆中,维护堆结构。最后遍历输出堆。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
BigHeap bigHeap = new BigHeap(k + 1);//要把堆的代码copy过来,在上面。太长了所以就不复制进来了
for(int i = 0;i < arr.length;i++){
bigHeap.push(arr[i]);
if(bigHeap.size() > k) bigHeap.pop();
}
int[] nums = new int[k];
for(int i = 0;i < k;i++){
nums[i] = bigHeap.pop();
}
return nums;
}
}
LeetCode1046. 最后一块石头的重量
class Solution {
public int lastStoneWeight(int[] stones) {
int len = stones.length;
BigHeap bigHeap = new BigHeap(len);//要把堆的代码copy过来,在上面。太长了所以就不复制进来了
for(int i : stones)
bigHeap.push(i);
while(bigHeap.size > 1){
int x = bigHeap.pop();
int y = bigHeap.pop();
if(x == y) continue;
if(x > y) bigHeap.push(x - y);
else bigHeap.push(y - x);
}
return bigHeap.pop();
}
}
LeetCode703. 数据流中的第 K 大元素
维护一个小顶堆,大顶堆的大小为k,遍历数组,每次都将遍历到的元素与堆顶元素相比较,如果该元素大于顶堆元素则堆顶元素弹出,该元素进入堆中,维护堆结构。最后输出堆顶元素。(堆中为前k个最大值)
class KthLargest {
SmallHeap smallHeap;//要把堆的代码copy过来,在上面。太长了所以就不复制进来了
int k;
public KthLargest(int k, int[] nums) {
this.smallHeap = new SmallHeap(k + 1);
this.k = k;
for(int i = 0;i < nums.length;i++){
smallHeap.push(nums[i]);
if(smallHeap.size() > k) smallHeap.pop();
}
}
public int add(int val) {
smallHeap.push(val);
if(smallHeap.size() > k) smallHeap.pop();
return smallHeap.top();
}
}
LeetCode215. 数组中的第K个最大元素
与上一道题大同小异
class Solution {
public int findKthLargest(int[] nums, int k) {
SmallHeap smallHeap = new SmallHeap(k + 1);//要把堆的代码copy过来,在上面。太长了所以就不复制进来了
for(int i : nums){
smallHeap.push(i);
if(smallHeap.size() > k) smallHeap.pop();
}
return smallHeap.top();
}
}