首先我们看看堆的定义:
所谓堆是一个满足以下两个条件的二叉树:1、完全二叉树,即除了最后一层右边的元素可能缺位,其他都是满的。2、每个父母节点的键要大于其子女节点的键(父母节点都大于子女节点的键值叫最大堆,都小于叫最小堆)。
堆排序的第一步就是要将给定序列构造出一个堆。
我们暂且用数组来实现一个堆。我们以最大堆为例,对于构造一个堆,有两种算法,自底向上以及自顶向下法。下面我们分别来看看这两种算法:
自底向上法:这一算法的基本思路是将给定序列先全部放到一颗完全二叉树上,然后从最后一个父母节点开始检验,看它是否满足大于子女节点这个条件,若满足则看前一个父母节点,若不满足,则将父节点和较大的子女节点交换位置,再看前一个父母节点,重复这个步骤一直到检查完所有的父母节点。
这个算法实现起来还是比较容易的,只不过有几个地方要注意一下。
1、对于一个n元素的堆有下面几个结论:
a、父母节点位于前 n/2 ,子女节点位于后n/2
b、若一个父母节点的位置为 i ,则其子女节点分别为 2*i 和 2*i+1,若一个节点堆序号为 i ,则其父母节点的堆序号为 i/2
注意:以上结论都建立在堆序号是从1 开始的基础上,如果我们用数组来实现堆,那么就要注意数组元素下标和堆序号的关系。
自底向上法构造堆用伪代码表述如下:
void heapButtonUp(int a[1...n]){
//用自底向上法构造最大堆
//输入:数组a[1...n]
//输出:大堆a[1...n]
for i←n/2 to 1 do //从1到n/2表示的是父母节点
k←i; //记录最后一个父母节点,以便后面改变
v←a[k]; //
heap←false;
while not heap and 2*k<=n
j←2*k; //j表示k的一个子女节点
if j<n //表明k有两个子女节点
if a[j]<a[j+1]
j←j+1;
//到此j表示k的较大子女节点的序号
if v>a[j]{ //满足要求,不要往下掉
heap←true;
}else {//不满足要求,往下掉,再次判断
a[k]←a[j];
k←j; //父母节点变为j,再判断
a[j]←v;
}
}
heapSort(int[] a){
//利用自底向上法构造大堆对数组进行堆排序
//输入:可排序数组a[1...n]
//输出:有序数组a[1...n]
for i← 0 to n-1
heapButtonUp(a[1...n-i];//构造出一个大堆
a[1]↔a[n-i]; //交换堆顶元素和最后一个元素
}
package com.poe.sort;
import java.util.Scanner;
/**
* 自底向下法构造最大堆
*
***** 注意点:节点序号不能从0开始,要从1开始,否则第一个父母节点找不到子女节点******** 第k号父母节点的子女节点序号为
* 2k,2k+1是建立在节点序号从1开始的基础上的,这样要对数组的下标进行处理
*
* @author Sam
*
*/
public class HeapButtonUp {
private static int[] array; // 待构造堆的数组,由命令行输入
/**
* 用自底向下构造最大堆 end表示要构造成堆的数组的终止下标
*
* @param a为待构造堆得数组
*/
public static void heapBu(int[] a, int end) { // end表示数组的序号
int heapEnd = end + 1; // 堆序号为1...heapEnd
for (int i = heapEnd / 2; i > 0; i--) { // 堆序号1...end+1,其中前一半为父母节点,后一半为叶子节点//第一个父母节点为a.length/2,然后要检验这个父母节点是否满足要求
System.out.println("检查" + i + "号节点");
int k = i; // k表示父母节点的下标,在后面可能会改变,因此不直接用i
int v = a[i - 1]; // 保存第一个父母节点的值,堆序号和数组序号相差1
boolean heap = false; // 是否满足最大堆的标志,为true表示满足,false表示不满足
while (!heap && 2 * k <= heapEnd) {// 当还不满足最大堆以及子女节点还在范围内时执行此循环
// 内循环的目的是:1、保证第一次检验时此父母节点满足要求,2、若发生交换,还要求交换后的新父母节点也满足要求,即要判断是否还需要往下掉
int j = 2 * k; // j代表第k个父母节点的子女节点,而且是较大子女的下标
if (j < heapEnd) {// 表示第k个节点有两个子女节点
if (a[j - 1] <= a[j + 1 - 1]) // 找到两个子女节点中的较大值
j = j + 1;
}
// 到此,k号父母节点的较大子女节点为j号
if (v >= a[j - 1]) { // 若父母节点的值大于较大子女节点,则不需要往下掉,也就不再需要再次内循环,退出内循环
System.out.println(i + "号节点满足要求");
heap = true;
} else { // 父母节点往下掉,还要再次内循环,直到最底层
System.out.println(i + "号节点不满足要求");
a[k - 1] = a[j - 1]; // 将父母节点与较大子女节点交换位置,并将掉下来的原父母节点所在的位置设为新的父母节点,进行内循环
k = j;
a[j - 1] = v;
}
}
}
}
public static void main(String[] args) {
array = new int[10];
System.out.println("请输入要排序的元素:");
Scanner sc = new Scanner(System.in);
for (int i = 0; i < 10; i++) {
System.out.print("array[" + i + "]=");
array[i] = sc.nextInt();
System.out.println();
}
System.out.print("排序前:");
for (int i = 0; i < array.length; i++)
System.out.print(array[i] + " ");
System.out.println();
for (int i = 0; i < array.length; i++) {
// 自底向下构造堆
heapBu(array, array.length - 1 - i);
int temp = array[0]; // 保存最大元素
array[0] = array[array.length - 1 - i]; // 将堆顶元素与最后一个元素对换
array[array.length - 1 - i] = temp;
}
System.out.print("排序后:");
for (int i = 0; i < array.length; i++)
System.out.print(array[i] + " ");
System.out.println();
}
}