minheap+hashmap組合解決動態topK問題(附堆排序完整實現)

TopK的解決方法通常有兩種:堆排序和partition。前者用優先隊列實現,時間複雜度爲O(NlogK)(N爲元素總數量),後者能夠直接調用C++ STL中的nth_element函數,時間複雜度O(N)。若是想獲取動態更新數據的topK就不那麼容易了,好比實時更新最常訪問的top10的網址,顯然除了維護一個size爲10的最小堆之外還須要一個哈希表實時記錄每個網址的訪問次數,並決定是否動態加入到最大堆中,同時可能刪除堆中的元素。那麼如何得到該網址在堆中的位置呢?須要另外一個hashmap記錄網址及其對應的offset。由於須要調整堆,若是直接用優先隊列實現是達不到要求的,優先隊列只能對堆頂元素進行操做,因此有必要用數組模擬堆的實現。思路仍是比較清晰的,下面上代碼!數組

 

typedef long URL;  // URL簡化爲long
struct Node
{
    URL site;
    int cnt;
    Node(URL s, int c):site(s),cnt(c){}
    bool operator<(const Node& other)const{  //comparator. 注意const
        return this->cnt < other.cnt;
    }
};
class WebCounter
{
private:
    unordered_map<URL, int> counterMap;  //計數map
    unordered_map<URL, int> offsetMap;   //偏移map
    vector<Node> minHeap;
    int size{1};                       //初始大小爲1,idx爲0沒有存內容。兩個用處:1.offset默認爲0,表示沒有記錄;2.便於堆調整操做
    int K;                             //top K
public:
    WebCounter(int k):K(k){
        minHeap.resize(1,{0,0});
    }
    void work(URL url) {
        int curCnt = ++counterMap[url];  //更新計數
        if (offsetMap[url] > 0) {     //offsetMap中存在記錄說明在topK裏面
            int i = offsetMap[url];   //取出偏移idx
            shiftDown(i);             //計數增長了,可能會大於後面的數,因此shift down
        } else if (size <= K) {       //說明堆裏面的元素數目小於k,繼續增長
            minHeap.push_back(Node(url, 1));
            offsetMap[url] = ++size;
            shiftUp(size);           //增長的計數爲1,必定是最小的,因此shift up
        } else if(minHeap[1].cnt < current){   //size已經達到k個了,因此新元素若是大於堆頂元素就把堆頂元素替換。大於堆頂元素的狀況發生在開始已經有計數了,正好等於堆頂元素,+1以後就大於了。
            minHeap[1] = Node(url, curCnt);
            shiftDown(1);       //替換以後是第一個元素,因此只看shift down
        }
        
    }
    void shiftUp(int i) {
        while (i > 1 && minHeap[i] < minHeap[i/2]) {
            swap(minHeap[i], minHeap[i/2]);   //這三行封裝起來更優雅
            offsetMap[minHeap[i/2].site] = i/2;
            offsetMap[minHeap[i].site] = i;
            i>>=1;
        }
    }
    

    void shiftDown(int i) {
        while ((i=i*2)<=size) {
            if (i+1 <= size && minHeap[i+1] < minHeap[i]) {
                ++i;
            }
            if (minHeap[i] < minHeap[i/2]) {
                swap(minHeap[i], minHeap[i/2]);
                offsetMap[minHeap[i/2].site] = i/2;
                offsetMap[minHeap[i].site] = i;
            } else {
                break;
            }
            
        }
    }

};

 

 

代碼中用到兩個調整堆的函數shiftDown和shiftUp,比較簡潔,另附堆排序相關的完整代碼。函數

template <class T>
void swap(T& a, T&b) {
    T t = a;
    a = b;
    b = t;
}

template <class T>
void shiftUp(vector<T> a, int i) {
    while (i > 1 && a[i/2] < a[i]) {
        swap(a[i], a[i/2]);
        i>>=1;
    }
}

template <class T>
void shiftDown(vector<T> a, int size, int i) {
    while ((i=i*2)<=size) {
        if (i+1 <= size &&a[i] < a[i+1]) {
            ++i;
        }
        if (a[i/2] < a[i]) {
            swap(a[i], a[i/2]);
        } else {
            break;
        }
        
    }
}

template <class T>
void makeHeap(vector<T> a, int n) {
    for (int i = n/2; i > 0; i--) {
        shiftDown(a, n, i);
    }
}

template <class T>
void insert(vector<T> a, int& size, T x) {
    a[++size] = x;
    shiftUp(a, size);
}


template <class T>
void del(vector<T> a, int& size, int i) {
    a[i] = a[size--];
    if (i > 0 && a[i/2] < a[i]) {
        shiftUp(a, i);
    } else {
        shiftDown(a, size, i);
    }
}

template <class T>
void heapSort(vector<T> a, int n) {
    makeHeap(a, n);
    for (int i = n; i > 1; i--) {
        swap(a[i], a[1]);
        shiftDown(a, i-1, 1);
    }
}
相關文章
相關標籤/搜索