堆排序
堆排序基本介绍:
1.堆排序是利用堆这种数据结构而设计的排序算法,堆排序是一种选择排序,它的最好最坏平均时间复杂度都是O(nlogn),是不稳定排序
2.堆排序过程中,将向量中存储的数据看成一棵完全二叉树,利用完全二叉树中双亲节点和孩子节点的内在关系来选择关键值最小的记录,即待排序记录采用的是数组存储方式并非以树的存储结构存储,而仅仅是采用完全二叉树的顺序结构
3.堆是具有以下性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为大根堆,注意:不要求节点的左孩子和右孩子值的关系,
4.每个节点的值都小于或者等于其左右孩子的值的堆称为小根堆
大根堆举例:
大根堆特点:arr[i]>=arr[2i+1]&&arr[i]>=arr[2i+2],i是对应的第几个节点,从0开始
小根堆举例:
小根堆特点:arr[i]<=arr[2i+1]&&arr[i]<=arr[2i+2]
5.堆排序一般升序采用大根堆,降序采用小根堆
6.堆排序思想:
1)将待排序序列构造成一个大根堆
2)此时序列中的最大值就是堆顶的根节点
3)将其与末尾进行交换,此时末尾就是最大值
4)然后将n-1个元素重新构造成一个堆,这样就会得到n个元素中的次小值,
如此反复执行就会得到一个有序的升序序列
在构建大根堆的过程中,用来构建大根堆的元素逐渐减少,最后得到的就是有序序列了。
堆排序的过程主要需要解决两个问题:
一.按堆的定义初建堆
二.去掉最大元后重建堆,得到次大元,以此类推
1.重建堆
问题:当堆顶记录改变时,如何重建堆?
算法思想:
首先将与堆相应的完全二叉树根节点中的记录移除,该记录称为待调整记录。此时根节点相当于空节点,从空节点的左右子树中选出一个关键字较大的记录,如果该记录的关键字大于待调整记录的关键字,则将该纪录上移至空记录中。
此时,原来关键字较大的子节点相当于空节点,从空节点的左右子树中选出一个关键字较大的记录,如果该记录的关键字仍然大于待调整记录的关键字,则将该纪录上移至空节点中。
重复上述移动过程,直到空节点的左右子树的关键字均小于待调整记录的关键字。此时,将待调整记录放入空节点即可
上述调整方法相当于把待调整记录逐步向下筛的过程,称其为筛选法。
2.建初堆
问题:如何由一个任意序列初建堆?
算法思想:
将一个任意序列看成是对应的完全二叉树,由于叶节点可以视为单元素的堆,所以可以利用上述的调整堆算法,自底向上逐层把所有子树调整为堆,直到将整个完全二叉树调整为堆。
上述完全二叉树中,最后一个非叶子节点位于第[n/2]个节点,n为二叉树的节点数,因此筛选时要从第[n/2]个节点开始,逐层向上倒退,直到根节点。
package Sort;
import java.util.Arrays;
//堆排序
/*堆排序的过程主要需要解决两个问题:
一.按堆的定义初建堆
二.去掉最大元后重建堆,得到次大元,以此类推
1.重建堆
问题:当堆顶记录改变时,如何重建堆?
算法思想:
首先将与堆相应的完全二叉树根节点中的记录移除,该记录称为待调整记录。
此时根节点相当于空节点,从空节点的左右子树中选出一个关键字较大的记录,
如果该记录的关键字大于待调整记录的关键字,则将该纪录上移至空记录中。
此时,原来关键字较大的子节点相当于空节点,从空节点的左右子树中选出一个关键字较大的记录,
如果该记录的关键字仍然大于待调整记录的关键字,则将该纪录上移至空节点中。
重复上述移动过程,直到空节点的左右子树的关键字均小于待调整记录的关键字。
此时,将待调整记录放入空节点即可
上述调整方法相当于把待调整记录逐步向下筛的过程,称其为筛选法。
2.建初堆
问题:如何由一个任意序列初建堆?
算法思想:
将一个任意序列看成是对应的完全二叉树,由于叶节点可以视为单元素的堆,所以可以利用上述的调整堆算法,
自底向上逐层把所有子树调整为堆,直到将整个完全二叉树调整为堆。
上述完全二叉树中,最后一个非叶子节点位于第[n/2]个节点,n为二叉树的节点数,
因此筛选时要从第[n/2]个节点开始,逐层向上倒退,直到根节点。*/
public class HeapSort2 {
public static void main(String[] args) {
int[] arr={4,6,8,5,9,1,2,-1};
heapSort(arr);
}
//堆排序的方法
public static void heapSort(int[] arr){
int temp=0;//交换时使用
//将无序序列构建成堆,根据需求建相应的堆,这里为大根堆,升序排序
for (int i =arr.length/2-1;i>=0; i--) {
//i值在合理范围内每变化一次就调用一次建堆的方法
//i就是非叶子节点的索引,从下到上将所有子树调整成需要的堆
adjustHeap(arr,i,arr.length);
}
/*
* 1.每次建堆完成后最大值就会调整到堆顶,我们需要将最大元素调整到数组末尾,就需要与末尾元素进行交换
* 2.交换后,我们在一次调整的序列就不包括数组最后一个元素了,因为最大值已经到了最后,所以j--
* 3.重新调整结构,满足对应堆的定义,然后继续交换,反复执行调整+交换
* */
for (int j = arr.length-1; j>=0 ; j--) {
//交换
temp=arr[j];
arr[j]=arr[0];
arr[0]=temp;
//交换完成之后再将新的数组进行重建堆,但不包括最后一个元素
adjustHeap(arr,0,j);
}
System.out.println("堆排序后的数组:"+Arrays.toString(arr));
}
//将数组调整为一个大根堆
/*
* 此方法功能在于完成将以i为非叶子节点的树调整为大根堆(局部调整:只会调整i节点为父节点的树)
* 从第一个非叶子节点开始调整,i的计算公式:i=arr.length/2-1
* 比如:{4,6,8,5,9},第一次i=1,就是节点6,以节点6为父节点进行调整==》{4,9,8,5,6}
* 方法参数说明:
* arr:待调整的数组
* i:当前非叶子节点在待调整数组中的索引
* length:待调整数组的长度
* */
public static void adjustHeap(int[] arr,int i,int length){
//调整思路:首先我们知道要从第一个非叶子节点开始调整
//所以要记录待调整节点的值
//i为待调整节点,所以定义一个临时变量来存储待调整节点的值
int temp=arr[i];
//记录好待调整节点的值之后,就需要开始从待调整节点值的左右子树中找出值较大的那个节点
//待调整节点的左孩子为2*i+1,右孩子为2*i+2
for (int k =2*i+1; k <length ; k++) {//如果k>=length了说明已经到了待排数组的最后一个元素,放置越界
//判断左右孩子谁的值大
if (k+1<length&&arr[k]<arr[k+1]){//k+1的作用就是来判断是否存在右孩子
//若果存在右孩子并且右孩子值还大于左孩子
//那么我们要做的事就是来比较右孩子和待调整节点的值谁的大
k++;//k指向右节点
}
//如果左不小于右,k也无需变化
//接下来判断孩子与父亲值的大小
if (arr[k]>temp){//之前定义了临时变量来保存待调整节点的值
arr[i]=arr[k];
i=k;//将待调整节点改为索引为k的节点
}else {
break;//如果左右孩子节点的值都不大于待调整节点的值那么就退出
}
}
//for循环结束后说明,以i为父节点的位置上已经放了以i为父节点的子树里的最大值
//如果for循环中发生了调整说明i的值发生了变化,变化为了调整后的位置将temp放在调整后的位置
// temp存放的就是进入for循环后以i为父节点的值,
arr[i]=temp;
}
}