今天我们说堆排序
基础常识 在一个二叉树里面 一个节点下标是i, 他的左儿子节点是 i * 2 + 1, 右儿子是 i * 2 + 2,他的父亲节点是 (i - 1)/2,所谓堆,就是任何一颗数, 他的父亲节点都比任何一个儿子节点都大, 递归子树也是
我们看下面一个数组 [44, 15, 60, 31, 41, 17, 32, 46]
画出二叉树就是
我们用两种方式进行堆排序
1. 我们从零开始构造一颗最大堆, 就是将原数组中的数,循环遍历构造最大堆, 最大堆我们用一个跟原数组大小 相同的数组存放
(1.) 44 因为没有子节点,所以他自己都是一个最大堆
(2)15, 因为他比他父亲节点44小,所以接入44左儿子,构成最大堆
(3)加入60, 如果要接入最大堆, 就必须保证是最大堆, 因为他比父亲节点44要大, 所以我们交换这两个节点位置。。。
到这里大顶堆就构建完成了,问题是怎么用大顶堆进行排序呢
注意观察,我们可以发现,大顶堆的第一个元素都是最大值, 所以我们循环遍历第一个元素的值, 每取出一个值, 就要重新构建大顶堆,然后再取
当我们取出第一个元素时,将大顶堆的最后一个元素跟第一个元素交换,并去掉最后一个值(已经选出来了), 然后再针对新的二叉树, 进行构造大顶堆,怎么构建呢?、
我们设置一个指针指向第一个元素, 将这个元素跟他的左儿子和右儿子进行比较, 谁大就和谁进行交换,直到他比任何一个儿子都大,或者没有儿子结束
下面看代码
import java.util.Arrays;
import java.util.Random;
public class HeapSort {
static int[] temp;
public static void main(String[] args) {
int[] arr = new int[8];
temp = new int[arr.length];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(70);
}
System.out.println(Arrays.toString(arr));
for(int i = 0; i < arr.length; i++){
buildHead(arr[i], i);
}
System.out.println(Arrays.toString(temp));
for(int i = 0; i < temp.length; i++){
int a = temp[0];
delete(i);
temp[temp.length - i-1] = a;
}
System.out.println(Arrays.toString(temp));
}
// 删除大顶堆的头结点 (i标明的是这是第几次删除,从0开始)
private static void delete(int i) {
// 将头结点跟最后一个节点交换
temp[0] = temp[temp.length - 1 - i];
// j的含义非常重要, 从头结点一直向下构建大顶堆
int j = 0;
// 当我们j指向的节点左节点都没有儿子了, 右节点肯定也没有儿子, 所以大顶堆就构建完成了
while((j * 2 +1) < temp.length - 1 - i ){
int left = j * 2 + 1; // 左儿子下标
int right = j * 2 + 2;// 右儿子下标
int max = j; // 默认在三个节点中自己最大
if(temp[left] > temp[max]){ // 左儿子大
max = left;
}
if(right < temp.length - 1 - i ){ // 当右儿子存在时
if(temp[right] > temp[max]){
max = right;
}
}
if(max == j){ // 如果当前j指向的已经是大顶堆了 不用继续了
break;
}else{
// 否则就交换
int temp_num = temp[j];
temp[j] = temp[max];
temp[max] = temp_num;
}
j = max;
}
}
// 根据数组元素和当前元素的下标构建二叉树
public static void buildHead(int i, int index) {
// 先将原数组元素 加入到二叉树的最后
temp[index] = i;
// 设置指针指向开始节点 (理解这个指针非常重要,
// 该指针指向的节点结束后一定要是一个堆, 所以该指针最后必定指向0,也到0结束)
int j = index;
while (j != 0) {
//父亲节点
int par = (j - 1) / 2;
// 如果你比父亲节点还要小,那就不同比了,因为我们加入的时候这个二叉树已经是大顶堆了
if (temp[par] >= temp[j]) {
break;
} else {
// 如果大,就交换数据
int curr = temp[j];
temp[j] = temp[par];
temp[par] = curr;
// j向上走一步
j = par;
}
}
}
}
第二种构建大顶堆, 我们就直接在原数组中构建大顶堆
package Sort;
import java.util.Arrays;
import java.util.Random;
public class HeapSort {
static int[] temp;
public static void main(String[] args) {
int[] arr = new int[8];
temp = new int[arr.length];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(70);
}
System.out.println(Arrays.toString(arr));
//从最后一个非叶子节点开始构造, 一步一步往上构造
for (int i = arr.length / 2 - 1; i >= 0; i--) {
injustBigHeap(arr, i, arr.length);
}
System.out.println(arr[0]);
for (int j = 1; j < arr.length; j++) {
// 每一次构建大顶堆好之后就将堆顶元素 放在末尾
int temp = arr[0];
arr[0] = arr[arr.length - j];
arr[arr.length - j] = temp;
// 这个时候我们不需要从新开始构建大顶堆 应为 我们只需要将 下标为0 的数大顶堆构建好就行了
injustBigHeap(arr, 0, arr.length - j);
System.out.println(arr[0]);
}
}
// 此方法将arr中长度为length的数组 以i为的顶点的二叉树构建成大顶堆
public static void injustBigHeap(int[] arr, int i, int length) {
if (i < length) {
int left = i * 2 + 1; // 左子节点
int right = i * 2 + 2;// 右子节点
int max = i;// 这样定义为了使根节点域左右两个节点都比较
if (left < length && arr[left] > arr[max]) {
max = left;
}
if (right < length && arr[right] > arr[max]) {
max = right;
}
if (max == i) { // 如果没变 就不用比了
return;
}
// 如果变了
// 先交换
int temp = arr[i];
arr[i] = arr[max];
arr[max] = temp;
// 递归将儿子节点继续构造成大顶堆
injustBigHeap(arr, max, length);
}
}
}