0. 相关概念
二叉堆
: 用数组储存的完全二叉树
完全二叉树
: 二叉树,且除最后一层外,每一层都是填满了的,最后一层从第一个节点开始依次向右增加节点
最大堆
: 父节点值大于等于任意一个子节点的值
最小堆
: 父节点值小于等于任意一个子节点的值
堆化
: 使得任意子树都是堆
1. 结构
2. 遍历
-
根节点下标: 0
节点
i
的:
父节点下标: ( i - 1 ) / 2
左孩子下标: i * 2 + 1
右孩子下标: i * 2 + 2 -
根节点下标: 1 (
常用
)节点
i
的:
父节点下标: i / 2
左孩子下标: i * 2
右孩子下标: i * 2 + 1
采用第二种方法的4种遍历:
/**
* 前序遍历
*
* @param a 堆
* @param index 当前节点下标
*/
public static void preOrder(int[] a, int index) {
if (index >= a.length)
return;
System.out.print(a[index] + " ");
preOrder(a, index * 2);
preOrder(a, index * 2 + 1);
}
/**
* 中序遍历
*
* @param a
* @param index
*/
public static void inOrder(int[] a, int index) {
if (index >= a.length)
return;
inOrder(a, index * 2);
System.out.print(a[index] + " ");
inOrder(a, index * 2 + 1);
}
/**
* 后序遍历
*
* @param a
* @param index
*/
public static void postOrder(int[] a, int index) {
if (index >= a.length)
return;
postOrder(a, index * 2);
postOrder(a, index * 2 + 1);
System.out.print(a[index] + " ");
}
/**
* 层序遍历
*
* @param a
*/
public static void seqOrder(int[] a) {
for (int i = 1; i < a.length; ++i)
System.out.print(a[i] + " ");
}
public void testTriversal() {
int[] a = range(10);
System.out.println("前序遍历");
preOrder(a, 1);
System.out.println("\n中序遍历");
inOrder(a, 1);
System.out.println("\n后序遍历");
postOrder(a, 1);
}
3. 建堆
从后往前, 对数组的每一个元素做堆化操作
堆化
: (大顶堆)自顶向下将比当前元素大的换上来, (小顶堆相反)
下面有递归和循环两种方式
3.1. 最大堆 ( 大顶堆 )
/**
* 建堆 O(logn)
*
* 只有一个元素的堆是最大堆(最小堆)
*
* @param a 堆
*/
public static void makeMaxHeap(int[] a) {
// 从后向前调用
for (int i = a.length - 1 >> 1; i >= 1; i--)
maxHeapifyLoop(a, i, a.length - 1);// 使用循环
// maxHeapifyRecursion(a, i, a.length - 1);// 使用递归
}
/**
* 将a调整为最大堆 循环 O(logn)
*
* i的左右子树都满足大顶堆的性质
*
* @param a 堆
* @param index 起始下标(包括)
* @param length 结束位置(包括)
*/
private static void maxHeapifyLoop(int[] a, int index, int length) {
while (index <= length) {// 不断向下调整
int left = index * 2;
int right = left + 1;
int max = index;// 左右子节点中最大的,的下标
// 找出max
if (left <= length && greater(a[left], a[max]))// 左子树存在,且较大
max = left;// 更新
if (right <= length && greater(a[right], a[max]))
max = right;
if (max == index)// a[index]比左右子节点都大,后面就不用调整了
break;
swap(a, max, index);// 如果没有跳出就交换
index = max;// 将i指向max继续向下调整
}
}
/**
* 堆化 -> 大顶堆 (递归)
*
* @param a 堆
* @param index 需要堆化处理的数据的索引
* @param length 未排序的堆(数组)的长度
*/
public static void maxHeapifyRecursion(int[] a, int index, int length) {
int li = index << 1; // 左子节点索引
int ri = li + 1; // 右子节点索引
int iMax = li; // 子节点值最大索引,默认左子节点。
if (li > length)
return; // 左子节点索引超出计算范围,直接返回。
if (ri <= length && a[ri] > a[li]) // 先判断左右子节点,哪个较大。
iMax = ri;
if (a[iMax] > a[index]) {
swap(a, iMax, index); // 如果父节点被子节点调换,
maxHeapifyRecursion(a, iMax, length); // 则需要继续判断换下后的父节点是否符合堆的特性。
}
/* 两个工具函数 */
public static boolean greater(int a, int b) {
return a > b;
}
public static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
3.2. 最小堆 ( 小顶堆 )
如法炮制
/**
* 建立小顶堆
*
* @param a
*/
public static void minHeapify(int[] a) {
// 从后向前调用
for (int i = a.length - 1; i >= 0; i--) {
minHeapDown(a, i);
}
}
private static void minHeapDown(int[] a, int i) {
while (i < a.length) {// 不断向下调整
int left = i * 2;
int right = i * 2 + 1;
int min = i;// 左右子节点中最小的,的下标
// 找出min
if (left < a.length && less(a[left], a[min]))// 左子树存在,且较小
min = left;// 更新
if (right < a.length && less(a[right], a[min]))
min = right;
if (min == i)// a[i]比左右子节点都小,后面就不用调整了
break;
// 如果没有跳出就交换
int temp = a[i];
a[i] = a[min];
a[min] = temp;
i = min;// 将i指向min继续向下调整
}
}
测试代码
@Test
public void test_makeMaxHeap() {
int[] a = new int[11];
for (int i = 1; i <= 10; i++) {
a[i] = i - 1;
}
makeMaxHeap(a);
seqOrder(a);
}
@Test
public void test_minHeapify() {
int[] a = new int[11];
for (int i = 1; i <= 10; i++) {
a[i] = 10 - i;
}
minHeapify(a);
seqOrder(a);
}
这里有个问题😅
为啥要自顶向下建堆而不是自底向上呢?
因为自底向上的化能会在交换的时候把本来在在正确位置的元素交换到下一层
或者下写个代码看看
/**
* 不用这个函数建立最小堆
*
* 从上面换下来的元素可能比parent大
*
* @param a
* @param i
*/
private static void heapUp(int[] a, int i) {
while (i > 0) {
int parent = i - 1 >> 1;
if (a[i] < a[parent]) {
int temp = a[i];
a[i] = a[parent];
a[parent] = temp;
i = parent;
} else {
break;
}
}
}
/**
* 不用这个
*
* @param a
*/
public static void makeMinHeapByUp(int[] a) {
for (int i = a.length - 1; i >= 0; i--) {
heapUp(a, i);
}
for (int i = a.length - 1; i >= 0; i--) {
heapUp(a, i);
}
}
还有一个问题 🤣
最开始的时候这份代码就是用Java写的, 而且数组下标是从0开始的现在我把它改成了从1开始的,可能有些地方没有改到…😥
4. 堆排序
我们已经知道最大堆,堆排序就是先建堆,每次把堆的第一个元素取出来放到堆末尾
如果要从小到大排序的化就用大顶堆,每次取出最大的元素放到堆尾(不是数组末尾)
/**
* 堆排序的主要入口方法,共两步。
*/
public static void heapSort(int[] a) {
/*
* 第一步:将数组堆化
* beginIndex = 第一个非叶子节点。
* 从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
* 叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
*/
int len = a.length - 1;
int beginIndex = len >> 1;
for (int i = beginIndex; i >= 1; i--) {
maxHeapifyRecursion(a, i, len);
}
/*
* 第二步:对堆化数据排序
* 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。
* 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。
* 直至未排序的堆长度为 0。
*/
for (int i = len; i > 0; i--) {
swap(a, 1, i);
maxHeapifyRecursion(a, 1, i - 1);
}
}
public static void heapSortLoop(int[] a) {
int len = a.length - 1;
int start = len >> 1;
for (int i = start; i >= 1; i--)
maxHeapifyLoop(a, i, len);
for (int i = len; i >= 1; i--) {
swap(a, 1, i);
for (int j = start; j >= 1; j--)
maxHeapifyLoop(a, 1, i - 1);
}
}
测试代码(非常简陋)
@Test
public void test_heapSort() {
int[] a = new int[31];
for (int i = 1; i <= 30; ++i)
a[i] = (int) (Math.random() * 100);
int[] b = a.clone();
heapSort(a);
seqOrder(a);
System.out.println();
heapSortLoop(b);
seqOrder(b);
}
5. PriorityQueue
在Java里面有着堆这种性质的数据结构就是PriorityQueue了
/**
* Class PriorityQueue<E>
* boolean offer(E e) 添加元素<br/>
* E poll() 删除元素<br/>
* E peek() 返回队首元素<br/>
*
* DEFAULT_INITIAL_CAPACITY = 11;
*
*/
@Test
public void testPriorityQueue() {
// 小顶堆
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(452);
pq.offer(3);
pq.offer(2352);
pq.offer(4);
pq.offer(34);
pq.offer(2);
System.out.println(pq.peek());
while (!pq.isEmpty())
System.out.println(pq.poll());
// 大顶堆
// PriorityQueue<Integer> pq1 = new PriorityQueue<>(new Comparator<Integer>() {
// @Override
// public int compare(Integer o1, Integer o2) {
// return o2 - o1;
// }
// });
System.out.println();
PriorityQueue<Integer> pq1 = new PriorityQueue<>((a, b) -> b - a);
// var p=new PriorityQueue<Integer>((a,b)->b-a);
pq1.offer(452);
pq1.offer(3);
pq1.offer(2352);
pq1.offer(4);
pq1.offer(34);
pq1.offer(2);
System.out.println(pq1.peek());
while (!pq1.isEmpty())
System.out.println(pq1.poll());
}