PriorityQueue 名叫優先級隊列,底層由堆結構實現,默認是小根堆。通過 offer 方法添加進去的元素會進行堆排序,最小的元素放在堆頂。通過 peek 方法可以獲得堆頂(最小)元素。通過 poll 方法可以刪除堆頂元素同時獲得堆頂元素,刪除之後剩下的元素中最小的元素仍處於堆頂。
一、應用場景
某電商平臺入駐了大量的商家,商家可以在平臺銷售商品,用戶可以在平臺的商家那裏購買商品。用戶付款後如果對購買到的商品不滿意,可以向平臺發起投訴。用戶對某商家的某件商品的投訴記錄會存儲在一張表中,表結構如下:
列名 | 類型 | 備註 |
---|---|---|
store_id | int | 商家id |
product_id | int | 商品id |
remark | string | 投訴記錄 |
現在的需求是:找到投訴記錄最多的前 3 個商家,目的是在搜索時對其店鋪進行降權處理。
二、思路分析
- 創建一個小根堆(PriorityQueue 默認就是小根堆)
- 小根堆中元素的數量小於 3 的時候就直接向集合中添加元素
- 當堆中的元素個數等於 3 的時候,通過 peek 方法取出堆頂元素(最小的那個)與當前遍歷到的元素比較
- 如果當前遍歷到的元素大於堆頂元素,就把原堆頂元素移除,把當前元素加入堆中
- 這樣使得移除的元素都小於堆中的元素
- 所以最後堆中保留下來的就是最大的N個元素
三、代碼實現
import java.util.*;
// 投訴日誌實體類
class ComplainLog {
// 商家id
public Integer storeId;
// 商品id
public Integer productId;
// 投訴記錄
public String remark;
ComplainLog(Integer storeId, Integer productId, String remark) {
this.storeId = storeId;
this.productId = productId;
this.remark = remark;
}
}
// 商家被投訴次數實體類
class ComplainCount {
// 商家id
public Integer storeId;
// 投訴次數
public Integer complainCount;
ComplainCount(Integer storeId, Integer complainCount) {
this.storeId = storeId;
this.complainCount = complainCount;
}
}
public class PriorityQueueTest {
public static void main(String[] args) {
// 模擬10條投訴記錄
ComplainLog log1 = new ComplainLog(101, 8650, "質量不行");
ComplainLog log2 = new ComplainLog(101, 8651, "書皮爛了,垃圾物流");
ComplainLog log3 = new ComplainLog(101, 7921, "牛肉不新鮮,懷疑是假貨");
ComplainLog log4 = new ComplainLog(101, 7963, "賣假貨,封了他家店!!!");
ComplainLog log5 = new ComplainLog(102, 6217, "店家態度不好,給他降權");
ComplainLog log6 = new ComplainLog(102, 6245, "衣服撕了。。。");
ComplainLog log7 = new ComplainLog(102, 5214, "就是想投訴");
ComplainLog log8 = new ComplainLog(103, 5215, "。。。");
ComplainLog log9 = new ComplainLog(104, 4632, "2333333");
ComplainLog log10 = new ComplainLog(104, 4632, "有人嗎");
ComplainLog log11 = new ComplainLog(104, 4632, "有人嗎,aaaaaa");
List<ComplainLog> complainLogList = new ArrayList<ComplainLog>();
complainLogList.add(log1);
complainLogList.add(log2);
complainLogList.add(log3);
complainLogList.add(log4);
complainLogList.add(log5);
complainLogList.add(log6);
complainLogList.add(log7);
complainLogList.add(log8);
complainLogList.add(log9);
complainLogList.add(log10);
complainLogList.add(log11);
// 統計出每家店鋪的投訴次數
HashMap<Integer, Integer> complainCountMap = new HashMap<Integer, Integer>();
for (ComplainLog complainLog : complainLogList) {
if (complainCountMap.get(complainLog.storeId) == null) {
complainCountMap.put(complainLog.storeId, 1);
} else {
complainCountMap.put(complainLog.storeId, complainCountMap.get(complainLog.storeId) + 1);
}
}
List<ComplainCount> complainCountList = new ArrayList<ComplainCount>();
for (Map.Entry<Integer, Integer> entry : complainCountMap.entrySet()) {
ComplainCount complainCount = new ComplainCount(entry.getKey(), entry.getValue());
complainCountList.add(complainCount);
}
// 通過PriorityQueue找出投訴記錄最多的前3個商家
PriorityQueue<ComplainCount> queue = new PriorityQueue<ComplainCount>(new Comparator<ComplainCount>() {
@Override
public int compare(ComplainCount o1, ComplainCount o2) {
return o1.complainCount.compareTo(o2.complainCount);
}
});
// 創建一個小根堆(PriorityQueue默認就是小根堆)
// 小根堆中元素的數量小於3的時候就直接向集合中添加元素
// 當堆中的元素個數等於3的時候,通過peek方法取出堆頂元素(最小的那個)與當前遍歷到的元素比較
// 如果當前遍歷到的元素大於堆頂元素,就把原堆頂元素移除,把當前元素加入堆中
// 移除的元素都小於堆中的元素
// 所以最後堆中保留下來的就是最大的N個元素
int topN = 3;
for (ComplainCount complainCount : complainCountList) {
if (queue.size() < topN) {
queue.offer(complainCount);
} else {
if (queue.peek().complainCount < complainCount.complainCount) {
queue.poll();
queue.offer(complainCount);
}
}
}
for (ComplainCount complainCount : queue) {
System.out.println(complainCount.storeId + ":" + complainCount.complainCount);
}
}
}
四、運行結果
可以看出最終的結果是 101,102,104 這三家店的投訴次數最多,符合預期。
五、總結
PriorityQueue 主要在大數據量求 TopN 這種場景下使用的,少量數據直接排個序就 ok。
關注我的微信公衆號(曲健磊的個人隨筆),獲取更多精彩內容: