堆排序
先放无注解的代码
public static void heapSort(int[] arr) {
for (int i = arr.length/2 -1 ; i >= 0 ; i--){
adjust(arr,i,arr.length);
}
int temp;
//对大顶堆进行递归
for (int j = arr.length -1; j > 0; j--) {
temp = arr[0];
arr[0] = arr[j];
arr[j] = temp;
adjust(arr,0,j);
}
}
private static void adjust(int[] arr, int i, int length) {
int temp = arr[i];
//先获取子节点
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
if (k + 1 < length && arr[k] < arr[k + 1]) {
k++;
}
if (arr[k] > temp) {
//如果子节点比当前节点大,则替换
arr[i] = arr[k];
i = k;
}else {
break;
}
}
arr[i] = temp;
}
首先理解什么是堆:堆是一个完全二叉树,完全二叉树:除了最后一层,其他层都是满的,最后一层从左到右依次有
叶子节点:没有子节点的节点
满二叉树每一层的结点个数都达到了最大值, 即满二叉树的第i层上有个2的i-1次方个结点 (i≥1) ,满二叉树就一共有2的i次方-1个 (注意区别)
好比有10层,那就是有2^10-1个节点1023,第10层有2的9次方个节点 512个
假设第10层没满,树一共有800个节点
800/2-1=399,到511之间 都是第9层
至于从第一层到第8层的每一层的节点个数是固定的,2的k-1次方
第八层有256个
求一个完全二叉树的叶子节点的个数,已知总节点个数为n,公式【(n+1)/2】 取整数
arr.length/2 -1 (这个地方是直接除以2)
这样的话是每次取出来一个最大的数
堆排序的构想:大顶堆,小顶堆
大顶堆,叶子节点,与非叶子节点的关系:
首先:
arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆,arr[i] <= arr[2i+2] && arr[i] <= arr[2i+2]
这个地方理解一下为什么是这样的关系
public void heapSort(int[] arr) {
//第一步,获取到大顶堆
//按照倒叙,将每个非叶子节点排一次序,到最后的时候,整个树就是大顶堆了
//为什么arr.length/2-1是最后一个非叶子节点的下标呢?
//因为arr.length /2 为非叶子节点的个数(注意,这个地方是Java的int类型的除,是不留小数点后面的数字的!
//那拿到了个数 -1 ,就是下标啦
//至于为什么arr.length/2就是非叶子节点的个数呢?在其他地方去验证吧!
for (int i = arr.length /2 -1; i >= 0; i --) {
adjustHeapNode(arr,i,arr.length);
}
//此时获取到了一个大顶堆,然后,获取到大顶堆以后呢,将最大值丢到最后面
//然后呢,将最后一个值放到最前面,去比较
int temp ;
for (int i = length-1; i > 0; i--) {
//这一步是将arr[0]和未排序的数组中最后一个值做对换
temp = arr[i]
arr[length] = arr[0];
arr[0] = temp
adjustHeapNode(arr,0,i); //这个地方记得区分下标与长度的区别
}
}
//这个方法不应该叫构建大顶堆
//应该叫做获取数组中最大的值
public void adjustHeapNode(int[] arr,int i ,int length) {
int tmep = arr[i];
//傻瓜注解
//k = i * 2 +1 这一步的操作是为了获取该节点的左子节点
//k = k * 2 + 1,这个操作是当结束循环时,将指针指向K节点的左字节点
if (int k = i *2 +1 ; k < length ; k = k * 2 + 1) {
if (k + 1 < length && arr[k] < arr[k+1]) {
k = k + 1; //指针层面的转换
}
//此时,k 为 左右子节点较大的那个指针
if (arr[k] > temp) {
//当arr[k] > temp时,那么我们是不确定,该节点下的子树中是否有比temp更大的
//此时先将k的值给它的父节点,,arr[i] = temp
arr[i] = arr[k];
//然后呢,i = k 这个操作是指针指向了k节点(k是一个父节点)
i = k;
} else {
//当arr[k] < temp时,此时需要先理清楚一个概念
//我们是对大顶堆进行排序的,大顶堆的特点是节点比它的左右子节点都大
//那如果说arr[k] < temp的话, 那k的子树(无论下面有多少层)的所有子节点都比它小,那就可以break了,没必要比下去了
//而当数组不是大顶堆时,我们是倒叙着去排每一个非叶子节点的
//简单理解一下,即便是构建大顶堆的时候,也不会出现父节点比叶子节点小
break;
}
}
//顺着上面的if来说的话,当我把i = k 后,i就是子节点了
// 我先把if判断去掉,代码是这个样子的 假设i = 1 k = 2 arr[i] = 4 arr[k] = 10
//int temp = arr[i] temp = 4
//arr[i] = arr[k] arr[1] = 10
// i = k i = 2
//arr[i] = temp arr[2] = 4
//对比一下结果,开始的时候是 arr[1] = 4,arr[2] = 10,现在是ar[1] = 10,arr[2] = 4,同时呢,我们是要继续往下对比的
//i 就变大了呀
//简化逻辑,就是找到一个比他大的数,然后替换就完事了
//当i找到了子节点比它大的数的时候
arr[i] = temp;
}