堆的概念
堆是具有下列性质的二叉树:每个结点的值都大于或等于其左右孩子结点的值,成为大顶堆,反之,则是小顶堆。
如果按照层序遍历的方式给结点从1开始编号,则结点满足一下关系:
这里i小于等于[n/2]是因为二叉树的性质5:一颗完全二叉树,如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲结点是[i/2]。那么对于n个节点的二叉树而言,它的i值自然就是小于等于[n/2]。这个结论在后面的代码中会运用到。
如果将上述大顶堆用层序遍历存入数组,则一定满足上面的关系,但需要注意的是数组下标是从0开始,对应i编号的关键字在数组中其实是i-1
0 1 2 3 4 5 6 7 8
90 70 80 60 10 40 50 30 20
堆排序算法
堆排序就是利用堆(我用的是大顶堆)进行排序的方法。
思想:构造大顶堆后,整个序列的最大值就是堆顶的根结点。移走根结点,然后将剩余的序列重新构造成大顶堆,如此反复,最终将会得到一个有序序列。移走根节点其实就是将其与堆数组的末尾元素交换。
代码
//排序算法中用的比较频繁
public static void swap(int[] test,int i,int j) {
int temp = test[i];
test[i] = test[j];
test[j] = temp;
}
public static void heapSort(int[] test) {
//构造大顶堆 按照层序遍历的方式给节点编号
//将每个非终端节点 (1~test.length/2对应编号的节点)当作根节点,将其和其子树调整成大顶堆
for (int i = test.length / 2; i > 0; i--) {
heapAdjust(test,i,test.length);
}
for (int i = test.length -1; i > 0; i--) {
//将根节点也就是堆顶记录与末尾元素交换
swap(test, 0, i);
//交换过后,末尾元素就是最大值了
//重构大顶堆
heapAdjust(test, 1, i);
}
}
//构造大顶堆 第一个参数是编号,第二个参数是指定数组的长度
private static void heapAdjust(int[] test, int s, int m) {
//注意:这里s和i都是编号,对应数组的计算的下标都要减1
int temp = test[s-1];
for (int i = 2 * s; i <= m; i*=2) {
//找以s作为编号的节点下的孩子中的最大值
//i < m说明不是最后一个元素,否则数组下标越界
if (i < m && test[i-1] < test[i]) {//左孩子小于右孩子
++i;//i为关键字较大的记录的下标
}
//根节点如果已经是最大了的,就跳出循环
if (temp >= test[i-1]) {
break;
}
test[s-1] = test[i-1];
//将编号i的节点赋给s
s = i;
}
//for循环结束后 插入
test[s-1] = temp;
}