From ecabb4077b32fa451f0c2b3fdfbc434f51b92ae2 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sun, 8 Jan 2023 22:18:23 +0800 Subject: [PATCH] Update codes of heap.java and my_heap.java --- codes/java/chapter_heap/heap.java | 60 +++++++ codes/java/chapter_heap/my_heap.java | 170 +++++++++++++++++++ codes/java/chapter_tree/binary_tree_bfs.java | 2 +- codes/java/chapter_tree/binary_tree_dfs.java | 2 +- codes/java/include/PrintUtil.java | 14 +- codes/java/include/TreeNode.java | 21 +-- docs/chapter_heap/heap.md | 54 ++++++ 7 files changed, 307 insertions(+), 16 deletions(-) create mode 100644 codes/java/chapter_heap/heap.java create mode 100644 codes/java/chapter_heap/my_heap.java create mode 100644 docs/chapter_heap/heap.md diff --git a/codes/java/chapter_heap/heap.java b/codes/java/chapter_heap/heap.java new file mode 100644 index 000000000..16e35e603 --- /dev/null +++ b/codes/java/chapter_heap/heap.java @@ -0,0 +1,60 @@ +/** + * File: my_heap.java + * Created Time: 2023-01-07 + * Author: Krahets (krahets@163.com) + */ + +package chapter_heap; + +import include.*; +import java.util.*; + + +public class heap { + public static void testPush(Queue heap, int val) { + // 元素入堆 + heap.add(val); + + System.out.format("\n添加元素 %d 后\n", val); + PrintUtil.printHeap(heap); + } + + public static void testPoll(Queue heap) { + // 元素出堆 + int val = heap.poll(); + + System.out.format("\n出堆元素为 %d\n", val); + PrintUtil.printHeap(heap); + } + + public static void main(String[] args) { + /* 初始化堆 */ + // 初始化最小堆 + Queue minHeap = new PriorityQueue<>(); + // 初始化最大堆(使用 lambda 表达式修改 Comparator) + Queue maxHeap = new PriorityQueue<>((a, b) -> { return b - a; }); + + /* 元素入堆 */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* 获取堆顶元素 */ + int peek = maxHeap.peek(); + System.out.format("\n堆顶元素为 %d\n", peek); + + /* 元素出堆 */ + testPoll(maxHeap); + testPoll(maxHeap); + + /* 获取堆大小 */ + int size = maxHeap.size(); + System.out.format("\n堆元素数量为 %d\n", size); + + /* 判断堆是否为空 */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\n堆是否为空 %b\n", isEmpty); + } +} diff --git a/codes/java/chapter_heap/my_heap.java b/codes/java/chapter_heap/my_heap.java new file mode 100644 index 000000000..39583c1d7 --- /dev/null +++ b/codes/java/chapter_heap/my_heap.java @@ -0,0 +1,170 @@ +/** + * File: my_heap.java + * Created Time: 2023-01-07 + * Author: Krahets (krahets@163.com) + */ + +package chapter_heap; + +import include.*; +import java.util.*; + +class MaxHeap { + // 使用列表而非数组,这样无需考虑扩容问题 + private List maxHeap; + + /* 构造函数,建立空堆 */ + public MaxHeap() { + maxHeap = new ArrayList<>(); + } + + /* 构造函数,堆化 nums 所有元素 */ + public MaxHeap(List nums) { + // 将元素拷贝至堆中 + maxHeap = new ArrayList<>(nums); + // 堆化除叶结点以外的其他所有结点 + for (int i = parent(size() - 1); i >= 0; i--) { + heapify(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 a = maxHeap.get(i), + b = maxHeap.get(j), + tmp = a; + maxHeap.set(i, b); + 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); + // 从底至顶堆化 + int i = size() - 1; + while (true) { + int p = parent(i); + if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) + break; + swap(i, p); + i = p; + } + } + + /* 元素出堆 */ + public int poll() { + // 判空处理 + if (isEmpty()) + throw new EmptyStackException(); + // 交换根结点与右下角(即最后一个)结点 + swap(0, size() - 1); + // 删除结点 + int val = maxHeap.remove(size() - 1); + // 从顶至底堆化 + heapify(0); + // 返回堆顶元素 + return val; + } + + /* 从结点 i 开始,从顶至底堆化 */ + private void heapify(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 最大,则无需继续堆化,跳出 + if (ma == i) break; + // 交换结点 i 与结点 max + swap(i, ma); + // 循环向下堆化 + i = ma; + } + } + + /* 打印堆(二叉树) */ + public void print() { + Queue queue = new PriorityQueue<>((a, b) -> { return b - a; }); + queue.addAll(maxHeap); + PrintUtil.printHeap(queue); + } +} + + +public class my_heap { + public static void testPush(MaxHeap maxHeap, int val) { + // 元素入堆 + maxHeap.push(val); + + System.out.format("\n添加元素 %d 后\n", val); + maxHeap.print(); + } + + public static void testPoll(MaxHeap maxHeap) { + // 元素出堆 + int val = maxHeap.poll(); + + System.out.format("\n出堆元素为 %d\n", val); + maxHeap.print(); + } + + public static void main(String[] args) { + /* 初始化堆 */ + // 初始化最大堆 + MaxHeap maxHeap = new MaxHeap(); + + /* 元素入堆 */ + testPush(maxHeap, 1); + testPush(maxHeap, 3); + testPush(maxHeap, 2); + testPush(maxHeap, 5); + testPush(maxHeap, 4); + + /* 获取堆顶元素 */ + int peek = maxHeap.peek(); + System.out.format("\n堆顶元素为 %d\n", peek); + + /* 元素出堆 */ + testPoll(maxHeap); + testPoll(maxHeap); + + /* 获取堆大小 */ + int size = maxHeap.size(); + System.out.format("\n堆元素数量为 %d\n", size); + + /* 判断堆是否为空 */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\n堆是否为空 %b\n", isEmpty); + } +} diff --git a/codes/java/chapter_tree/binary_tree_bfs.java b/codes/java/chapter_tree/binary_tree_bfs.java index 450311d08..ffeb352e0 100644 --- a/codes/java/chapter_tree/binary_tree_bfs.java +++ b/codes/java/chapter_tree/binary_tree_bfs.java @@ -30,7 +30,7 @@ public class binary_tree_bfs { public static void main(String[] args) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode root = TreeNode.arrToTree(new Integer[] { 1, 2, 3, 4, 5, 6, 7 }); + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); System.out.println("\n初始化二叉树\n"); PrintUtil.printTree(root); diff --git a/codes/java/chapter_tree/binary_tree_dfs.java b/codes/java/chapter_tree/binary_tree_dfs.java index ad5337f22..9f2d0507d 100644 --- a/codes/java/chapter_tree/binary_tree_dfs.java +++ b/codes/java/chapter_tree/binary_tree_dfs.java @@ -43,7 +43,7 @@ public class binary_tree_dfs { public static void main(String[] args) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode root = TreeNode.arrToTree(new Integer[] { 1, 2, 3, 4, 5, 6, 7 }); + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); System.out.println("\n初始化二叉树\n"); PrintUtil.printTree(root); diff --git a/codes/java/include/PrintUtil.java b/codes/java/include/PrintUtil.java index b2593ac1f..1ebc45ba0 100755 --- a/codes/java/include/PrintUtil.java +++ b/codes/java/include/PrintUtil.java @@ -105,10 +105,16 @@ public class PrintUtil { } } - public static void printHeap(PriorityQueue queue) { - Integer[] nums = (Integer[])queue.toArray(); - TreeNode root = TreeNode.arrToTree(nums); - + /** + * Print a heap (PriorityQueue) + * @param queue + */ + public static void printHeap(Queue queue) { + List list = new ArrayList<>(queue); + System.out.print("堆的数组表示:"); + System.out.println(list); + System.out.println("堆的树状表示:"); + TreeNode root = TreeNode.listToTree(list); printTree(root); } } diff --git a/codes/java/include/TreeNode.java b/codes/java/include/TreeNode.java index 11d457a10..22a349747 100644 --- a/codes/java/include/TreeNode.java +++ b/codes/java/include/TreeNode.java @@ -23,26 +23,27 @@ public class TreeNode { /** * Generate a binary tree given an array - * @param arr + * @param list * @return */ - public static TreeNode arrToTree(Integer[] arr) { - if (arr.length == 0) + public static TreeNode listToTree(List list) { + int size = list.size(); + if (size == 0) return null; - TreeNode root = new TreeNode(arr[0]); + TreeNode root = new TreeNode(list.get(0)); Queue queue = new LinkedList<>() {{ add(root); }}; int i = 0; while(!queue.isEmpty()) { TreeNode node = queue.poll(); - if (++i >= arr.length) break; - if(arr[i] != null) { - node.left = new TreeNode(arr[i]); + if (++i >= size) break; + if (list.get(i) != null) { + node.left = new TreeNode(list.get(i)); queue.add(node.left); } - if (++i >= arr.length) break; - if(arr[i] != null) { - node.right = new TreeNode(arr[i]); + if (++i >= size) break; + if (list.get(i) != null) { + node.right = new TreeNode(list.get(i)); queue.add(node.right); } } diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md new file mode 100644 index 000000000..b0151622f --- /dev/null +++ b/docs/chapter_heap/heap.md @@ -0,0 +1,54 @@ +# 堆 + +「堆 Heap」是一种特殊的树状数据结构,并且是一颗「完全二叉树」。堆主要分为两种: + +- 「大顶堆 Max Heap」,任意结点的值 $\geq$ 其子结点的值,因此根结点的值最大; +- 「小顶堆 Min Heap」,任意结点的值 $\leq$ 其子结点的值,因此根结点的值最小; + +(图) + +!!! tip "" + + 大顶堆和小顶堆的定义、性质、操作本质上是相同的,区别只是大顶堆在求最大值,小顶堆在求最小值。 + +## 堆常用操作 + +值得说明的是,多数编程语言提供的是「优先队列 Priority Queue」,其是一种抽象数据结构,**定义为具有出队优先级的队列**。 + +而恰好,堆的定义与优先队列的操作逻辑完全吻合,大顶堆就是一个元素从大到小出队的优先队列。从使用角度看,我们可以将「优先队列」和「堆」理解为等价的数据结构,下文将统一使用 “堆” 这个名称。 + +堆的常用操作见下表(方法命名以 Java 为例)。 + +

Table. 堆的常用操作

+ +
+ +| 方法 | 描述 | +| --------- | -------------------------------------------- | +| add() | 元素入堆 | +| poll() | 堆顶元素出堆 | +| peek() | 访问堆顶元素(大 / 小顶堆分别为最大 / 小值) | +| size() | 获取堆的元素数量 | +| isEmpty() | 判断堆是否为空 | + +
+ +```java + +``` + +## 堆的实现 + +!!! tip + + 下文使用「大顶堆」来举例,「小顶堆」的用法与实现可以简单地将所有 $>$ ($<$) 替换为 $<$ ($>$) 即可。 + +我们一般使用「数组」来存储「堆」,这是因为完全二叉树非常适合用数组来表示(在二叉树章节有详细解释)。 + + + +## 堆常见应用 + +- 优先队列。 +- 堆排序。 +- 获取数据 Top K 大(小)元素。