堆排序
堆排序运用到了树的思想。它仍然使用数组排序,并不创建树,但是排序时将数组看作一个完全二叉树,通过不断地构建大顶堆,将树顶元素与树尾元素交换,以树顶构建新的大顶堆来把整个数组从小到大排序。
首先,将数字按从上到下,从左到右的顺序写成一颗完全二叉树,这样的树的特点是,对于父节点i(对应数组中的下标i),它的左子节点的数组下标为2*i+1,右子节点的数组下标为2*i+2。而最下面的非叶子节点的数组下标为arr.length/2-1
进行堆排序大致分为两步,第一步是构建大顶堆,第二步是树顶元素与末尾元素交换。
首先,大顶堆是指:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆。小顶堆是指:每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。对数组升序排列要构建大顶堆,而降序排列要构建小顶堆。
思路
构建大顶堆:
这个方法用来将以一个非叶子节点开始的子树构建成大顶堆,需要三个变量,数组arr,该非叶子节点的数组下标i,和要改变的最大元素数(一开始就是数组的长度,每次排好了一个元素时就不用再去改变它,所以排列时每次会减1)
首先用temp保存这个非叶子节点的值。这个值中途是不会改变的,实际上,我们就是需要把它放在合适的位置上。
然后用for循环来遍历它的子树,j用来指向子节点(j表示的数组的下标),根据上面的性质j=2*i+1,当j小于length时,就可以不断循环(一开始代表的是传进来的这个非叶子节点的左子节点,之后会不断指向子节点的子节点),这时,在两个子节点中寻找较大的,并让j指向大的那个数。j+1就代表右子节点,它同样要小于length,防止出现越界,所以当右子节点较大时,让j+1就行。完成后,j就指向了子节点中大的那个,这时判断temp和arr[j]的大小,如果temp小,就把arr[j]赋给arr[i],并让i=j,如果temp大,说明符合大顶堆的逻辑,直接break退出循环就好。
注意的是:temp的值是不变的,等于一开始的非叶子节点值,每次是用temp和arr[j]比较,而不是arr[i]和arr[j]比较,因为arr[i]的值是会变化的(一开始指向传进来的非叶子节点,如果不是大顶堆,就会指向该节点较大的子节点,然后再指向子节点的子节点,不断改变)而我们实际上是在为temp找一个合适的位置,如果是arr[i]和arr[j]比较,就会出现temp小于arr[i]的情况,使得无法正确构建大顶堆,在这里的赋值是不断将较大的子节点赋给父节点。i=j这是让i指向较大的子节点,也就是下一次循环中的父节点,也就是i永远指向父节点,j永远指向较大的子节点。至于敢于直接break,是因为我们一定是从最小的非叶子节点开始构建大顶堆,从右向左,从下到上,所以可以直接break,因为下面的构建总是正确的。
退出循环后,让arr[i]等于temp,因为我们之前只是将较大的子节点赋给父节点,而子节点的值没有改变,实际上,它们应该交换。此时i指向的就是temp合适的位置,也就是某个子节点或者就没变,这时就直接赋值,完成交换。而这一步一定要放在循环外。因为循环内的i不一定是temp的合适位置。
代码
/**
* 构建大顶堆
* @param arr 原数组
* @param i 构建大顶堆的最顶的非叶子节点的下标
* @param length 要调整的最大元素数,每次不断减少
*/
public static void adjustHeap(int[] arr,int i,int length){
//保存非叶子节点值
int temp=arr[i];
//找到子节点的最大值
for (int j = (2*i+1); j <length ; j=(2*j+1)) {
//左子节点小于右子节点,就让j指向右子节点
if((j+1)<length && arr[j]<arr[j+1]){
j++;
}
if(temp<arr[j]) {
arr[i] = arr[j];
i=j;
}else {
break;
}
}
arr[i]=temp;
}
排序
排序方法的思路:首先调用上面的方法将原数组构建成大顶堆。这时因为上面的逻辑,要保证一定要从最后一个非叶子节点开始,逐渐向上。所以先求出下标,然后- -。完成后,显然根节点就是数组中最大的数了,让它与数组末尾数交换,那么最大的数就放在最后了,这时新的根节点不一定是数组中最大的,所以就以它为头,重新构建大顶堆(因为实际上只改变了两个数,而移下去的最大数不参与排列新大顶堆,也就是下面的大顶堆逻辑并没有被破坏,所以不用循环,而直接排就可以),然后重复这个过程,直到排列完毕。
public static void heapSort(int[] arr){
int temp=0;
//构建大顶堆
for (int i = arr.length/2-1; i >=0 ; i--) {
adjustHeap(arr,i,arr.length);
}
//交换
for (int i = arr.length-1; i >0 ; i--) {
temp=arr[i];
arr[i]=arr[0];
arr[0]=temp;
adjustHeap(arr,0,i);
}
}