性质:
- 度:子树就是二叉树的分支。度就是分支的数目。
- 完全二叉树:除最后一层可能不满以外,其他各层都达到该层节点的最大数,最后一层如果不满,该层所有节点都全部靠左排。如果最后一层的节点数也达到最大就是慢二叉树。
- 堆:实际上就是一棵完全二叉树(以一个下标以0开头的数组{16,7,3,20,17,8为例,从上至下,从左至右一次填入堆中)。
- 有最大堆(堆顶元素的值是最大的)和最小堆(堆顶元素的值是最小的)。
- 总有以arr[i]为根的节点,其左孩子节点为arr[2*i+1],右孩子节点为arr[2*i+2].(前提:节点总个数等于数组元素个数,2*i+1 < arr.length-1 , 2*i+2
堆排序基本思想:
构建最大堆,取出堆顶最大元素与最后一个元素交换位置;
此时堆顶已不是最大元素,需将剩余节点以同样算法构建成最大堆,取出堆顶可得剩余节点中最大元素与剩余节点的最后一个元素交换位置;
重复前两步直至结束。
- 详解
1. 构建最大堆
1. 首先,将数组按自然下标构建成一个无序堆。我们知道最大堆(假设节点数为n吧)的每个子树都符合最大堆的性质(根节点大于左右孩子节点)。一个子堆的堆顶元素的左右子节点下标必然满足2*i+1<=n-1且2*i+2<=n-1,得i<=n/2-1. R[0]~R[n/2-1]这些节点才会成为子堆的堆顶元素,我们定义函数maxify(i)将以i位置节点为根的子树都构建成最大堆。
2. 取堆顶最大元素
1. 将初始待排序关键字序列(R0,R1,R2....R[n-1])构建成大顶堆,此堆为初始的无序区;
2. 将堆顶元素R[0]与最后一个元素R[n-1]交换,此时得到新的无序区(R0,R1,R2,......Rn-2)和新的有序区(R[n-1]),且满足R[0,1,2...n-2]<=R[n-1];
3. 由于交换后新的堆顶R[0]可能违反堆的性质,因此需要对当前无序区(R0,R1,R2,......Rn-2)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R0,R1,R2....Rn-3)和新的有序区(Rn-1,Rn-2)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
代码
import org.junit.Test;
public class HeapSortTest {
private int[] data = new int[]{5, 3, 6, 2, 1, 9, 4, 8, 7};
private int size = data.length;
@Test
public void run(){
buildMapHeap(size-1);//将所有元素构造在最大堆里
System.out.print("--------begin:------");
for(int a : data){ System.out.print(a); } System.out.println();
fetchMax();//一层一层,逐一取出堆顶最大元素放在数组最后,剩下的元素生成最大堆
System.out.print("--------end:------");
for(int a : data){ System.out.print(a); } System.out.println();
}
//构建最大堆 getLeft(i)=i*2+1<=lastIndex && getRight(i)=i*2+2<=lastIndex
private void buildMapHeap(int lastIndex){
for(int i=(lastIndex-1)/2; i>=0; i--)
maxify(i);
}
/**
* 将以i为根的子堆(无序)构建成最大堆
* @param i 子堆的根节点
*/
private void maxify(int i){
int left = getLeft(i);
int right = getRight(i);
// System.out.println("("+ data[i]+ ")i is "+ i +", (" + data[left] +")left is "+ left + ", ("+ data[right] +")right is " + right);
int maxIndex = i;
//如果"data[left]>data[maxIndex]"放在左边作为第一个条件的话,可能left>data.length-1,会报nullPointer错误
if( left<=size-1&& data[left]>data[maxIndex] )
maxIndex = left;
if( right<=size-1 && data[right]>data[maxIndex] )
maxIndex = right;
if( i!=maxIndex ){
swap(i, maxIndex);
maxify(maxIndex);//如果有位置交换,那么交换后,data[maxIndex]的值发生变化,以data[maxIndex]为根的堆现在最大值可能并不是data[maxIndex];在 3-2-1这个堆和2-8-7这个堆的根交换位置时就有这种情况
}
}
private void fetchMax(){
for(int i=data.length-1; i>0; i--){
swap(0, i);
size--;
buildMapHeap(i-1);
}
}
private int getLeft(int i){
return i*2+1;
}
private int getRight(int i){
return i*2+2;
}
private void swap(int x, int y){
int sum = data[x]+data[y];
data[y] = sum - data[y];
data[x] = sum - data[y];
}
}