堆排序就是利用堆的特性来进行排序的一种算法,在学习堆排序之前需要先了解什么是堆,以及什么是大根堆、小根堆。
堆:堆就是一颗完全二叉树。
大根堆:堆中的每一个节点都大于等于它的孩子节点。
小根堆:堆中的每一个节点都小于等于它的孩子节点。
在了解了堆之后,我们还需要知道堆在数组中是如何存放的,如图:
由此可以知道,对于堆中的任意一个节点 i:
i 的左孩子:2*i + 1
i 的右孩子:2*i + 2
i 的父节点:(i - 1) / 2
堆排序的实现可以分为对两个问题的解决:
1. 对于任意的一个数组,如何将数组调整成为一个大根堆?
实现思路:遍历数组,将每一个元素与自己的父节点比较,如果大于父节点,则与父结点交换位置,然后继续与交换之后新的父节点比较,如果依然大于新的父节点,就继续交换比较,直到不再大于自己的父节点或成为根节点。
Java代码:
public class heapInsert {
public static void heapinsert(int[] arr){
for(int i = 0; i < arr.length; i++){
int heap = i;
//当前节点大于自己的父节点时,与父结点交换位置
while(arr[heap] > arr[(heap - 1) / 2]){
swap(arr, heap, ((heap - 1) / 2));
heap = (heap - 1) /2;
}
}
}
//交换两个元素在数组中的位置
public static void swap(int[] arr, int a, int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public static void main(String[] args){
int[] arr = {4,2,7,9,1,6,4,8,2,4,5,1};
heapinsert(arr);
System.out.println(Arrays.toString(arr));
}
}
2. 对于一个大根堆,将最大值放到最后,再把除最大值以外剩下的元素重新调整为大根堆
实现思路:对于一个大根堆,根位置的值即为最大值,首先将根位置的元素与最后一位的元素交换,然后将新的根位置的元素与自己的最大孩子节点比较,如果小于自己的最大孩子,就与之交换,并继续和新的最大孩子比较,直到大于自己的最大孩子。
Java代码:
public class heapIfy {
public static void heapify(int[] arr){
int heapSize = arr.length - 1;
//将最后一位放到原来的根的位置
swap(arr, heapSize, 0);
//标记此时在根位置上的元素
int n = 0;
//如果n还有孩子节点
while (n * 2 + 1 < heapSize) {
//如果存在右孩子并且右孩子大于左孩子,max = 右孩子的下标,否则等于左孩子的下标
int max = n * 2 + 2 < heapSize && arr[n * 2 + 2] > arr[n * 2 + 1] ? n * 2 + 2 : n * 2 + 1;
//如果n位置的值大于等于max位置的值,那么max更新为n
max = arr[n] >= arr[max] ? n : max;
//如果最终的max就等于n,说明n已经是比自己的两个孩子都大,此时已经是大根堆,不用继续比较
if (max == n) {
break;
}
//将左右孩子中最大的与父节点交换,交换之后更新n,继续比较
swap(arr, n, max);
n = max;
}
}
//交换数组中的两个数
public static void swap(int[] arr, int a, int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
//主方法
public static void main(String[] args){
int[] arr = {9, 8, 6, 7, 5, 4, 4, 2, 2, 1, 4, 1};
heapify(arr);
System.out.println(Arrays.toString(arr));
}
}
综合以上两个问题,完整代码如下:
public class heapSort {
public static void heapInsert(int[] arr){
for(int i = 0; i < arr.length; i++){
int heap = i;
//当前节点大于自己的父节点时,与父结点交换位置
while(arr[heap] > arr[(heap - 1) / 2]){
swap(arr, heap, ((heap - 1) / 2));
heap = (heap - 1) /2;
}
}
}
public static void heapIfy(int[] arr) {
for (int heapSize = arr.length - 1; heapSize > 1; heapSize--) {
//将最后一位与原来的根交换位置
swap(arr, heapSize, 0);
//标记此时在根位置上的元素
int n = 0;
//如果n还有孩子节点
while (n * 2 + 1 < heapSize) {
//如果存在右孩子并且右孩子大于左孩子,max = 右孩子的下标,否则等于左孩子的下标
int max = n * 2 + 2 < heapSize && arr[n * 2 + 2] > arr[n * 2 + 1] ? n * 2 + 2 : n * 2 + 1;
//如果n位置的值大于等于max位置的值,那么max更新为n
max = arr[n] >= arr[max] ? n : max;
//如果最终的max就等于n,说明n已经是比自己的两个孩子都大,此时已经是大根堆,不用继续比较
if (max == n) {
break;
}
//将左右孩子中最大的与父节点交换,交换之后更新n,继续比较
swap(arr, n, max);
n = max;
}
}
}
//交换数组中的两个数
public static void swap(int[] arr, int a, int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public static void main(String[] args){
int[] arr = {4,2,7,9,1,6,4,8,2,4,5,1};
heapInsert(arr);
heapIfy(arr);
System.out.println(Arrays.toString(arr));
}
}
以上内容为个人学习总结,如有错误,欢迎指正。