2. 堆
堆就是用數組實現的二叉樹,所有它沒有使用父指針或者子指針。堆根據“堆屬性”來排序,“堆屬性”決定了樹中節點的位置。
堆的常用方法:
- 構建優先隊列
- 支持堆排序
- 快速找出一個集合中的最小值(或者最大值)
堆分爲兩種:最大堆和最小堆,兩者的差別在於節點的排序方式。
在最大堆中,父節點的值比每一個子節點的值都要大。在最小堆中,父節點的值比每一個子節點的值都要小。這就是所謂的“堆屬性”,並且這個屬性對堆中的每一個節點都成立。
這裏有一篇介紹的非常好的文章:
https://www.jianshu.com/p/6b526aa481b1
堆排序
-
創建一個堆 H[0……n-1];
-
把堆首(最大值)和堆尾互換;
-
把堆的尺寸縮小 1,並調用 shift_down(0),目的是把新的數組頂端數據調整到相應位置;
-
重複步驟 2,直到堆的尺寸爲 1。
#include <stdio.h>
void Swap(int *heap, int len); /* 交換根節點和數組末尾元素的值 */
void BuildMaxHeap(int *heap, int len);/* 構建大頂堆 */
int main()
{
int a[6] = {7, 3, 8, 5, 1, 2};
int len = 6; /* 數組長度 */
int i;
for (i = len; i > 0; i--)
{
BuildMaxHeap(a, i);
Swap(a, i);
}
for (i = 0; i < len; i++)
{
printf("%d ", a[i]);
}
return 0;
}
/* Function: 構建大頂堆 */
void BuildMaxHeap(int *heap, int len)
{
int i;
int temp;
for (i = len/2-1; i >= 0; i--)
{
if ((2*i+1) < len && heap[i] < heap[2*i+1]) /* 根節點大於左子樹 */
{
temp = heap[i];
heap[i] = heap[2*i+1];
heap[2*i+1] = temp;
/* 檢查交換後的左子樹是否滿足大頂堆性質 如果不滿足 則重新調整子樹結構 */
if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2]))
{
BuildMaxHeap(heap, len);
}
}
if ((2*i+2) < len && heap[i] < heap[2*i+2]) /* 根節點大於右子樹 */
{
temp = heap[i];
heap[i] = heap[2*i+2];
heap[2*i+2] = temp;
/* 檢查交換後的右子樹是否滿足大頂堆性質 如果不滿足 則重新調整子樹結構 */
if ((2*(2*i+2)+1 < len && heap[2*i+2] < heap[2*(2*i+2)+1]) || (2*(2*i+2)+2 < len && heap[2*i+2] < heap[2*(2*i+2)+2]))
{
BuildMaxHeap(heap, len);
}
}
}
}
/* Function: 交換交換根節點和數組末尾元素的值*/
void Swap(int *heap, int len)
{
int temp;
temp = heap[0];
heap[0] = heap[len-1];
heap[len-1] = temp;
}
top K問題
LeetCode374
https://leetcode-cn.com/problems/top-k-frequent-elements/solution/qian-k-ge-gao-pin-yuan-su-by-leetcode/
- 快速排序法
在快速排序中,每一輪排序都會將序列一分爲二,左子區間的數都小於基準數,右子區間的數都大於基準數,而快速排序用來解決TopK問題,也是基於此的。N個數經過一輪快速排序後,如果基準數的位置被換到了i,那麼區間[0,N-1]就被分爲了[0,i-1]和[i+1,N-1],這也就是說,此時有N-1-i個數比基準數大,i個數比基準數小,假設N-1-i=X那麼就會有以下幾種情況:
①X=K。這種情況說明比基準數大的有K個,其他的都比基準數小,那麼就說明這K個比基準數大的數就是TopK了;
②X<K。這種情況說明比基準數大的數不到K個,但是這X肯定是屬於TopK中的TopX,而剩下的K-X就在[0,i]之間,此時就應當在[0,i]中找到Top(K-X),這就轉換爲了TopK的子問題,可以選擇用遞歸解決;
③X>K。這種情況說明比基準數大的數超過了K個,那麼就說明TopK必定位於[i+1,N-1]中,此時就應當繼續在[i+1,N-1]找TopK,這樣又成了TopK的一個子問題,也可以選擇用遞歸解決。
代碼思路如下(有點問題,超時)
class Solution {
public:
int partition(vector<int> &nums, int l, int r){
int base = nums[l];
int base_index = l;
while(l<r){
while(l<r && nums[r]>base) --r;
while(l<r && nums[l]<base) ++l;
swap(nums[l], nums[r]);
}
nums[base_index] = nums[l];
nums[l] = base;
return l;
}
int find_topK_index(vector<int> &nums, int k, int l, int r){
int index = partition(nums, l, r);
int count = r-index;
if(count == k){
return index;
}
else if(count<k){
// cout<<index<<endl;
return find_topK_index(nums, k-count, l, index-1);
}
return find_topK_index(nums, k, index+1, r);
}
vector<int> topKFrequent(vector<int>& nums, int k) {
int len = nums.size();
if(len==k){
return nums;
}
vector<int> temp(nums.begin(), nums.end());
int index = find_topK_index(temp, k, 0, len-1);
vector<int> result;
for(int i=len-1; i>index; --i){
result.push_back(index);
}
return result;
}
};
- 堆排序法
出自LeetCode上面的題解:
https://leetcode-cn.com/problems/top-k-frequent-elements/solution/leetcode-di-347-hao-wen-ti-qian-k-ge-gao-pin-yuan-/
題目最終需要返回的是前 kk 個頻率最大的元素,可以想到藉助堆這種數據結構,對於 kk 頻率之後的元素不用再去處理,進一步優化時間複雜度。
具體操作爲:
- 藉助 哈希表 來建立數字和其出現次數的映射,遍歷一遍數組統計元素的頻率
- 維護一個元素數目爲 kk 的最小堆
- 每次都將新的元素與堆頂元素(堆中頻率最小的元素)進行比較
- 如果新的元素的頻率比堆頂端的元素大,則彈出堆頂端的元素,將新的元素添加進堆中
- 最終,堆中的 kk 個元素即爲前 kk 個高頻元素
- 桶排序法
首先依舊使用哈希表統計頻率,統計完成後,創建一個數組,將頻率作爲數組下標,對於出現頻率不同的數字集合,存入對應的數組下標即可。
O(n)的複雜度,python寫起來比較簡單。
C++寫也可以,需要用unsorted_map實現哈希表,用list容器實現桶。
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
times = {}
for i in nums:
if i in times:
times[i] += 1
else:
times[i] = 1
bottom = [[] for _ in range(len(nums)+1)]
for i in times.keys():
time = times[i]
bottom[time].append(i)
res = []
for i in bottom[::-1]:
res.extend(i)
return res[0:k]