二叉堆:就是一颗完全二叉树
完全二叉树:满二叉树缺了一个右下角
堆的两个特点:
1.二叉堆首先是一颗完全二叉树(结构上)
2.二叉堆节点间关系满足以下关系:
堆中根节点的值>=子树中结点的值(最大堆/大根堆)
堆中根节点值<=子树中的结点值(最小堆/小根堆)
在大根堆中,只能保证当前根节点大于等于子树的所有节点,在任意子树中仍然满足
但是节点的大小关系和所处的层次无关。
完全二叉树(包括满二叉树)建议使用顺序表存储(数组)
完全二叉树中不存在只有右子树没有左子树的情况,不用存储空节点
其他二叉树不建议使用。
普通的二叉树不适用数组存储,就是因为要存储控几诶但,会浪费大量的空间。
如何仅用索引就能判断一个结点K是否有子树?
2k+1 <数组长度(因为满二叉树可以没有右子树)
给定一个结点k,如何判断他的父节点是否存在?
只需要看父结点的编号 > 0 即可
父节点的索引为 (k - 1) >> 1
向一个大根堆中添加一个元素,并且找到对应的位置:
public void add(int val){
data.add(val);
//进行元素的上浮操作
shiftUp(data.size() -1);
}
private void shiftUp(int k) {
//元素的上浮操作
//终止条件: 到达根节点 或者 当前结点值 <= 父节点值
while(k >0 && data.get(k) > data.get(parent(k))){
//未达到终止条件
//交换当前结点和父节点的值
swap(k,parent(k));
k = parent(k);
}
}
private void swap(int i, int j) {
int temp = data.get(i);
data.set(i,data.get(j));
data.set(j,data.get(i));
}
将最大堆不断进行extractMax直到堆为空,我们可以得到一个降序排列的数组。
//取出当前最大队的最大值
public int extractMax(){
//取值注意判空
if(isEmpty()){
throw new NoSuchElementException("heap is empty cannot extract!");
}
//大根堆默认跟根节点元素最大
int max = data.get(0);
//1.将数组末尾元素顶到堆顶
int lastVal = data.get(data.size()- 1);
data.set(0,lastVal);
//2.将数组末尾的元素删除
data.remove(data.size() -1);
//3.进行元素的下沉操作
shiftDown(0);
return max;
}
private void shiftDown(int k) {
//还存在子树
while(leftChild(k) < data.size()){
int j = leftChild(k);
//判断是否有右树
if(j+1 < data.size() && data.get(j +1) >data.get(j)){
//此时右树存在,并且大于左树的值
j = j+1;
}
//此时j就对应左右子树的最大值
//和当前的k去比较
if(data.get(k) >data.get(j)){
//下沉结束
break;
}else{
swap(k,j);
k = j;
}
}
}
测试数据时,理论上不能只使用一个小集合,我们使用ThreadLocalRandom生成一个10w数据的大集合,可以解决多个线程发生的竞争争夺。
这里列出我错误的原因:ThreadLocalRandom不能实例化new,直接使用它的静态方法current即可
从Math.random()改变到ThreadLocalRandom有如下好处:
我们不再有从多个线程访问同一个随机数生成器实例的争夺。
取代以前每个随机变量实例化一个随机数生成器实例,我们可以每个线程实例化一个。
heapify -- 堆化:将任意数组调整为堆结构
思路一(时间复杂度NlogN):
1.将任意数组看成一个完全二叉树
2.将这N个元素逐步调用add方法插入到一个新堆中,得到一个最大堆
int [] data = {15,17,19,13,22,16,28,30,41,62};
//逐步插入到堆中
MaxHeap heap = new MaxHeap();
for(int i :data){
heap.add(i);
//添加元素,其中包含了元素上浮
}
System.out.println(heap);
shiftUp方法中,k = parent(k),除2操作,这就是二叉树的高度问题。
思路二(时间复杂度N):
1.让任意数组都可以看做一颗完全二叉树遍历
2.从当前这个完全二叉树的最后一个非叶子结点进行元素的下沉操作即可调整为堆
那么如何找到这个非叶子结点的呢?
最后一个叶子结点的双亲结点就是!
最后一个叶子结点 ---数组的最后一个元素
int lastNodeLeafNode = pareant(data.size() -1);
public MaxHeap(int[] arr){
data = new ArrayList<>(arr.length);
//先将arr的所有元素复制到data数组中
for(int i :arr){
data.add(i);
}
//从最后一个非叶子节点开始进行shiftDown
for (int i = parent(data.size() -1); i >= 0 ; i--) {
shiftDown(i);
}
}