摘要 介绍堆排序的基本概念及其实现。
前言
排序大的分类可以分为两种:内排序和外排序。在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。这里讲的排序是内排序中的堆排序算法,它属于选择排序的一种。
堆排序和插入排序一样,是一种就地排序算法(不需要额外的存储空间)。堆是一种数据结构,它可以被视为一种完全二叉树。最小堆又叫小顶堆,满足小顶堆的条件是每个孩子节点的值都大于父节点。大顶堆则相反。
源码
import java.util.Arrays;
public class HeapSort {
//使用数组存储堆中的数据
private int[] data;
public static void main(String[] args) {
int[] a = {1, 50, 38, 78, 33, 12, 65, 97, 76, 13, 27, 32, 50, 63, 101};
int lastIndex = a.length - 1;
//循环建堆
for (int i = 0; i < lastIndex; i++) {
//建堆
buildMaxHeap(a, lastIndex - i);
//交换堆顶和最后一个元素
swap(a, 0, lastIndex - i);
System.out.println("第" + i + "次遍历,执行结果:" + Arrays.toString(a));
}
}
/**
* 对data数组从0到 lastIndex 建大顶堆
*/
public static void buildMaxHeap(int[] data, int lastIndex) {
//从lastIndex节点(最后一个节点)的父节点开始
for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
//k保存正在判断的节点
int k = i;
//如果当前k节点的子节点存在
while (k * 2 + 1 <= lastIndex) {
//k节点的左子节点的下标
int biggerIndex = left(k);
//如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
if (biggerIndex < lastIndex) {
//若右子节点的值较大
if (data[biggerIndex] < data[biggerIndex + 1]) {
//biggerIndex总是记录较大子节点的下标
biggerIndex++;
}
}
//如果k节点的值小于其较大的子节点的值
if (data[k] < data[biggerIndex]) {
//交换它们
swap(data, k, biggerIndex);
//将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值
k = biggerIndex;
} else {
break;
}
}
}
}
/**
* 根据父节点下标获取左孩子节点下标
*
* @param index 下标
* @return 2 * index + 1
*/
private static int left(int index) {
// 左移1位相当于乘2
return (index + 1) << 1 - 1;
}
//交换
private static void swap(int[] data, int i, int j) {
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
}
基于Top k的最小堆算法
/**
* 最小堆
*
*/
public class MinHeap {
//使用数组存储堆中的数据
private int[] data;
public MinHeap(int[] data) {
this.data = data;
bulidHeap();
}
/**
* 建立最小堆
*/
private void bulidHeap() {
for (int i = (data.length) / 2 - 1; i >= 0; i--) {//下标小于等于i的节点拥有子节点
change(i);
}
}
/**
* 根据父节点判断是否
* 与左右孩子交换
*
* @param i
*/
private void change(int i) {
int temp = 0;
int left = left(i);
int right = right(i);
//存在右节点则存在左节点
if (right < data.length) {
//拿到左右孩子中小的下标
temp = min(left, right);
if (data[i] > data[temp]) {
swap(i, temp);
//如果和子节点发生交换,则要对子节点的左右孩子进行调整
change(temp);
}
} else if (right < data.length) {
//不存在右节点但存在左节点,则左节点无孩子节点
if (data[i] > data[left])
swap(i, left);
//孩子节点大于父节点,直接交换位置
}
}
/**
* 获取两个节点中较小的节点的下标
*
* @param i
* @param j
* @return
*/
private int min(int i, int j) {
if (data[i] >= data[j])
return j;
return i;
}
/**
* 根据父节点下标获取
* 左孩子节点下表
*
* @param i
* @return
*/
private int left(int i) {
return ((i + 1) << 1) - 1;
}
/**
* 根据父节点下表获取
* 右孩子节点下标
*
* @param i
* @return
*/
private int right(int i) {
return (i + 1) << 1;//左移1位相当于乘2
}
/**
* 根据下标交换数组中的位置
*
* @param i
* @param j
*/
private void swap(int i, int j) {
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
/**
* 重置堆顶
*
* @param root
*/
public void newRoot(int root) {
data[0] = root;
change(0);
}
/**
* 获取堆顶
*
* @return
*/
public int getRoot() {
return data[0];
}
/**
* 获取堆数据
*/
public int[] getData() {
return data;
}
}
实战
小顶堆和大顶堆对于解决TOP-K问题拥有较高的效率,在N个数中找到K(K<N)个最大或最小的数可以分别使用小顶堆算法和大顶堆算法。下面我用上面的小顶堆算法找出一个数组中最大的7个数。
public class TopK {
public static void main(String[] args) {
int[] num = {1, 3, 6, 7, 332, 355, 11, 325, 63, 25, 75, 32, 393, 759};
int[] data = {1, 3, 6, 7, 332, 355, 11};//取前7个数据建立最小堆
MinHeap heap = new MinHeap(data);
for (int i = 7; i < num.length; i++) {
//循环与堆顶比较,大于于堆顶则重置堆顶
if (num[i] > heap.getRoot()) {
heap.newRoot(num[i]);
}
}
//循环输出前7个最大的数
for (int n : heap.getData()) {
System.out.println(n);
}
}
}
测试结果如下所示:
63
325
75
393
332
355
759
小结
对于Wiener以上的话题,大家又有什么自己的独特见解呢?欢迎在下方评论区留言!