二叉堆详解
一、什么是二叉堆
二叉堆是完全二叉树或者是近似完全二叉树,它分为两个类型:
- 最大堆
- 最小堆
最大堆是指任何一个父节点的值,都大于等于它左右孩子节点的值;最小堆是指任何一个父节点的值,都小于等于它左右孩子节点的值
二、二叉堆的自我调整
1.插入节点
我们以下面的最小堆为例,插入元素
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YiN0ctJ0-1593423185458)(./二叉堆1.png)]](https://images2.imgbox.com/30/db/XenIS42H_o.png)
新插入的元素为0,将新元素置于二叉堆最后一个位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PpXfLzF8-1593423185461)(./二叉堆2.png)]](https://images2.imgbox.com/87/a6/MdT95Klc_o.png)
我们让新元素与它的父节点比较,如果新元素小于父节点,则与父节点交换位置,否则调整结束
新元素0与父节点5比较,0小于5,0与5交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OV7P0E8s-1593423185463)(./二叉堆3.png)]](https://images2.imgbox.com/98/c6/MtwCOcJl_o.png)
新元素0继续与父节点2比较,0小于2,0与2交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QoYh0J9d-1593423185464)(./二叉堆4.png)]](https://images2.imgbox.com/3c/24/L1KauGKI_o.png)
新元素0继续与父节点1比较,0小于1,0与1交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q0VX2myY-1593423185467)(./二叉堆5.png)]](https://images2.imgbox.com/fc/8a/AkaNeXwt_o.png)
新元素0到了堆顶的位置,调整结束
2.删除节点
我们同样以前面的二叉堆为例,进行删除元素,删除堆顶元素1
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-52RtO65E-1593423185469)(./二叉堆6.png)]](https://images2.imgbox.com/82/f6/f0typQ7i_o.png)
我们将最后一个元素10补到堆顶
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eHWBtS3B-1593423185470)(./二叉堆7.png)]](https://images2.imgbox.com/a1/26/cP5PpLrg_o.png)
接下来,我们将补到堆顶的元素10与它的左右孩子比较,若同时小于左右孩子,则调整结束,否则与左右孩子中较小的一个交换位置
10分别与2、3比较,10大于2、3,2是左右孩子中较小的一个,10与2交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CkSLUOK9-1593423185472)(./二叉堆8.png)]](https://images2.imgbox.com/95/b7/y7mzmy6m_o.png)
10继续与左右孩子4、5比较,10大于4、5,4是左右孩子中较小的一个,10与4交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ycOQn1wI-1593423185473)(./二叉堆9.png)]](https://images2.imgbox.com/b6/e8/tZXk3Dl7_o.png)
10继续与左右孩子8、9比较,10大于8、9,8是左右孩子中较小的一个,10与8交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YJWqB17t-1593423185474)(./二叉堆10.png)]](https://images2.imgbox.com/57/be/IKizUCFk_o.png)
此时元素10已无孩子节点,调整结束
3.构建二叉堆
构建二叉堆,就是把一个无序的完全二叉树调整为二叉堆,本质上就是让所有非叶子节点依次下沉
我们以下面无序的完全二叉树进行调整,构建二叉堆
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YkW7dGWo-1593423185475)(./二叉堆11.png)]](https://images2.imgbox.com/48/48/dfuXahok_o.png)
我们先找到最后一个非叶子节点3,将它与它的左右孩子比较,若同时小于左右孩子,则该叶子节点调整结束,否则与左右孩子中较小的一个交换位置
3与1比较,3大于1,3与1交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eq2IuFBZ-1593423185476)(./二叉堆12.png)]](https://images2.imgbox.com/18/91/kmfwOW5E_o.png)
接着比较下一个非叶子节点7,7大于孩子节点2、4,其中2是较小的孩子节点,7与2交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpq04rB0-1593423185477)(./二叉堆13.png)]](https://images2.imgbox.com/9e/9c/Ql2ll3Xn_o.png)
接着比较下一个非叶子节点8,8同时小于孩子节点9、10,不需要交换位置
继续比较下一个非叶子节点6,6大于孩子节点1、2,其中1是较小的孩子节点,6与1交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SnhsjKeM-1593423185478)(./二叉堆14.png)]](https://images2.imgbox.com/70/8a/UEvt7QUb_o.png)
交换后,6还有孩子节点3,6与3比较,6大于孩子节点3,6与3交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tRE262A8-1593423185479)(./二叉堆15.png)]](https://images2.imgbox.com/4f/63/CHF4Lj7j_o.png)
接着比较最后一个非叶子节点5,5与孩子节点1、8比较,5大于1,5与1交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7YCRabQK-1593423185480)(./二叉堆16.png)]](https://images2.imgbox.com/a9/b8/NmLsY4lF_o.png)
5继续与孩子节点2、3比较,5大于2、3,其中2是较小的孩子节点,5与2交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vITRmATX-1593423185481)(./二叉堆17.png)]](https://images2.imgbox.com/3c/18/Pyva4Xu1_o.png)
5继续与孩子节点7、4比较,5大于4,5与4交换位置
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5J9Xcmn-1593423185482)(./二叉堆18.png)]](https://images2.imgbox.com/fd/85/FDOxapNt_o.png)
自此,二叉堆的构建完成
三、代码实现
二叉堆的所有节点都存在数组中,若父节点的下标是parent,则左右孩子节点的下标分别为2parent+1,2parent+2
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wk1hVjzl-1593423185483)(./二叉堆19.png)]](https://images2.imgbox.com/62/77/vNerNuta_o.png)
// 上浮调整
function upAdjust(arr) {
let childIndex = arr.length - 1;
let parentIndex = parseInt((childIndex - 1) / 2);
// 缓存子节点的值,用于最后进行赋值,而不需要每一步进行交换
let temp = arr[childIndex];
while (childIndex > 0 && temp < arr[parentIndex]) {
arr[childIndex] = arr[parentIndex];
childIndex = parentIndex;
parentIndex = parseInt((childIndex - 1) / 2);
}
arr[childIndex] = temp;
}
// 下沉调整
function downAdjust(arr, parentIndex, length) {
// 缓存父节点的值,用于最后进行赋值,而不需要每一步进行交换
let temp = arr[parentIndex];
// 孩子节点下标,暂时定为左孩子节点下标
let childIndex = 2 * parentIndex + 1;
while (childIndex < length) {
// 当存在右孩子节点,且右孩子节点的值小于左孩子节点的值,childIndex记录为右孩子节点的下标
// childIndex实际记录的是最小的孩子节点的下标
if (childIndex + 1 < length && arr[childIndex + 1] < arr[childIndex]) {
childIndex++;
}
// 如果父节点的值比孩子节点的值都小,则调整结束
if (temp <= arr[childIndex]) {
break;
}
// 将最小的孩子节点的值赋值给父节点
arr[parentIndex] = arr[childIndex];
parentIndex = childIndex;
childIndex = 2 * parentIndex + 1;
}
arr[parentIndex] = temp;
}
function buildHeap(arr) {
// 从最后一个非叶子节点开始下沉调整
for (let i = parseInt(arr.length / 2) - 1; i >= 0; i--) {
downAdjust(arr, i, arr.length);
}
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0];
upAdjust(arr);
console.log(arr);
arr = [5, 6, 8, 7, 3, 10, 9, 2, 4, 1];
buildHeap(arr);
console.log(arr);