启发于b站的一个视频,推荐大家去看看,讲的很清晰(av47196993 阿婆主:正月点灯笼)
完整的代码在本文末
一.堆是什么
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆
二.完全二叉树
如果二叉树的深度为k,则除第k层外其余所有层节点的度都为2,且叶子节点从左到右依次存在 。只有这两种情况
我们用数组来存储树,假设父节点的下标为i,那么父节点的左子节点下标为2*i+1,右子节点下标为2*i+2,如下表格
父节点下标 | i |
左子节点下标 | 2*i+1 |
右子节点下标 | 2*i+2 |
三.堆排序
总思路:先把乱序数组排成堆的结构,根据大根堆的性质,最大元素为根节点。循坏建堆+取根,建堆+取根这样的操作...这样不断取到的根必然是有序的,最终完成排序
建堆思路:一个完全二叉必然有类似下图所示结构,我们用红色框把一个完全二叉分成若干个部分
从最后一个叶子节点的父节点开始,开始对父节点与它的两个子节点进行大小的比较(右子节点不存在就只比较父节点和左子节点就可以了),把最大元素与父节点元素交换位置。图中的框就是每次的操作单元。我们把图中的结构,从下到上,从有到左依次每个框里面执行这样的比较操作,那么最后就会得到一个堆
我这里把整个算法分成三个方法
①建堆(会调用②)
//把待数组建成一个堆结构
public void buildHeap(int[] tree, int n) {
int lastnode = n - 1; //末节点为数组长度-1
int parent = (lastnode - 1) / 2; //根据完全二叉树性质得到父节点
for (int i = parent; i >= 0; i--) { //开始一直往前做heapify()
heapify(tree, n, i);
}
}
②局部把最大值放堆顶(就是上面说到的,每个红框里,把父节点和它的左右子节点中,比较得出他们之间的最大值,并交换到在父节点位置)
//heapify的作用就是把每个最小部分的最大值放到堆顶
public void heapify(int[] tree, int n, int nodeindex) {
if (nodeindex >= n) //方法出口
return;
int leftson = 2 * nodeindex + 1; //根据完全二叉性质得到左子节点
int rightson = 2 * nodeindex + 2; //根据完全二叉性质得到右子节点
int maxindex = nodeindex; //把开始节点下标设置为最大元素值下标
//从父节点和它的两个自节点中找到最大的,并把最大的下标给max下标
if ((leftson < n) && (tree[leftson] > tree[maxindex]))
maxindex = leftson;
if ((rightson < n) && (tree[rightson] > tree[maxindex]))
maxindex = rightson;
if (maxindex != nodeindex) {
swap(tree, maxindex, nodeindex);
heapify(tree, n, maxindex);
}
}
③排序(会调用①,②)
public void heapSort(int[] tree, int n) {
buildHeap(tree, n);//先把堆好
//循环把最大的元素值(即堆顶元素)与数组末端的元素进行交换
//因为循环过程中持续i--,每经过一次循环就相当于把数组长度减一,
//也就达到了,把每次放到数组末端的最大值元素排除出后续操作的范围
for (int i = n - 1; i >= 0; i--) {
swap(tree, i, 0);
heapify(tree, i, 0);
}
}
四.完整代码(java实现)
package test;
public class HeapSort {
public static void main(String[] args) {
int[] tree = { 25, 14, 97, 32, 31, 30, 75, 100, 84, 81, 26, 34 };
for (int i : tree) {
System.out.print(i + " ");
}
int n = tree.length;
HeapSort heapSort = new HeapSort();
heapSort.heapSort(tree, n);
System.out.println("");
for (int i : tree) {
System.out.print(i + " ");
}
}
//把待数组建成一个堆结构
public void buildHeap(int[] tree, int n) {
int lastnode = n - 1; //末节点为数组长度-1
int parent = (lastnode - 1) / 2; //根据完全二叉树性质得到父节点
for (int i = parent; i >= 0; i--) { //开始一直往前做heapify()
heapify(tree, n, i);
}
}
//heapify的作用就是把每个最小部分的最大值放到堆顶
public void heapify(int[] tree, int n, int nodeindex) {
if (nodeindex >= n) //方法出口
return;
int leftson = 2 * nodeindex + 1; //根据完全二叉性质得到左子节点
int rightson = 2 * nodeindex + 2; //根据完全二叉性质得到右子节点
int maxindex = nodeindex; //把开始节点下标设置为最大元素值下标
//从父节点和它的两个自节点中找到最大的,并把最大的下标给max下标
if ((leftson < n) && (tree[leftson] > tree[maxindex]))
maxindex = leftson;
if ((rightson < n) && (tree[rightson] > tree[maxindex]))
maxindex = rightson;
if (maxindex != nodeindex) {
swap(tree, maxindex, nodeindex);
heapify(tree, n, maxindex);
}
}
public void heapSort(int[] tree, int n) {
buildHeap(tree, n);//先把堆好
//循环把最大的元素值(即堆顶元素)与数组末端的元素进行交换
//因为循环过程中持续i--,每经过一次循环就相当于把数组长度减一,
//也就达到了,把每次放到数组末端的最大值元素排除出后续操作的范围
for (int i = n - 1; i >= 0; i--) {
swap(tree, i, 0);
heapify(tree, i, 0);
}
}
// 交换
public void swap(int[] tree, int i, int j) {
int tmp = tree[i];
tree[i] = tree[j];
tree[j] = tmp;
}
}
执行结果图