什么是堆?
首先要明白堆是基于二叉树而言的,堆是二叉树的一种形式。
并且堆是完全二叉树,具有完全二叉树的特点。
堆只有2种:大顶堆和小顶堆。
大顶堆:就是根节点大于子节点,小顶堆反之。
下面上图:
构造堆
一棵完全二叉树如何才能转换成一个堆呢?
把握好堆的特点:大顶堆的根永远大于左右孩子,那么只需要把每一个子树中的左右孩子的较大值放到根的上面即可。
以大顶堆为例:
观察以上的例子,通过自下向上进行堆的构造其实并不复杂,不过在上述例子的第三步可以看出发生交换的节点的子树的堆平衡可能被破坏,需要重新对子树的堆进行构造(第四步)。
Java实现堆的生成
因为堆是完全二叉树,所以需要使用到完全二叉树的一些特性
有关二叉树和完全二叉树的生成代码可查看二叉树及完全二叉树
对单个节点进行调整是上面图中的单个子树的交换过程
这个代码实现一定要知道完全二叉树的特性:
序列[8 1 3 9 12 21]
将序列从0开始编号,第0的是根节点,第1个是左孩子,第2个是右孩子
规律:第i个节点的左孩子是2i+1,右孩子是2i+2,,所以可以直接使用序列来调整完全二叉树。
//对单个节点进行调整
public static void node(int[] a,int i){
//若此二叉树有左子树
if(2*i+1 < a.length){
//若此二叉树有右子树
if(2*i+2 < a.length){
//找到左右子树中的最大值与根节点交换
if (a[2*i+1] > a[i] && a[2*i+1] > a[2*i+2]){
int swap = a[i];
a[i] = a[2*i+1];
a[2*i+1] = swap;
}
if (a[2*i+2] > a[i] && a[2*i+2] > a[2*i+1]){
int swap = a[i];
a[i] = a[2*i+2];
a[2*i+2] = swap;
}
}else {
if (a[2*i+1] > a[i]){
int swap = a[i];
a[i] = a[2*i+1];
a[2*i+1] = swap;
}
}
}
}
以上代码完成可能会留有一个疑问:调整中可能出现上面第4步的问题,调整后该孩子节点被破坏了!那么需要对调整后的节点进行往复的调整,递归思想!!!
完整的调整代码:
//对单个节点进行调整
public static void node(int[] a,int i){
//若此二叉树有左子树
if(2*i+1 < a.length){
//若此二叉树有右子树
if(2*i+2 < a.length){
//找到左右子树中的最大值与根节点交换
if (a[2*i+2] > a[i] && a[2*i+2] > a[2*i+1]){
int swap = a[i];
a[i] = a[2*i+2];
a[2*i+2] = swap;
//子节点可能平衡被破坏,继续进行子节点的调整
node(a,2*i+2);
}
if (a[2*i+1] > a[i] && a[2*i+1] > a[2*i+2]){
int swap = a[i];
a[i] = a[2*i+1];
a[2*i+1] = swap;
node(a,2*i+1);
}
}else {
if (a[2*i+1] > a[i]){
int swap = a[i];
a[i] = a[2*i+1];
a[2*i+1] = swap;
node(a,2*i+1);
}
}
}
}
完成了单个节点的调整后对整个序列进行一个堆的生成,接着调用上面的单个调整方法自下向上进行调整。
public static int[] heap(int[] a){
int length = a.length;
while (true) {
if (length == 0) break;
node(a,--length);
}
return a;
}
堆和堆排序
一个堆的顶端是一个序列的最大值或者最小值!排序的思路可以是每次拿走最大值让剩余的序列重新生成一个堆,往复取根节点的最大值。
为了保证最大的空间利用率,将生成堆的最大值a[0]与最后的值交换,接着将前面的序列重新生成一个堆,重复上述步骤。
Java实现堆排序
基于单个节点的调整代码注意事项
因为单个堆调整代码中存在一个对后续的堆的破坏进行检验的操作(上面的代码中的递归),而堆排序的序列末尾会放置最大值会被重新平衡掉,所以需要控制调整节点的范围:
public static void node(int[] a,int i,int length){
//传入长度来控制节点的范围
if(2*i+1 <= length){
//若此二叉树有右子树
if(2*i+2 <= length){
//找到左右子树中的最大值与根节点交换
if (a[2*i+1] > a[i] && a[2*i+1] >= a[2*i+2]){
int swap = a[i];
a[i] = a[2*i+1];
a[2*i+1] = swap;
node(a,2*i+1,length);
}
if (a[2*i+2] > a[i] && a[2*i+2] > a[2*i+1]){
int swap = a[i];
a[i] = a[2*i+2];
a[2*i+2] = swap;
//子节点可能平衡被破坏,继续进行子节点的调整
node(a,2*i+2,length);
}
}else {
if (a[2*i+1] > a[i]){
int swap = a[i];
a[i] = a[2*i+1];
a[2*i+1] = swap;
node(a,2*i+1,length);
}
}
}
}
接着要重新修改堆生成的方法,实现控制堆排序长度
public static int[] heap(int[] a,int length){
int i = length;
while (true) {
node(a,i,length);
if (length == 0) break;
length--;
}
return a;
}
最终进行一个堆排序方法,生成堆后最大值a[0]放到后面,然后对前面的序列继续进行堆排序
public static void sort(int[] a){
int n = a.length-1;
while (true){
heap(a,n);
int swap = a[0];
a[0] = a[n];
a[n] = swap;
if (n == 0) break;
n--;
}
}