堆及topk问题

来源:互联网 发布:淘宝哪家店女装时尚 编辑:程序博客网 时间:2024/06/10 23:40

堆的本质

vector+向下调整算法/向上调整算法。
注意:这个二叉树为完全二叉树

向下调整算法:

已知条件:从一个节点开始向下调整,已知这个节点的左右子树已经是大堆或小堆。
所以需要从第一个不是叶子节点的节点开始调整,而这个节点正好是最后一个节点的父节点。
i = (_v.size() - 2)>>1; i即是最后一个不是叶子节点的节点。

以小堆为例:

1.以删除堆顶元素为例来说明向下调整算法:

这里写图片描述

2.以向堆中插入节点来说明堆的向上调整算法:
这里写图片描述

代码如下:

Heap.h

#pragma once#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>#include <assert.h>using namespace std;#include<vector>template<class T>struct Less{    bool operator()(T& left,T& right)    {       return left < right;    }};template<class T>struct Greater{    bool operator()(T& left, T& right)    {       return left > right;    }};template<class T, class Compare = Greater<T> >class Heap{public:    Heap()    {}    Heap(const T* a, size_t n)    {       _v.reserve(n);//可以提高效率       for (size_t i = 0; i < n; ++i)       {           _v.push_back(a[i]);       }       //建堆       for (int i = ((int)_v.size() - 2) >> 1; i >= 0; --i)       {           AjustDown(i);       }    }    void Push(const T& data)    {       _v.push_back(data);       AdjustUp(_v.size() - 1);    }    void Pop()    {       //1。先将堆顶元素与堆的最后一个元素交换       swap(_v[0], _v[_v.size() - 1]);       //2.删除掉最后一个节点       _v.pop_back();       //3.向下调整       AjustDown(0);    }protected:    //向上调整算法    void AdjustUp(int index)    {       int child = index;       int parent = (child - 1) >> 1;       while (child > 0)       {           if (Compare()(_v[parent],_v[child]))           {               break;           }           else           {               swap(_v[child], _v[parent]);               child = parent;               parent = (child - 1) >> 1;           }       }    }    //向下调整    void AjustDown(int root)    {       int parent = root;       int child = parent * 2 + 1;//默认是左孩子       while (child < _v.size())       {           if (child + 1 < _v.size() && Compare()(_v[child + 1], _v[child]))           {               child++;//保证child是最大的孩子节点           }           if (Compare()(_v[child],_v[parent]))           {               swap(_v[child], _v[parent]);//交换父节点和孩子节点               parent = child;               child = parent * 2 + 1;           }           else//不用交换:说明已经是最大的堆了           {               break;           }       }    }private:    vector<T> _v;};

测试用例:

#include"Heap1.h"void Test(){    int array[] = {53,17,78,9,45,65,87,23};    Heap<int,Less<int> > h(array,sizeof(array)/sizeof(int));    //h.Push(80);    h.Pop();}int main(){    Test();    return 0;}

堆的应用:

1.实现优先级队列:

#pragma once#include"Heap1.h"template<class T>class PriorityQueue{public:    PriorityQueue()    {}    void Push(const T& data)    {       hp.Push(data);    }    void Pop()    {       hp.Pop();    }    bool Empty()    {       return hp.Empty();    }    T& Top()    {       return hp.Top();    }    size_t Size()    {       return hp.Size();    }private:    Heap<T> hp;};

测试代码:

#include"Heap1.h"#include"PriorityQueue1.h"void TestHeap(){    int array[] = {53,17,78,9,45,65,87,23};    Heap<int,Greater<int> > h(array,sizeof(array)/sizeof(int));    cout << h.IsMaxHeap() << endl;    //h.Push(80);    h.Pop();    cout << h.IsMaxHeap() << endl;}void TestQueue(){    int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };    size_t len = sizeof(array) / sizeof(int);    PriorityQueue<int> p;    for (size_t index = 0; index < len; ++index)    {       p.Push(array[index]);    }    cout << p.Top() << endl;    p.Pop();    cout << p.Top() << endl;}int main(){    //TestHeap();    TestQueue();    return 0;}

2.topk问题(也是海量数据处理问题):

问题:需要从十亿个数据中找出最大的前k个数。

分析思路:

思路:不能使用排序,因为使用排序的话,内存就必须可以容纳十亿个数据,但是这很明显不可能,所以不能使用排序。

我们可以先从十亿个数据中取出前k(假如为100)个数据,使用这k个数据来建一个小堆,那么堆顶就是这个堆中最小的数据,
然后我们就从十亿减k个数据中取出一个数据赖和堆顶数据来进行比较,如果比堆大,那么就拿这个数据来替换堆顶数据。
然后采用向下调整算法,使之堆顶又是这个堆中最小的数据。依次比较。。替换。。。调整。。。
最终,堆中的前k个数据就是十亿个数据中最大的k个数据。

这里应该可以想到:最大的前k个数据一定会进堆。–>因为每次都是比较堆顶的数据和从剩余的数据进行比较,
而这个堆顶数据又是这个堆中最小的数据。

注意:不能建大堆,如果建大堆,然后从剩余的十亿数据中取数据,拿取到的数据和堆顶数据进行比较,
如果这个数据大于堆顶的数据,那么就交换两者的数据。最终只能找出十亿数据中最大的一个数据。—->不符合。

代码如下:

void Topk(){    int arr[N];    int res[k];    for (size_t i = 0; i < N; ++i)    {       arr[i] = rand() % N;    }    //验证topk问题    /*arr[999] = N + 10;    arr[898] = N + 23;    arr[766] = N + 46;    arr[3] = N + 59;    arr[310] = N + 29;    arr[19] = N + 58;    arr[20] = N + 40;    arr[209] = N + 12;    arr[58] = N + 60;    arr[334] = N + 14;*/    //1.建一个小堆    Heap<int, Less<int> > hp;    for (size_t index = 0; index < k; ++index)    {       res[index] = arr[index];    }    //向下调整    for (int i = (k - 2) / 2; i >= 0; --i)    {       AdjustDown(res, k, i);    }    //找出k个最大的数据    for (size_t j = k; j < N; ++j)    {       if (res[0] < arr[j])       {           res[0] = arr[j];           AdjustDown(res, k, 0);       }    }    for (int j = 0; j < k; ++j)    {       cout << res[j] << " ";    }    cout << endl;}
3.堆排序问题

升序(需要建一个大堆)

将0号下标的数据和最后一个数据交换,堆中数据个数减1,然后再向下调整,使得0号下标的数据是最大值,然后继续和堆中最后一个数据交换,继续将堆中元素个数减一。

降序(需要建一个小堆)

同上,将0号下标的数据和堆中最后一个数据进行交换,然后将堆的元素个数减一,再向下调整堆中数据,使得堆中的0号下标的数据称为现在堆中最小的数据。

代码如下:

//向下调整算法void AdjustDown(int* heap, int n, int root){    assert(heap);    int parent = root;    int child = parent*2+1;    while (child < n)    {        if (child+1<n && heap[child+1] > heap[child])        {            ++child;        }        if (heap[child] > heap[parent])        {            swap(heap[child], heap[parent]);            parent = child;            child = parent*2+1;        }        else        {            break;        }    }}

//堆排序

void HeapSort(int* a, size_t n){    assert(a);    // 建堆:时间复杂度 O(N*lgN)    //从最后一个非叶子节点处开始向下调整    for(int i = (n-2)/2; i >=0; --i)    {        AdjustDown(a, n, i);    }    // 选择排序    int end = n-1;    while (end > 0)    {        swap(a[0], a[end]);        AdjustDown(a, end, 0);        --end;    }}