堆树是一棵完全二叉树,且每一个节点的值都大于等于或者小于等于其左右节点的值。通过这一结构可以快速找到堆中的最大值或最小值。
满二叉树:除了叶子节点,每个节点的度都为2 完全二叉树:二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布
小顶堆:根节点最小的堆
大顶堆:根节点最大的堆
堆树的存储结构
由于完全二叉树的性质,堆树可以直接利用数组来存储,假设根节点在数组中下标为0,那么父节点和子节点的关系如下:
- 下标为 i 的节点其左节点下标为 2*i+1,位运算 (i << 1)+1
- 下标为 i 的节点其右节点下标为 2*i+2,位运算 (i << 1)+2
- 下标为 i 的节点其父节点下标为 (i-1)/2,位运算 (i-1) >> 1

堆树的插入
堆树的插入操作也叫堆化,常用的插法是从尾部插入,时间复杂度为O(logN)
- 新节点统一插入到数组尾部
- 依次向上调整,直到满足小于(大于)父节点的要求
以小顶堆为例,在数组 {3, 5, 8, 7, 10, 12} 中插入 6 ,首先将6插入到数组尾部
接着向上调整,与父节点比较,若小于父节点则需要交换位置,直到满足条件,完成插入。6小于8,所以6与8互换位置,6大于3,满足堆树条件,插入完成。

堆树的删除
为了保证完全二叉树的性质(最后一层节点一次靠左),不能直接将节点删除再把后续节点依次向上调整,正确的做法是
- 将要删除的节点与尾节点替换,删除尾节点
- 依次将节点向下调整,直到符合堆树条件
时间复杂度为O(logN),以小顶堆为例,数组 {3, 5, 6, 7, 10, 12, 8},删除根节点3,首先与尾节点交换位置,删除尾节点
依次向下调整直到符合堆树条件
代码实现
/*** @author :松岛安* @slogan :社会主义码农* @description :堆树结构实现*/public class HeapTree {private static int[] heap;private static int size;public HeapTree(int capacity){size = 0;heap = new int[capacity+1];Arrays.fill(heap, -1);}public boolean isFull(){return size == heap.length;}public boolean isEmpty(){return size == 0;}//获取父节点下标public int getParent(int i){if((i-1) >> 1 >= 0){return (i-1) >> 1;}else{return -1;}}//获取左子节点下标public int getLeftChild(int i){if((i<<1) + 1 <= heap.length){return (i<<1) + 1;}else{return -1;}}//获取右子节点下标public int getRightChild(int i){if((i<<1) + 2 <= heap.length){return (i<<1) + 2;}else{return -1;}}//查找最大孩子节点下标public int getMaxChild(int i){int leftChild = getLeftChild(i);int rightChile = getRightChild(i);if(leftChild < 0 || rightChile < 0){return -1;}return heap[leftChild] >= heap[rightChile] ? leftChild : rightChile;}//获取最小孩子节点下标public int getMinChild(int i){int leftChild = getLeftChild(i);int rightChile = getRightChild(i);if(leftChild < 0 || rightChile < 0){return -1;}return heap[leftChild] < heap[rightChile] ? leftChild : rightChile;}//大顶堆节点向上调整public void bigHeapUp(int i){int insertedValue = heap[i];while (i > 0 && insertedValue > heap[getParent(i)]){//向上调整heap[i] = heap[getParent(i)];i = getParent(i);}heap[i] = insertedValue;}//小顶堆节点向上调整public void smallHeapUp(int i){int insertedValue = heap[i];while (i > 0 && insertedValue < heap[getParent(i)]){//向上调整heap[i] = heap[getParent(i)];i = getParent(i);}heap[i] = insertedValue;}//大顶堆节点向下调整public void bigHeapDown(int i){int maxChild;int temp = heap[i];while (getRightChild(i) < size){maxChild = getMaxChild(i);if(temp > heap[maxChild]){break;}//孩子节点上移heap[i] = heap[maxChild];i = maxChild;}heap[i] = temp;}//小顶堆节点向下调整public void smallHeapDown(int i){int minChild;int temp = heap[i];while (getRightChild(i) < size){minChild = getMinChild(i);if(temp < heap[minChild]){break;}heap[i] = heap[minChild];i = minChild;}heap[i] = temp;}//查找最大值或最小值public int getRoot(){return heap[0];}//大顶堆插入public void bigInsert(int value){if(isFull()){throw new RuntimeException("the heap is full");}heap[size] = value;size ++;bigHeapUp(size-1);}//小顶堆插入public void smallInsert(int value){if(isFull()){throw new RuntimeException("the heap is full");}heap[size] = value;size ++;smallHeapDown(size-1);}//大顶堆删除public void bigDelete(int index){if(isEmpty()){throw new RuntimeException("the heap is empty");}int deleteNode = heap[index];heap[index] = heap[size - 1];size --;bigHeapDown(index);}//小顶堆删除public void smallDelete(int index){if(isEmpty()){throw new RuntimeException("the heap is empty");}int deleteNode = heap[index];heap[index] = heap[size - 1];size --;smallHeapDown(index);}}
堆排序
堆排序就是将数组先构造成一棵堆树,然后在这基础上进行排序。假设有一个数组 {8,4,21,6,3,1,26,14,18},要讲数组从小到大进行排序,其堆树结构如图
排序过程
- 从最后一个非叶子节点开始,从后往前开始堆化(叶子节点下没有节点可以进行比较),构建一棵大(小)顶堆
- 将堆顶节点与最后一个节点进行替换,根节点重新开始堆化,记录最后一个节点(该元素已经是最大(小)的元素,下次不必再与该元素交换),依次往后进行排序
堆化
找到第一个非叶子节点6,开始进行堆化
第二个非叶子节点21
第三个非叶子节点4
第四个非叶子节点8
至此,我们的大顶堆构建完毕。
排序
先将堆顶节点与尾节点进行替换,然后重新开始堆化
重复上面操作






至此,整个排序工作完成
代码实现
public class HeapSort {/** @Author 松岛安* @Description 大顶堆堆化* @Param data 数组* @Param start 开始位置* @Param end 结束位置,记录上次已完成排序的节点* */public static void maxHeap(int[] data, int start, int end){int parent = start;//获取左子节点下标//为什么不用右节点判断? 因为有可能出现2*start+2>end,而2*start+1=end的情况,导致左节点没有进行堆化int son = 2 * start + 1;//为什么不能是 son<=end,因为下标为end的节点已经完成堆化,不需要再进行交换,end参数的意思是end-1之前的才需要堆化,end及end之后的不需要堆化while(son < end){int temp = son;//获取最大子节点下标//为什么不能是 son+1<=end,因为小标为end的节点已经完成堆化,不需要再进行交换,end参数的意思是end-1之前的才需要堆化,end及end之后的不需要堆化if(son+1 < end && data[son] < data[son+1]){temp = son + 1;}//如果堆化父节点大于子节点,堆化完成if(data[parent] > data[temp]){return;}else{//父节点与子节点交换位置int t = data[parent];data[parent] = data[temp];data[temp] = t;//重新调整父节点与子节点下标,开始下一轮堆化parent = temp;son = 2*parent +1;}}return;}public static void heapSort(int[] data){//尾节点下标/*int len = data.length-1;*/int len = data.length;//从第一个非叶子节点依次向上堆化//len = data.length-1,会造成最后一个节点没有堆化(因为堆化中的比较是son<end而不是son<=end,相等进入不了下一次循环),但这不影响后续操作,因为最后一个节点会与堆顶进行互换//最后一个节点要堆化则 len = data.lengthfor(int i = (len-2)>>1; i>=0; i--){maxHeap(data, i, len);}//堆顶和尾节点互换,i--,继续进行排序for(int i = len-1; i>=0; i--){int temp = data[i];data[i] = data[0];data[0] = temp;//互换后从新进行堆化,这里最后一个节点已经排好序,不用堆化,第一次堆化时i=data.length-1,证明了最后一个节点没有堆化maxHeap(data, 0, i);}}public static void main(String[] args) {int[] data = new int[]{8,6,12,45,78,2,36,54};heapSort(data);System.out.println(Arrays.toString(data));}
