一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 12 天,点击查看活动详情。
介绍
二叉堆是一种特殊的二叉树,有以下两个特性
- 它是一个完全二叉树,表示树的每一层都有左侧和右侧子节点(除了最后一层的节点),并且最后一层的叶节点尽可能都是左侧子节点,这叫作结构特性。
- 二叉堆不是最小堆就是最大堆。最小堆允许你快速导出树的最小值,最大堆允许你快速导出树的最大值。所有节点都大于等于(最大堆)或小于等于(最小堆)每个它的子节点。这叫作堆特性
思路
我们先创建一个树,然后在插入节点的时候,遵循以下规则
- 每个子节点都要大于等于父节点
- 二叉树是一个满二叉树
根据以上的规则,我们可以选择使用对象或者数组来实现,这里我们使用数组来实现,这样更直观一点。
实现
创建最小堆类
class MinHeap<T> implements IMainHeap<T> {
public heap: T[];
public compare: (a: T, b: T) => number;
constructor(compare: (a: T, b: T) => number) {
//接收比较函数
this.compare = compare;
//使用数组来创建二叉堆
this.heap = [];
}
}
我们首先创建一个MinHeap类,在类的构造函数当中,我们初始化一个数组this.heap,并且接收比较函数compare。
访问特定节点
/**
*
* @param index 通过当前index获取左子树的index
*/
getLeftIndex(index: number): number {
return 2 * index + 1;
}
/**
*
* @param index 通过当前index获取右子树的index
*/
getRightIndex(index: number): number {
return 2 * index + 2;
}
/**
*
* @param index 通过当前index获取父节点的index
*/
getParentIndex(index: number): number {
if (index === 0) {
return undefined;
}
return Math.floor((index - 1) / 2);
}
向堆当中插入值
/**
*
* @param value 插入一个元素到最小二叉堆
*/
insert(value: T): boolean {
// 插入值
if (value === null) {
return false;
}
//将当前值插入到树的最后一位
this.heap.push(value);
this.siftUp(this.heap.length - 1);
return true;
}
/**
*
* @param length 获取数组的尾元素
*/
siftUp(index: number) {
let parent = this.getParentIndex(index);
//遍历堆,将插入值和其父节点做对比没如果插入值小于其父节点的值,那么就互换位置
while (
index > 0 &&
this.compare(this.heap[parent], this.heap[index]) === compareConfig.SMALL
) {
//交换这两个元素
this.swap(this.heap, parent, index);
//将元素的下标交换
index = parent;
parent = this.getParentIndex(index);
}
}
/**
*
* @param array 需要交换元素的数组
* @param indexA 需要交换的元素下标
* @param indexB 需要交换的元素下标
*/
swap(array: T[], indexA: number, indexB: number) {
let temp: T = array[indexA];
array[indexA] = array[indexB];
array[indexB] = temp;
}
向堆当中插入值,首先判断插入的值是否为空,如果为空直接返回fasle。我们先将值插入到对当中的最后this.heap.push(value);,然后调用siftUp方法对堆进行自下而上的从新排列。siftUp方法当种,接收一个index参数,是当前堆的最后一个元素的下标,将堆最后一个元素的值和其父元素比较,如果堆最后一个元素小于父元素,那么就交换位置。然后将index赋值为parent。
获取堆当中的最小值
/**
*
* @returns 返回最小值,但是不会删除它
*/
findMinMum(): T {
return this.isEmpty() ? undefined : this.heap[0];
}
size(): number {
return this.heap.length;
}
isEmpty(): boolean {
if (this.size() === 0) {
return true;
}
return false;
}
导出对当中的最小值
/**
*
* @returns 移除最小值,并且将他返回
*/
extract(): T {
if (this.isEmpty()) {
return undefined;
}
if (this.size() === 1) {
return this.heap[0];
}
//把堆当中第一个元素取出,最小对当中,这个元素是最小值,最大堆当中,这个值是最大值
const removeValue = this.heap.shift();
this.heap.unshift(this.heap.pop());
this.siftDown(0);
return removeValue;
}
/**
*
* @param index 获取更新节点
*/
siftDown(index: number) {
//将index保存到index
let element = index;
//获取左节点下标
const left = this.getLeftIndex(element);
// 获取右节点下标
const right = this.getRightIndex(element);
// 获取堆长度
const size = this.size();
//判断:如果当前值小于左节点的值
if (
left < size &&
this.compare(this.heap[left], this.heap[element]) === compareConfig.SMALL
) {
element = left;
}
if (
right < size &&
this.compare(this.heap[right], this.heap[element]) === compareConfig.SMALL
) {
element = right;
}
//如果index不等于element,说明element改变了
if (index !== element) {
this.swap(this.heap, element, index);
this.siftDown(element);
}
}
先将首元素弹出堆,这样最小值已经被弹出堆了,然后我们要对最小堆进行重新排列,找出最小值放到根节点上(也就是数组下标为 0 的位置),我们首先将堆的最后一个元素放到根节点的位置上,然后调用siftDown方法对其进行自上而下的重新排序。
siftDown方法接收一个参数index,表示从下标为index的位置开始重新排序,我们先将index赋值给element。然后我们虎丘当前index节点的左右节点left和right,还有获取堆长度size。我们首先判断element下标的节点值和它左节点的值比较,如果element节点值大于左节点值,那么就将左左节点的下标值赋值给element,然后再判端右节点的值和element的节点值进行比较,如果右节点的值小于element节点值,那么就将right赋值给element。最后,如果element不等于index,说明中间有节点需要进行交换,所以调用swap方法来交换,然后递归调用this.siftDown(element);
完整代码
interface ICompareConfig {
BIG: number;
SMALL: number;
EQUAL: number;
}
const compareConfig: ICompareConfig = {
BIG: 1,
SMALL: -1,
EQUAL: 0,
};
/**
* 最小二叉堆
*/
interface IMainHeap<T> {
getLeftIndex(index: number): number;
getRightIndex(index: number): number;
getParentIndex(index: number): number;
insert(value: T): boolean;
extract(): T;
findMinMum(): T;
siftUp(length: number);
swap(array: T[], indexA: number, indexB: number);
}
class MinHeap<T> implements IMainHeap<T> {
public heap: T[];
public compare: (a: T, b: T) => number;
constructor(compare: (a: T, b: T) => number) {
//接收比较函数
this.compare = compare;
//使用数组来创建二叉堆
this.heap = [];
}
/**
*
* @param length 获取数组的尾元素
*/
siftUp(index: number) {
let parent = this.getParentIndex(index);
//遍历堆,将插入值和其父节点做对比没如果插入值小于其父节点的值,那么就互换位置
while (
index > 0 &&
this.compare(this.heap[parent], this.heap[index]) === compareConfig.SMALL
) {
//交换这两个元素
this.swap(this.heap, parent, index);
//将元素的下标交换
index = parent;
parent = this.getParentIndex(index);
}
}
/**
*
* @param array 需要交换元素的数组
* @param indexA 需要交换的元素下标
* @param indexB 需要交换的元素下标
*/
swap(array: T[], indexA: number, indexB: number) {
let temp: T = array[indexA];
array[indexA] = array[indexB];
array[indexB] = temp;
}
/**
*
* @param index 通过当前index获取左子树的index
*/
getLeftIndex(index: number): number {
return 2 * index + 1;
}
/**
*
* @param index 通过当前index获取右子树的index
*/
getRightIndex(index: number): number {
return 2 * index + 2;
}
/**
*
* @param index 通过当前index获取父节点的index
*/
getParentIndex(index: number): number {
if (index === 0) {
return undefined;
}
return Math.floor((index - 1) / 2);
}
/**
*
* @param value 插入一个元素到最小二叉堆
*/
insert(value: T): boolean {
// 插入值
if (value === null) {
return false;
}
//将当前值插入到树的最后一位
this.heap.push(value);
this.siftUp(this.heap.length - 1);
return true;
}
/**
*
* @returns 移除最小值,并且将他返回
*/
extract(): T {
if (this.isEmpty()) {
return undefined;
}
if (this.size() === 1) {
return this.heap[0];
}
//把堆当中第一个元素取出,最小对当中,这个元素是最小值,最大堆当中,这个值是最大值
const removeValue = this.heap.shift();
this.heap.unshift(this.heap.pop());
this.siftDown(0);
return removeValue;
}
/**
*
* @returns 返回最小值,但是不会删除它
*/
findMinMum(): T {
return this.isEmpty() ? undefined : this.heap[0];
}
size(): number {
return this.heap.length;
}
isEmpty(): boolean {
if (this.size() === 0) {
return true;
}
return false;
}
/**
*
* @param index 获取更新节点
*/
siftDown(index: number) {
//将index保存到index
let element = index;
//获取左节点下标
const left = this.getLeftIndex(element);
// 获取右节点下标
const right = this.getRightIndex(element);
// 获取堆长度
const size = this.size();
//判断:如果当前值小于左节点的值
if (
left < size &&
this.compare(this.heap[left], this.heap[element]) === compareConfig.SMALL
) {
element = left;
}
if (
right < size &&
this.compare(this.heap[right], this.heap[element]) === compareConfig.SMALL
) {
element = right;
}
//如果index不等于element,说明element改变了
if (index !== element) {
this.swap(this.heap, element, index);
this.siftDown(element);
}
}
}
function compare<T>(indexKey: T, insertKey: T) {
if (indexKey > insertKey) {
return -1;
} else if (indexKey < insertKey) {
return 1;
} else {
return 0;
}
}