一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情。
我们首先讲一下堆这种数据结构,他远远比堆排序重要得多
最后那个补充一下,优先级队列就是堆结构,只不过通常拿来作优先级使用,不是队列结构
完全二叉树
堆这种结构其实是一种完全二叉树结构,什么是完全二叉树(从左往右 依次遍历满的二叉)
上图不是完全二叉树,按顺序左侧少了一个
上图则是完全二叉树
数组转换完全二叉树
接着数组怎么转化为或者对应为完全二叉树呢?-- 把数组从0出发的连续一段依次放入
接着我们需要找到数组对应二叉树位置的关系,以及二叉树的size:
0位置,左孩子就是20+1 = 1位置 右孩子就是20+2=2位置
...
记住这个下标变换
引入堆
堆是一棵比较特殊的完全二叉树,其次分为大根堆和小根堆
大根堆:
以xx为头的整个树最大值是自己(子树最大值就是根):
以6为头整个树,最大的是6
以5为头整个树,最大的是5
小根堆则相反,每一棵子树的最小值是根
大根堆维护 - 存数字 - heapinsert过程
假设我们开辟一段数组空间,初始heapSize=0,用户输入数字,我们如何保证数组对应的二叉树一直是一个大根堆?
用户初始输入5,这时候heapsize++,然后对应的二叉树满足大根堆条件
接着用户输入3:
我们拿3和其父节点进行比较,父节点0.5*(i - 1)为0位置就是5,没有比较过,不动
接着输入6,我们拿6和0位置比较,>5,和根节点交换:
交换之后仍然保证是大根堆.....:
上述的过程就是一个heap insert的过程!对应这一个过程的代码:
还是比较容易理解的,说的就是当前index位置的数经过while和swap的过程最后确定他的位置,如果此index是最大的数,最后应该出现在根处。
而while里面包含了两种截止条件,第一个是比父小停止,第二个是到0位置停止
现在我们已经解决了用户给我们一个数字,我们都可以存为大根堆结构的目的了!
大根堆维护 - 取数字 - heapify堆化过程
现在用户有了新的需求,想要取大根堆最大的数字,之后将其移除并且保证剩下还是大根堆结构
取最大数字很简单,直接拿arr[0]就是最大数字,问题是如何保证用户取完该数字后剩余数字还是一个大根堆?
这里就需要从上到下进行比较和交换了!
首先第一步,将arr[0]和arr[length - 1]最后一个数字swap一下,并且heapSize--,这样其实就相当于首位最大值已经断开连接没用了。
接着就需要从上到下进行父与子比较,首先从0位置判断左右子谁大,谁大跟谁比较,如果小于孩子就交换。并且被交换的一侧继续执行上述逻辑,最后1移动到了右下方,并且目前还是一个大顶堆:
简单来说每次操作完之后都保证了每一个树和子树都是大顶堆了!
对应的过程叫做heapify堆化,我们看一下代码:
public static void heapify(int[] arr, int index, int heapSize){
// heapSize是需要的,因为他要管着堆边界
int left = index * 2 + 1; // 左孩子下标
while(left < heapSize){ // 还存在左孩子时(没有左孩子肯定没有右孩子)
// 选择左右孩子大的值 - left+1是右孩子下标
int largest = left + 1 < heapSize && arr[left+1] > arr[left] ? left + 1 : left;
// 父亲和孩子再比较,选择最大的
largest = arr[largest] > arr[index] ? largest: index;
// 判断父节点是连同孩子环境在内的最大,跳出结束堆化过程
if(largest == index){
break;
}
// index还需要往下走,走之前先交换
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
大根堆维护 - 任意一个位置值变化
用户有一个需求,希望0-heapSize-1的位置上有一个值从a变为?号,这个时候应该怎么做呢?
其实结合上面的方式,我们知道首先应该判断?和a的值谁大,如果i位置更新值变小了,就应该往下进行堆化过程,如果更新值更大,就应该往上执行堆插入过程,具体这两个过程之前都分析过了
记住,堆结构最重要的就是存和取,heapinsert和heapify这两个操作,其他都是从这两个操作演化来的。
复杂度计算
这里我们看看上面操纵的复杂度怎么算?
首先对于完全二叉树的高度要有一个印象,N个节点的完全二叉树的高度是logN级别!
用户新增一个值,heapinsert过程就是向上过程,复杂度是O(logN)
用户移除一个值也是同理,复杂度是O(logN)