堆排序的Java实现
由于这里是堆排序,因此还是说明下堆排序的原理:
一般堆是用数组来进行模拟的,arr[k]的子节点为arr[k * 2+1]和 arr[k * 2+2] .
堆排序就是解决以下两个问题:
1、如何将一个待排序数组(含有n个元素)建成堆?
2、输出堆顶元素之后,怎么样调整剩余的n-1个元素使其变为一个新堆然后进行这一步。
以最小堆为例
至于第一个问题:就是将二叉堆的非叶子节点(对应的数组的arr[(len/2)-1]~arr[0])都依次向后沉。”向后沉”的意思如果父节点的值大于两个子节点中的最小值就进行交换然后继续进行这样的判断。
至于第二个问题:堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆(这里的恢复堆就是将A[0]向后沉即可)。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。
注意使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。
由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)。
按照上面两步的思路实现的代码如下:
public static void heapSort(int[] arr, int len) {
//第一步先调整arr为最小堆
if(arr==null||len<1){
return ;
}
for(int i=(len/2)-1;i>=0;i--){//对于每个非叶子节点开始向下调整
siftDown(arr,i,len);
}
// System.out.println("数组调整之后为:");
// print(arr,len);
//第二步,开始排序
int begin = 0;
int end = len-1;
while(begin<end){
//先交换
swap(arr,begin,end);
//然后调数组arr从下标为0~end-1位置为一个最小堆。
siftDown(arr,0,end);//这里的end为剩余的长度
//调整之后进行下一轮
end--;
}
}
其中,二叉堆中”下沉” 某个元素的实现代码如下:
//函数功能:将arr[index]元素向后沉。
private static void siftDown(int[] arr, int index, int len) {
if(arr==null||len<1||index<0||index>len){
return;
}
int half = len/2;
int val = arr[index];
while(index<half){
int leftChild = (index<<1) + 1;
int rightChild = leftChild + 1;
int minIndex = leftChild;
if(rightChild<len&&arr[rightChild]<arr[leftChild]){
minIndex = rightChild;
}
//将arr[minIndex]与 val进行比较
if(arr[minIndex]>=val){
break;
}
//交换
arr[index] = arr[minIndex];
index = minIndex;
}
arr[index] = val;
}
上面还涉及到一个交换数组中两个位置的元素的方法如下:
//函数功能:完成数组位置begin/end的元素的交换
private static void swap(int[] arr, int begin, int end) {
if(arr==null||begin<0||begin>=arr.length||end<0||end>=arr.length){
return;
}
int temp = arr[begin];
arr[begin] = arr[end];
arr[end] = temp;
}
测试代码如下:
public static void main(String[] args) {
Random r = new Random(47);
for(int j=0;j<20;j++){
int len = r.nextInt(20);
int [] arr = new int[len];
for(int i=0;i<len;i++){
arr[i] = r.nextInt(2*len);
}
// int len = 10;
// int[] arr = {9,12,17,30,50,20,60,65,4,49};
heapSort(arr,len);
System.out.println("数组排序后的结果为:");
print(arr,len);
}
}
//函数功能:打印输出
private static void print(int[] arr,int len) {
if(arr==null||len<1){
return;
}
for(int i=0;i<len;i++){
System.out.print(arr[i]+" ");
}
}
以上就是关于堆排序的一个实现。还是比较简单哈。
使用最大堆的实现堆排序
上面是采用的是最小堆完成的堆排序,是逆序。下面是采用最大堆的形式完成的堆排序。
与最小堆相比,改动的地方就是“下沉”函数siftDown。在最小堆中,是父节点大于两个子节点的最大值就下层。而在最大堆中,是父节点小于两个子节点的最小值就下层。就这一点区别。
siftDown函数代码如下:
//函数功能:将arr[index]元素向后沉。
private static void siftDown(int[] arr, int index, int len) {
if(arr==null||len<1||index<0||index>len){
return;
}
int half = len/2;
int val = arr[index];
while(index<half){
int leftChild = (index<<1) + 1;
int rightChild = leftChild + 1;
//现在是以最大堆为例,则是小的往下沉
int maxIndex = leftChild;
if(rightChild<len&&arr[rightChild]>arr[leftChild]){
maxIndex = rightChild;
}
//将arr[maxIndex]与 val进行比较
if(arr[maxIndex]<=val){
break;
}
//交换
arr[index] = arr[maxIndex];
index = maxIndex;
}
arr[index] = val;
}
小结
自己以前一直没有研究过堆排序,原因在于感觉挺复杂的,这两天看了PriorityQueue和PriorityBlockingQueue这两个类的源码之后,发现,原来二叉堆的“下沉”和“上浮”还是挺有意思的,于是自己也就一下子就理解了。因此就有了这篇关于堆排序的实现。
关于堆和堆排序的介绍还可以参考这篇博文:http://blog.csdn.net/morewindows/article/details/6709644。