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);
    }
}