什么是堆
堆是一种满足特定条件的完全二叉树,它分为
1.小顶堆:任意节点的值<=其任意子节点的值
2.大顶堆:任意节点的值>=其任意子节点的值
堆的作用
堆常在编程语言中实现优先队列(priority queue),大顶堆担任按从大到小出队的优先队列
时间复杂度
与完全二叉树对应,
元素入堆 push() O(Logn)
元素出堆 pop() O(Logn)
查看堆顶顶元素 peek() O(1)
获取堆元素数量 size() O(1)
判断堆是否为空 isEmpty() O(1)
堆的实现形式
因为堆是一种完全二叉树,而完全二叉树适合用数组表示,所以我们用数组来实现堆。
在堆中元素代表节点的值,索引代表在二叉树的位置,节点指针通过节点映射公式来实现:
子节点:2i+1和2i+2,父节点(i-1)/2,当索引越界表示节点不存在。
节点入大顶堆
1.元素入队
2.找到父元素
3.判断是否优化(判断条件:是否越界||是否满足条件)
4.与父节点交换位置
因为只遍历一半的树时间复杂度为O(Logn)
/* 元素入堆 */
public void push(int val) {
// 添加节点
maxHeap.add(val);
// 从底至顶堆化
siftUp(size() - 1);
}
/* 从节点 i 开始,从底至顶堆化 */
private void siftUp(int i) {
while (true) {
// 获取节点 i 的父节点
int p = parent(i);
// 当“越过根节点”或“节点无须修复”时,结束堆化
if (p < 0 || maxHeap.get(i) <= maxHeap.get(p))
break;
// 交换两节点
swap(i, p);
// 循环向上堆化
i = p;
}
}
元素出堆
1.出堆元素与堆底交换位置
2.元素出堆
3.找到子节点的最大值
4.交换节点并循换优化直到这个元素的最大值是根节点为止
同样时间复杂度也是O(Logn)
/* 元素出堆 */
public int pop() {
// 判空处理
if (isEmpty())
throw new IndexOutOfBoundsException();
// 交换根节点与最右叶节点(交换首元素与尾元素)
swap(0, size() - 1);
// 删除节点
int val = maxHeap.remove(size() - 1);
// 从顶至底堆化
siftDown(0);
// 返回堆顶元素
return val;
}
/* 从节点 i 开始,从顶至底堆化 */
private void siftDown(int i) {
while (true) {
// 判断节点 i, l, r 中值最大的节点,记为 ma
int l = left(i), r = right(i), ma = i;
if (l < size() && maxHeap.get(l) > maxHeap.get(ma))
ma = l;
if (r < size() && maxHeap.get(r) > maxHeap.get(ma))
ma = r;
// 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
if (ma == i)
break;
// 交换两节点
swap(i, ma);
// 循环向下堆化
i = ma;
}
}
堆的应用
优先队列:堆通常作为实现优先队列的首选数据结构,其入队和出队操作的时间复杂度均为 O(logn) ,而建堆操作为 O(n) ,这些操作都非常高效。
堆排序:给定一组数据,我们可以用它们建立一个堆,然后不断地执行元素出堆操作,从而得到有序数据。然而,我们通常会使用一种更优雅的方式实现堆排序,详见“堆排序”章节。
获取最大的 k 个元素:这是一个经典的算法问题,同时也是一种典型应用,例如选择热度前 10 的新闻作为微博热搜,选取销量前 10 的商品等。
实现一个大顶堆MyHeap
public class TreeNode {
int val;
int height; //节点高度:该节点离叶子节点的最远距离,叶子节点高度为0,空节点高度为-1
public TreeNode left;
public TreeNode right;
TreeNode(int val) {
this.val = val;
}
public int getVal() {
return val;
}
/* 将列表反序列化为二叉树:递归 */
private static TreeNode listToTreeDFS(List<Integer> arr, int i) {
if (i < 0 || i >= arr.size() || arr.get(i) == null) {
return null;
}
TreeNode root = new TreeNode(arr.get(i));
root.left = listToTreeDFS(arr, 2 * i + 1);
root.right = listToTreeDFS(arr, 2 * i + 2);
return root;
}
/* 将列表反序列化为二叉树 */
public static TreeNode listToTree(List<Integer> arr) {
return listToTreeDFS(arr, 0);
}
}
/* 大顶堆 */
public class MyHeap {
// 使用列表而非数组,这样无须考虑扩容问题
private List<Integer> maxHeap;
/* 构造方法,根据输入列表建堆 */
public MyHeap(List<Integer> nums) {
// 将列表元素原封不动添加进堆
maxHeap = new ArrayList<>(nums);
// 堆化除叶节点以外的其他所有节点
for (int i = parent(size() - 1); i >= 0; i--) {
siftDown(i);
}
}
/* 获取左子节点的索引 */
private int left(int i) {
return 2 * i + 1;
}
/* 获取右子节点的索引 */
private int right(int i) {
return 2 * i + 2;
}
/* 获取父节点的索引 */
private int parent(int i) {
return (i - 1) / 2; // 向下整除
}
/* 交换元素 */
private void swap(int i, int j) {
int tmp = maxHeap.get(i);
maxHeap.set(i, maxHeap.get(j));
maxHeap.set(j, tmp);
}
/* 获取堆大小 */
public int size() {
return maxHeap.size();
}
/* 判断堆是否为空 */
public boolean isEmpty() {
return size() == 0;
}
/* 访问堆顶元素 */
public int peek() {
return maxHeap.get(0);
}
/* 元素入堆 */
public void push(int val) {
// 添加节点
maxHeap.add(val);
// 从底至顶堆化
siftUp(size() - 1);
}
/* 从节点 i 开始,从底至顶堆化 */
private void siftUp(int i) {
while (true) {
// 获取节点 i 的父节点
int p = parent(i);
// 当“越过根节点”或“节点无须修复”时,结束堆化
if (p < 0 || maxHeap.get(i) <= maxHeap.get(p))
break;
// 交换两节点
swap(i, p);
// 循环向上堆化
i = p;
}
}
/* 元素出堆 */
public int pop() {
// 判空处理
if (isEmpty())
throw new IndexOutOfBoundsException();
// 交换根节点与最右叶节点(交换首元素与尾元素)
swap(0, size() - 1);
// 删除节点
int val = maxHeap.remove(size() - 1);
// 从顶至底堆化
siftDown(0);
// 返回堆顶元素
return val;
}
/* 从节点 i 开始,从顶至底堆化 */
private void siftDown(int i) {
while (true) {
// 判断节点 i, l, r 中值最大的节点,记为 ma
int l = left(i), r = right(i), ma = i;
if (l < size() && maxHeap.get(l) > maxHeap.get(ma))
ma = l;
if (r < size() && maxHeap.get(r) > maxHeap.get(ma))
ma = r;
// 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
if (ma == i)
break;
// 交换两节点
swap(i, ma);
// 循环向下堆化
i = ma;
}
}
/* 打印二叉树 */
public static void printTree(TreeNode root) {
printTree(root, null, false);
}
public static void printTree(TreeNode root, Trunk prev, boolean isRight) {
if (root == null) {
return;
}
String prev_str = " ";
Trunk trunk = new Trunk(prev, prev_str);
printTree(root.right, trunk, true);
if (prev == null) {
trunk.str = "———";
} else if (isRight) {
trunk.str = "/———";
prev_str = " |";
} else {
trunk.str = "\\———";
prev.str = prev_str;
}
showTrunks(trunk);
System.out.println(" " + root.getVal());
if (prev != null) {
prev.str = prev_str;
}
trunk.str = " |";
printTree(root.left, trunk, false);
}
public static void showTrunks(Trunk p) {
if (p == null) {
return;
}
showTrunks(p.prev);
System.out.print(p.str);
}
/* 打印堆(优先队列) */
public static void printHeap(Queue<Integer> queue) {
List<Integer> list = new ArrayList<>(queue);
System.out.print("堆的数组表示:");
System.out.println(list);
System.out.println("堆的树状表示:");
TreeNode root = TreeNode.listToTree(list);
printTree(root);
}
/* 打印堆(二叉树) */
public void print() {
Queue<Integer> queue = new PriorityQueue<>((a, b) -> {
return b - a;
});
queue.addAll(maxHeap);
printHeap(queue);
}
}
MyHeapTest
public class my_heapTest {
public static void main(String[] args) {
/* 初始化大顶堆 */
MyHeap maxHeap = new MyHeap(Arrays.asList(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2));
System.out.println("\n输入列表并建堆后");
maxHeap.print();
/* 获取堆顶元素 */
int peek = maxHeap.peek();
System.out.format("\n堆顶元素为 %d\n", peek);
/* 元素入堆 */
int val = 7;
maxHeap.push(val);
System.out.format("\n元素 %d 入堆后\n", val);
maxHeap.print();
/* 堆顶元素出堆 */
peek = maxHeap.pop();
System.out.format("\n堆顶元素 %d 出堆后\n", peek);
maxHeap.print();
/* 获取堆大小 */
int size = maxHeap.size();
System.out.format("\n堆元素数量为 %d\n", size);
/* 判断堆是否为空 */
boolean isEmpty = maxHeap.isEmpty();
System.out.format("\n堆是否为空 %b\n", isEmpty);
}
}