欢迎关注个人主页:逸狼
创造不易,可以点点赞吗~
如有错误,欢迎指出~
目录
堆
堆 提供两个最基本的操作: 返回最高优先级对象 和 添加新的对象。
堆是一个完全二叉树,由顺序存储的(数组模拟) ,有大根堆和小根堆两种堆
将根节点最大的堆叫做 最大堆 或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
已知父节点为i 孩子节点分别为2i+1和2i+2
已知孩子节点为i 父节点为(i-1)/2
测试堆
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue=new PriorityQueue<>();
priorityQueue.offer(12);//默认创建的是一个小根堆
priorityQueue.offer(5);
priorityQueue.offer(57);
System.out.println(priorityQueue.poll());//5
System.out.println(priorityQueue.poll());//12
}
模拟实现堆
创建堆
public class MyHeap {
public int[] elem;
public int usedSize;
public MyHeap() {
this.elem = new int[10];
}
//初始化
public void init(int[] array) {
for (int i = 0; i < array.length; i++) {
elem[i] = array[i];
usedSize++;
}
}
将堆调整为大根堆
从堆的最后一颗子树开始调整
左右子树的最大值与根节点比较,大于根节点的就与根交换.
p ->(len-1-1)/2
child ->2*p+1p
时间复杂度:T(n)=n-log(n+1),当n增大时,约等于n
因此,建堆的时间复杂度为:O(n)
//调整为大根堆
public void createHeap() {
for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {
siftDown(parent, usedSize);//usedSize是向下调整结束的条件
}
}
//交换
private void swap(int i, int j) {
int tmp = elem[i];
elem[i] = elem[j];
elem[j] = tmp;
}
//向下调整
public void siftDown(int parent, int end) {
int child = 2 * parent + 1;
while (child < end) {
//比较child和child+1的值,使child 的值为最大
if (child + 1 < end && elem[child] < elem[child + 1]) {
child++;
}
if (elem[child] > elem[parent]) {
swap(child, parent);
//继续往下调整
parent = child;
child = 2 * parent + 1;
} else {
break;
}
}
}
堆的插入
在堆尾插入一个数,并自动调整为大根堆
//插入一个数,并自动调整为大根堆
public void offer(int val){
if(isFull()){
elem= Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize]=val;
usedSize++;
siftUp(usedSize-1);//usedSize-1是新插入的值的下标
}
public void siftUp(int child){
int parent=(child-1)/2;
while(parent>=0){
if(elem[child]>elem[parent]){
swap(child,parent);
child=parent;
parent=(child-1)/2;
}else{
break;
}
}
}
public boolean isFull(){
return usedSize==elem.length;
}
堆的删除
堆的删除一定删除的是堆顶元素。
具体如下:
1. 将堆顶元素对堆中最后一个元素交换
2. 将堆中有效数据个数减少一个
3. 对堆顶元素进行向下调整
//删除一个数
public int poll(){
if(isEmpty()){
return -1;
}
int old=elem[0];
swap(0,usedSize-1);
usedSize--;
siftDown(0,usedSize);
return old;
}
public boolean isEmpty(){
return usedSize==0;
}
堆的使用
1.使用时必须导入PriorityQueue所在的包
2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException异常
3. 不能插入null对象,否则会抛出NullPointerException
4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
5. 插入和删除元素的时间复杂度为O(logN)
6. PriorityQueue底层使用了堆数据结构
7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素
topK 问题
最 大 或 者 最 小 的 前 k 个 数 据 。
topK的简单解法
求最小的前K个数据
时间复杂度:O((N+K)*logN)
public static int[] smallestK(int[] arr,int k){
PriorityQueue<Integer> minHeap=new PriorityQueue<>();
for (int i = 0; i < arr.length; i++) {
minHeap.offer(arr[i]);
}
int[] tmp=new int[k];
for (int i = 0; i < k; i++) {
int val=minHeap.poll();
tmp[i]=val;
}
return tmp;
}
解法2(减少了时间复杂度)
时间复杂度:O(N*logk)
求最小的前K个数据
现将前k个元素建立大小为K的大根堆
用i遍历剩下的元素
堆顶元素大于当前i 下标的值就出堆
//比较器,将堆的默认值从小根堆改为大根堆
class IntCmp implements Comparator<Integer> {
public int compare(Integer o1,Integer o2){
return o2.compareTo(o1);
}
}
class Solution {
public int[] smallestK(int[] arr, int k) {
int[] tmp=new int[k];
if(k==0){
return tmp;
}
PriorityQueue<Integer> maxHeap=new PriorityQueue<>(new IntCmp());
//将前k个元素放入堆中
for(int i=0;i<k;i++){
maxHeap.offer(arr[i]);
}
//遍历剩下的n-k个元素
for (int i = k; i < arr.length; i++) {
if(arr[i]<maxHeap.peek()){
maxHeap.poll();
maxHeap.offer(arr[i]);
}
}
for (int i = 0; i < k; i++) {
int val=maxHeap.poll();
tmp[i]=val;
}
return tmp;
}
}
堆排序
从小到大排序
要求数组本身排序,不能申请额外的内存
建立一个大根堆,将0下标与最后一个交换,
调整(不包括刚刚交换的元素)
时间复杂度:O(N*(logN))
public void heapSort(){
int endIndex=usedSize-1;
while(endIndex>0){
swap(0,endIndex);
siftDown(0,endIndex);
endIndex--;
}
}