使用优先队列寻找中位数&&用stack 实现 queue && ADT 的应用
Next, Suppose we would like to invent a new ADT called MedianFinder which is a collection of integers and supports finding the median of the collection.
MedianFinder
add(x); // adds x to the collection of numbers
median(); // returns the median from a collection of numbers Describe
how you could implement this ADT by using existing Java ADTs as building blocks. What’s the most efficient implementation you can come up with?
import java.util.Comparator; import java.util.PriorityQueue; public class MedianFinder { private PriorityQueue<Integer> smallerHalf; private PriorityQueue<Integer> largerHalf; public MedianFinder() { smallerHalf = new PriorityQueue<>(Comparator.reverseOrder()); largerHalf = new PriorityQueue<>(); } public void add(int x) { if (smallerHalf.isEmpty() || x <= smallerHalf.peek()) { smallerHalf.add(x); } else { largerHalf.add(x); } balanceHeaps(); } private void balanceHeaps() { if (smallerHalf.size() > largerHalf.size() + 1) { largerHalf.add(smallerHalf.poll()); } else if (largerHalf.size() > smallerHalf.size()) { smallerHalf.add(largerHalf.poll()); } } public double median() { if (smallerHalf.size() == largerHalf.size()) { return (smallerHalf.peek() + largerHalf.peek()) / 2.0; } else { return smallerHalf.peek(); } } public static void main(String[] args) { MedianFinder test = new MedianFinder(); test.add(8); test.add(7); test.add(6); test.add(5); test.add(4); test.add(3); test.add(2); double median = test.median(); System.out.println(median); } }
这段代码实现了一个名为 `MedianFinder` 的类,用于找到一系列整数的中位数。在这个类中,我们使用了两个优先队列(`PriorityQueue`),`smallerHalf` 和 `largerHalf` 分别存储较小的一半和较大的一半数字。优先队列可以很方便地获取、添加和删除元素。
1. `public MedianFinder()`: 构造函数,初始化两个优先队列。`smallerHalf` 为大顶堆(通过使用 `Comparator.reverseOrder()` 反转默认的小顶堆),存储较小的一半数字;`largerHalf` 为小顶堆,存储较大的一半数字。
2. `public void add(int x)`: 向集合中添加一个整数 `x`。根据 `x` 和 `smallerHalf` 堆顶元素的大小关系,将 `x` 添加到适当的堆中。然后调用 `balanceHeaps()` 方法来平衡两个堆。
3. `private void balanceHeaps()`: 平衡两个堆的大小。确保两个堆的大小之差不超过 1,这样中位数可以在常数时间内找到。如果 `smallerHalf` 的大小比 `largerHalf` 大 2 个或以上,那么将 `smallerHalf` 的堆顶元素移动到 `largerHalf`。反之,如果 `largerHalf` 的大小比 `smallerHalf` 大,那么将 `largerHalf` 的堆顶元素移动到 `smallerHalf`。
4. `public double median()`: 计算并返回中位数。如果两个堆的大小相同,那么中位数是两个堆顶元素的平均值;如果 `smallerHalf` 的大小比 `largerHalf` 大 1,那么中位数就是 `smallerHalf` 的堆顶元素。
5. `public static void main(String[] args)`: 主方法,用于测试 `MedianFinder` 类的功能。向 `MedianFinder` 对象中添加一些整数,然后计算并打印中位数。
这个实现利用了数据结构优先队列(`PriorityQueue`),可以在对数时间内向集合中添加整数,并在常数时间内找到中位数。这种方法在处理大量数据时非常高效。
下面是一个示例,展示了在类的成员变量中创建比较器的方式:
import java.util.Comparator; import java.util.PriorityQueue; public class CustomPriorityQueueExample { private Comparator<String> lengthComparator; // 类的成员变量 public CustomPriorityQueueExample() { lengthComparator = new LengthComparator(); // 在构造函数中创建比较器实例 } public void processElements() { PriorityQueue<String> pq = new PriorityQueue<>(lengthComparator); pq.add("Java"); pq.add("Python"); pq.add("C"); pq.add("JavaScript"); while (!pq.isEmpty()) { String element = pq.poll(); System.out.println(element); } } private class LengthComparator implements Comparator<String> { @Override public int compare(String s1, String s2) { return Integer.compare(s1.length(), s2.length()); } } public static void main(String[] args) { CustomPriorityQueueExample example = new CustomPriorityQueueExample(); example.processElements(); } }
在这个示例中,我们在类的成员变量中创建了一个比较器`lengthComparator`。这个比较器用于比较字符串的长度。
在构造函数`CustomPriorityQueueExample()`中,我们实例化了`lengthComparator`比较器。然后,在`processElements()`方法中,我们创建了一个新的`PriorityQueue`实例,并将比较器传递给它。
在私有内部类`LengthComparator`中,我们实现了`Comparator<String>`接口,并覆盖了`compare()`方法来定义字符串之间的比较逻辑。
通过将比较器作为类的成员变量,我们可以在需要时重复使用相同的比较器实例,而无需每次都创建一个新的比较器。
在`main()`方法中,我们创建了一个`CustomPriorityQueueExample`实例,并调用`processElements()`方法来演示使用自定义比较器的`PriorityQueue`。
这个示例展示了如何在类的成员变量中创建比较器,以提供可重用的比较器逻辑,并在需要时使用该比较器来排序元素。
import java.util.Stack; public class QueueExample<T>{ //stack last in first out //queue first in first out, finish push and poll methods //E poll(): 移除并返回队列的头部元素,如果队列为空,则返回null。 //push(e) which adds an element, //E pop(): 移除并返回栈顶的元素。 private Stack<T> inbox; private Stack<T> outbox; public QueueExample() { inbox = new Stack<T>(); outbox = new Stack<T>(); } public T poll() { if(outbox.isEmpty()) { while(!inbox.isEmpty()) { outbox.push(inbox.pop()); } } return outbox.pop(); } public void push(T item) { inbox.push(item); } public boolean isEmpty() { return inbox.isEmpty() && outbox.isEmpty(); } }
判断给定整数数组A中是否存在两个数的和等于给定的目标值k。
import java.util.HashSet; public class SumPairChecker { public static boolean hasSumPair(int[] A, int k) { HashSet<Integer> numSet = new HashSet<>(); for (int num : A) { int target = k - num; if (numSet.contains(target)) { return true; } numSet.add(num); } return false; } public static void main(String[] args) { int[] array = {1, 2, 3, 4, 5}; int target = 7; boolean hasPair = hasSumPair(array, target); System.out.println(hasPair); } }
Find the k most common words in a document. Assume that you can represent this as an array of Strings, where each word is an element in the array. You might find using multiple data structures useful.
import java.util.*; public List<String> findKMostCommonWords(String[] words, int k) { Map<String, Integer> wordFrequency = new HashMap<>(); for (String word : words) { wordFrequency.put(word, wordFrequency.getOrDefault(word, 0) + 1); } PriorityQueue<Map.Entry<String, Integer>> minHeap = new PriorityQueue<>(Comparator.comparingInt(Map.Entry::getValue)); for (Map.Entry<String, Integer> entry : wordFrequency.entrySet()) { minHeap.offer(entry); if (minHeap.size() > k) { minHeap.poll(); } } List<String> result = new ArrayList<>(); while (!minHeap.isEmpty()) { result.add(0, minHeap.poll().getKey()); } return result; }
`PriorityQueue<Map.Entry<String, Integer>> minHeap` 表示创建了一个优先队列(最小堆),其中元素的类型为 `Map.Entry<String, Integer>`。
`Map.Entry` 是一个表示键值对的接口,在这种情况下,键是字符串(String),值是整数(Integer)。因此,`Map.Entry<String, Integer>` 表示一个键为字符串、值为整数的键值对。
`PriorityQueue` 是Java中的一个队列实现,它可以根据元素的优先级进行排序。在这种情况下,我们创建了一个最小堆,即优先级最低的元素位于队列的前面。
`minHeap` 是我们创建的优先队列的实例,用于存储键值对,其中键是单词(字符串),值是该单词在文档中的频率(整数)。通过指定 `Comparator.comparingInt(Map.Entry::getValue)` 作为优先队列的比较器,我们基于频率对键值对进行升序排序,以便在堆的顶部保留频率最低的元素。
这样,我们可以使用 `minHeap` 来跟踪文档中频率最低的k个单词。
这段代码实现了在给定的单词数组中找到出现频率最高的k个单词的功能。下面对代码进行逐行分析:
1. 创建一个空的 `wordFrequency` HashMap,用于存储单词及其出现的频率。
2. 遍历输入的 `words` 数组:
- 如果 `word` 已经存在于 `wordFrequency` 中,使用 `getOrDefault` 方法获取其当前频率并将其加1。
- 如果 `word` 不存在于 `wordFrequency` 中,将其添加到 `wordFrequency` 中,并将其初始频率设置为1。
3. 创建一个优先队列 `minHeap`,用于存储键值对 `Map.Entry<String, Integer>`,根据频率进行升序排序。
4. 遍历 `wordFrequency` 的每个键值对:
- 将当前键值对 `entry` 添加到 `minHeap` 中。
- 如果 `minHeap` 的大小超过了k,即队列中的元素个数大于k,则通过 `poll` 方法移除优先队列的顶部元素,即最小频率的元素。
5. 创建一个空的 `result` 列表,用于存储最终的结果。
6. 当 `minHeap` 非空时,重复以下步骤:
- 使用 `poll` 方法从 `minHeap` 中取出一个元素(频率最低的键值对)。
- 将该元素的键(即单词)添加到 `result` 列表的开头,以便保持最终结果的频率降序排序。
7. 返回 `result` 列表,其中包含了出现频率最高的k个单词,按频率降序排列。
总体来说,该代码使用HashMap来计算每个单词的频率,并使用优先队列(最小堆)来跟踪频率最低的k个单词。它通过一次遍历和两次堆操作,以时间复杂度为O(n log k)的效率找到了结果。
for (Map.Entry<String, Integer> entry : wordFrequency.entrySet()) { minHeap.offer(entry); if (minHeap.size() > k) { minHeap.poll(); } }
这段代码是在遍历 `wordFrequency` 中的每个键值对 `entry`,将其添加到 `minHeap` 优先队列中,并确保队列的大小不超过k。
具体的执行过程如下:
1. 对于 `wordFrequency` 中的每个键值对 `entry`,执行以下操作:
2. 使用 `minHeap.offer(entry)` 将当前键值对 `entry` 添加到优先队列 `minHeap` 中。
3. 接着,检查优先队列的大小是否超过了k(`minHeap.size() > k`)。
- 如果队列大小超过k,说明当前队列中保存的元素个数已经超过了k个,需要将频率最低的元素移除。
- 使用 `minHeap.poll()` 方法移除优先队列的顶部元素,即频率最低的键值对。
- 这样,优先队列中始终保留着频率最高的k个键值对,而频率较低的键值对会被移除。
- 这样做的目的是确保在遍历完 `wordFrequency` 的所有键值对后,优先队列中只剩下频率最高的k个单词。
4. 重复执行步骤2和步骤3,直到遍历完 `wordFrequency` 中的所有键值对。
通过这段代码的执行,我们可以得到一个优先队列 `minHeap`,其中保存了频率最高的k个单词的键值对,按照频率的升序进行排序。这样,我们可以通过从队列顶部开始,依次取出元素,得到频率降序排列的最常见的k个单词。
public static void topFivePopularWords(String[] words, int k) { Map<String, Integer> counts = new HashMap<>(); for (String word : words) { if (!counts.containsKey(word)) { counts.put(word, 1); } else { counts.put(word, counts.get(word) + 1); } } PriorityQueue<String> pq = new PriorityQueue<>(new Comparator<String>() { @Override public int compare(String a, String b) { return counts.get(b) - counts.get(a); } }); for (String word : counts.keySet()) { pq.add(word); } for (int i = 0; i < k; i++) { System.out.println(pq.poll()); } }

浙公网安备 33010602011771号