高級數據結構-1

2. 堆

堆就是用數組實現的二叉樹,所有它沒有使用父指針或者子指針。堆根據“堆屬性”來排序,“堆屬性”決定了樹中節點的位置。

堆的常用方法:

  • 構建優先隊列
  • 支持堆排序
  • 快速找出一個集合中的最小值(或者最大值)

堆分爲兩種:最大堆和最小堆,兩者的差別在於節點的排序方式。

在最大堆中,父節點的值比每一個子節點的值都要大。在最小堆中,父節點的值比每一個子節點的值都要小。這就是所謂的“堆屬性”,並且這個屬性對堆中的每一個節點都成立。

這裏有一篇介紹的非常好的文章:
https://www.jianshu.com/p/6b526aa481b1

堆排序

  1. 創建一個堆 H[0……n-1];

  2. 把堆首(最大值)和堆尾互換;

  3. 把堆的尺寸縮小 1,並調用 shift_down(0),目的是把新的數組頂端數據調整到相應位置;

  4. 重複步驟 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/

  1. 快速排序法

在快速排序中,每一輪排序都會將序列一分爲二,左子區間的數都小於基準數,右子區間的數都大於基準數,而快速排序用來解決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;
    }
};
  1. 堆排序法

出自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 頻率之後的元素不用再去處理,進一步優化時間複雜度。
具體操作爲:

  1. 藉助 哈希表 來建立數字和其出現次數的映射,遍歷一遍數組統計元素的頻率
  2. 維護一個元素數目爲 kk 的最小堆
  3. 每次都將新的元素與堆頂元素(堆中頻率最小的元素)進行比較
  4. 如果新的元素的頻率比堆頂端的元素大,則彈出堆頂端的元素,將新的元素添加進堆中
  5. 最終,堆中的 kk 個元素即爲前 kk 個高頻元素

在這裏插入圖片描述

  1. 桶排序法

首先依舊使用哈希表統計頻率,統計完成後,創建一個數組,將頻率作爲數組下標,對於出現頻率不同的數字集合,存入對應的數組下標即可。
在這裏插入圖片描述
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]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章