0 课程地址
https://coding.imooc.com/lesson/207.html#mid=13743
1 重点关注
1.1 基于堆的优先队列
见3.1
1.2 泛型使用
见3.1
方法中只要拿定义就好了,不需要extends
1.3 优先队列常用方法
getSize
isEmpty
入队
出队
查看队首元素
1.4 堆的复杂度分析
比如作为优先队列,虽然堆能够快速看到最大值,但每次取值之后都要重新维护堆结构,时间复杂度还是O(LogN),
动态数据排序的话,建堆是O(NLogN),插入和删除都是O(LogN),排序是O(NLogN),整体应该是O(NLogN);平衡树建树如果我没算错的话应该是O(NLogN),排序时做一次中序遍历只需要O(N),整体应该还是(建树+排序)O(NLogN)
1.5 堆和平衡树的应用
堆应用于动态数据结构,专门为更快速的寻找最大最小元素而设计的数据结构
平衡树用于静态数据,专门用于查找排序
2 课程内容
2.1 堆和平衡树的各自优势
首先,平衡树,或者堆这种数据结构,关键就是动态,跟排序没有可比性。想排序,当然用排序算法就好,
用这类数据结构的关键,就是“动态”。你的数据要不断进出的。
你说的对,从时间复杂度的角度,平衡树的性能和堆是一样的。但关键是,复杂度只是一个分析算法性能的理论工具,描述的是n趋于无穷大的性能趋势。但其实,都是O(nlogn),具体实现的性能差距也需要被重视。否则,我们有AVL树就好了,也不需要红黑树了:)
具体上,堆的优势
1)最关键的,平衡树的旋转耗时;
2)其次,堆可以方便地用数组存储。在数组上做事情,也比用指针快。
代价则是,堆不能做快速的查找或者删除任意元素,它是专门针对“更”快速地寻找最大最小元素设计的动态数据结构。平衡树可以用来快速查找和排序
3 Coding
3.1 基于堆的优先队列
- 优先队列接口
package com.company; /** * 优先队列接口 * @author weidoudou * @date 2023/1/11 5:46 **/ public interface Queue <E>{ /** * 获取大小 * @author weidoudou * @date 2023/1/11 5:46 * @return int **/ int getSize(); /** * 是否为空 * @author weidoudou * @date 2023/1/11 5:46 * @return boolean **/ boolean isEmpty(); /** * 入队操作 * @author weidoudou * @date 2023/1/11 5:47 * @param e 请添加参数描述 * @return void **/ void enqueue(E e); /** * 出队操作 * @author weidoudou * @date 2023/1/11 5:48 * @return E **/ E dequeue(); /** * 取出堆顶元素 * @author weidoudou * @date 2023/1/11 5:48 * @param * @return E **/ E getFront(); }
- 优先队列实现类
package com.company; public class PriorityQueue<E extends Comparable<E>> implements Queue<E>{ //完全二叉堆 private MaxHeap<E> maxHeap; public PriorityQueue(){ maxHeap = new MaxHeap(); } @Override public int getSize() { return maxHeap.size(); } @Override public boolean isEmpty() { return maxHeap.isEmpty(); } @Override public void enqueue(E e) { maxHeap.shiftup(e); } @Override public E dequeue() { return maxHeap.remove(); } @Override public E getFront() { return maxHeap.findMax(); } }
- 最大堆
package com.company;
/**
* 最大堆
* @author weidoudou
* @date 2023/1/3 12:35
**/
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;
public MaxHeap(){
data = new Array<E>();
}
public MaxHeap(int capacity){
data = new Array<E>(capacity);
}
/**
* heapify 将数组转化为堆(将数组可以看作完全二叉堆)
* @author weidoudou
* @date 2023/1/10 7:34
* @param arr 请添加参数描述
* @return null
**/
public MaxHeap(E[] arr){
data = new Array<>(arr);
for(int i = getParent(arr.length-1);i>=0;i--){
siftDown(i);
}
}
/**
* 获取最大堆的元素个数
* @author weidoudou
* @date 2023/1/3 12:40
* @return int
**/
public int size(){
return data.getSize();
}
/**
* 获取最大堆是否为空
* @author weidoudou
* @date 2023/1/3 12:41
* @return boolean
**/
public boolean isEmpty(){
return data.isEmpty();
}
/**
* 最大堆新增元素
* 1 先加入到最大二叉堆实现的 队列中
* 2 把新加入的元素和父元素对比,若大于父元素,则和父元素交换位置,以此为循环
* @author weidoudou
* @date 2023/1/3 12:42
* @param e 请添加参数描述
* @return void
**/
public void shiftup(E e){
//1 先加入到最大二叉堆实现的 队列中
data.addLast(e);
if(data.getSize()==1){
return;
}
//2 把新加入的元素和父元素对比,若大于父元素,则和父元素交换位置,以此为循环
int k = data.getSize()-1;
loop(k,getParent(k));
}
/**
* 递归调用
* @author weidoudou
* @date 2023/1/4 8:09
* @param k 请添加参数描述
* @param j 请添加参数描述
* @return void
**/
private void loop(int k,int j){
//终止条件
//由于j是父节点,索引总是比较小,如果小于等于0,说明已经是根节点
if(j<0||k<=0||k>data.getSize()-1){
return;
}
//最终循环的位置是该元素小于父元素
if(data.get(k).compareTo(data.get(j))<=0){
return;
}
//子元素和父元素交换位置
data.swap(k,j);
//循环
loop(j,(j-1)/2);
}
/**
* 基本方法获取父节点
* @author weidoudou
* @date 2023/1/4 7:54
* @param child 请添加参数描述
* @return int
**/
private int getParent(int child){
if(child==0){
throw new IllegalArgumentException("当前节点为根节点");
}
return (child - 1) / 2;
}
/**
* 基本方法获取左子节点
* @author weidoudou
* @date 2023/1/4 7:56
* @param parent 请添加参数描述
* @return int
**/
private int getLeftChild(int parent){
return 2 * parent + 1;
}
/**
* 基本方法获取右子节点
* @author weidoudou
* @date 2023/1/4 7:56
* @param parent 请添加参数描述
* @return int
**/
private int getRightChild(int parent){
return 2 * parent + 2;
}
/**
* 最大堆元素的下沉(删除堆顶元素)
* @author weidoudou
* @date 2023/1/5 7:59
* @return void
**/
public E remove(){
//1 校验
E temp = findMax();
//2 删除元素(堆顶和堆的最小值进行交换,删除最大值后,递归堆顶的最小元素和左右子节点比较)
//2.1 特殊化处理,如果只有一个元素
int size = data.getSize();
/* if(size==1){
return data.removFirst();
}*/
//2.2 多个元素
//2.2.1 首尾交换
data.swap(0,size-1);
//2.2.2 删除堆的最大元素
data.removLast();
//2.2.3 递归调用比较堆顶和左右子节点
siftDown(0);
return temp;
}
/**
* 递归调用比较堆顶和左右子节点
* @author weidoudou
* @date 2023/1/5 8:15
* @param i 请添加参数描述
* @return void
**/
private void siftDown(int i){
int j = getLeftChild(i);//左子节点索引
int k = getRightChild(i);//右子节点索引
//1 终止条件
//1.1 无左子节点
if(j>data.getSize()-1){
return;
}
//1.2 左子节点一定有,若左子节点大于根节点,则比较右子节点和左子节点,否则,比较右子节点和根节点
if(data.get(j).compareTo(data.get(i))>0){//无右子节点或者左子节点比右子节点要大,则交换左子节点和父节点
if(k>data.getSize()-1||data.get(j).compareTo(data.get(k))>0){
data.swap(i,j);
siftDown(j);
}else{//左右节点都有并且右子节点大于左子节点
data.swap(i,k);
siftDown(k);
}
}else{//右节点存在并且父节点小于右节点,更换位置,否则不更换
if(k<=data.getSize()-1&&data.get(i).compareTo(data.get(k))<0