堆排序
许多应用程序都需要处理有序的元素,但不一定要求他们全部有序,或者不一定要一次就将他们排序,很多时候,我们每次只需要操作数据的最大元素(最小元素),那么有一种基于二叉堆的数据结构可以提供支持。
所谓二叉堆,是一个完全二叉树的结构,同时满足堆的性质:即子结点的键值或索引总是小于(或大于)它的父节点。在一个二叉堆中,根节点总是最大(或者最小)节点,这样堆我们称之为最大(小)堆。
堆排序算法就是抓住这一特点,每次都获取堆顶的元素,然后将剩余的元素重新调整为最大(最小)堆,依次类推,最终得到排序的序列。
完全二叉树的相关知识:
推论1:对于位置为K的结点 左子节点=2K+1,右子节点=2(K+1)
验证:C:2 左子节点:2×2+1=5 右子节点: 2×(2+1)=6
推论2:最后一个非叶子节点的位置为(N/2)-1,N为数组长度
验证:数组长度为6,(6/2)-1=2
步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
例如有这样一个数组:1 2 5 4 3 7
按照完全二叉树的定义,元素1的下标为0为根节点,元素2的下标为1为根节点的左节点,元素5的下标为2为根节点的右节点,以此类推构成一个完全二叉树,如图所示:
但这样只是一个完全二叉树还不是一个堆,我们需要找出最后一个非叶子节点,按照公式:最后一个非叶子节点的位置为(N/2)-1
也就是(6/2)-1=2,也就是下标为2的元素也就是5。然后将元素5与他的叶子节点进行比较和交换。5比他的叶子节点7要小,需要进行调换,变成1 2 7 4 3 5。
然后这样还不是一个最大堆,因为我们之前算出最后一个非叶子节点的下标为2,那么下标2之前到0的位置都不是叶子节点,于是我们倒着将这个完全二叉树变成满足最大堆的完全二叉树。接着从下标1的元素2开始,元素2与其左节点和右节点进行比较,元素2的左节点4和右节点3都比元素2大,左节点4大于右节点3,所以将元素2与元素4进行调换,变成1 4 7 2 3 5
接着还有下标为0的元素1,他的左节点4和右节点7都比他大,右节点7比左节点4大,所以元素1和元素7进行调换,变成7 4 1 2 3 5
当这样还不算是一个最大堆,下标为2的元素1比下标为5的元素5要小,需要进行调换(所以这个地方需要递归最大值所对应的位置(也就是之前7的位置也就是2)进行调整,避免最大位置对应的元素左子树或者右子树的元素不是构成最大堆二叉树),变成7 4 5 2 3 1
这样就构成了一个最大堆
接下来就可以进行堆排序了,我们将堆顶元素和尾元素进行交换,变成1 4 5 2 3 7
这个时候我们就将元素7从二叉树中进行脱离,不再是我们的二叉树一部分,剩下的元素为1 4 5 2 3,此时构成的二叉树又不是一个最大堆,我们需要重新进行调整,以此类推,不断得到最大堆,并将其堆顶元素和末尾元素进行调换,并将堆顶元素进行脱离,剩下部分继续进行调整。
通过这种不断的调整,大的元素在后面,从而实现升序
代码如下:
private void buildMaxHeap(int[] array) {
//从最后一个非叶子节点开始向上构造最大堆
for(int i=(len/2-1);i>=0;i--){
adjustHeap(array,i);
}
System.out.println("构建完成最大堆");
}
从小到大(升序)
//调整使之成为最大堆
private void adjustHeap(int[] array, int i) {
int maxIndex=i;
int left=2*i+1;
int right=2*(i+1);
//如果有左子树,且左子树大于父节点,则将最大指针指向左子树
if(left<len&&array[left]>array[maxIndex]){
maxIndex=left;
}
//如果有右子树,且右子树大于父节点且大于左子树,则将最大指针指向右子树
if(right<len&&array[right]>array[maxIndex]&&array[right]>array[left]){
maxIndex=right;
}
//如果父节点不是最大值,则将父节点与最大值进行交换,并且递归调整与父节点交换的位置(因为交换后,可能会找出最大值原所在位置不是最大堆,所以要进行递归调整)
if(maxIndex!=i){
swap(array,maxIndex,i);
adjustHeap(array,maxIndex);
}
}
构建最大堆的动态图
步骤二:循环将堆首位(最大值)与未排序数据末位交换,然后重新调整为最大堆,将堆顶元素脱离二叉树,从剩下的元素再次进行堆排,重复调整,直到二叉树的元素全部脱离,这样就完成堆排序。
while (len>0){
//交换堆顶元素和末尾元素
swap(nums,0,len-1);
//将堆顶元素脱离二叉树
len--;
//从堆顶元素开始再次进行调整最大堆
adjustHeap(nums,0);
}
整体代码如下:
package SortTest;
import java.util.Arrays;
public class HeapSort2 {
int len=0;
//交换数组内两个元素
public static void swap(int[] array,int i,int j){
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
public int[] sortArray(int[] nums){
len=nums.length;
//长度小于1,代表没有直接返回
if(len<1){
return nums;
}
//构建一个最大堆
buildMaxHeap(nums);
//循环将堆首位(最大值)与未排序数据末位交换,重新调整为最大堆
while (len>0){
swap(nums,0,len-1);
len--;
adjustHeap(nums,0);
}
return nums;
}
private void buildMaxHeap(int[] array) {
//从最后一个非叶子节点开始向上构造最大堆
for(int i=(len/2-1);i>=0;i--){
adjustHeap(array,i);
}
}
//调整使之成为最大堆
private void adjustHeap(int[] array, int i) {
//记录最大值的下标
int maxIndex=i;
//记录左子树
int left=2*i+1;
int right=2*(i+1);
//如果有左子树,且左子树大于父节点,则将最大指针指向左子树
if(left<len&&array[left]>array[maxIndex]){
maxIndex=left;
}
//如果有右子树,且右子树大于父节点且大于左子树,则将最大指针指向右子树(右子树的值大于左子树)
if(right<len&&array[right]>array[maxIndex]&&array[right]>array[left]){
maxIndex=right;
}
//如果父节点不是最大值,则将父节点与最大值进行交换,并且递归调整与父节点交换的位置(避免出现交换后,最大位置对应的元素左子树或者右子树的元素不是构成最大堆二叉树)
if(maxIndex!=i){
swap(array,maxIndex,i);
adjustHeap(array,maxIndex);
}
}
public static void main(String[] args) {
int [] nums=new int[]{1,2,5,4,3,7};
int[] array = new HeapSort2().sortArray(nums);
System.out.println(Arrays.toString(array));
}
}
要实现降序也很简单,只要将其构建成最小堆即可,代码如下:
从大到小(降序)
//调整使之成为最小堆
private void adjustHeap(int[] array, int i) {
int maxIndex=i;
int left=2*i+1;
int right=2*(i+1);
//如果有左子树,且左子树大于父节点,则将最大指针指向左子树
if(left<len&&array[left]<array[maxIndex]){
maxIndex=left;
}
//如果有右子树,且右子树大于父节点且大于左子树,则将最大指针指向右子树
if(right<len&&array[right]<array[maxIndex]&&array[right]<array[left]){
maxIndex=right;
}
//如果父节点不是最大值,则将父节点与最大值进行交换,并且递归调整与父节点交换的位置
if(maxIndex!=i){
swap(array,maxIndex,i);
adjustHeap(array,maxIndex);
}
}