堆的基本操作及堆排序

堆简介
堆是一种特殊的数据结构,它通常是一个可以看做一棵树的数组对象。
堆既然是一个数组,它便通过下标以二叉树的方式来保存。
以下图二叉树模型为例
红色数字代表节点的值,黑色数字代表节点的下标。
这里写图片描述
这是一颗乱序的完全二叉树,可以观察到
父节点下标 = (左孩子下标-1)/2
左孩子下标 = (父节点下标*2)+1
根据以上规律便可以很好地存储一棵树了。
堆有两种存储结构:
最大堆:每个父节点的值都大于孩子节点。
最小堆:每个父节点的值都小于孩子节点。
基本操作
(一)建堆

    Heap(const T* a,int n)
    {
        _a.reserve(n);
        for(int i = 0;i < n;i++)//给定数值全部压入堆
        {
            _a.push_back(a[i]);
        }
        for(int i = _a.size()-2>>1;i >= 0;--i)//找到最后一个叶子节点的父亲节点
        {
            _AdjustDown(i);
        }
    }

(二)向下调整:
步骤:
(1)假设该节点的下标为parent;
(2)找到该节点的左孩子child=parent*2+1;
(3)如果右孩子存在,找到左孩子和右孩子中的较小者。
(4)比较父节点是否小于其左右孩子中的较小者,如果父节点小于较小者调整结束,否则将父节点的元素与较小孩子交换,此时有可能导致其子树不满足堆的性质,继续调整其子树直到满足堆的性质。

void _AdjustDown(int root)
{
    int parent = root;
    int child = parent*2 +1;
    while(child<_a.size())
    {
        if(child+1<_a.size()&&_a[child+1]<_a[child])
        {
            child=child+1;
        }
        if(_a[child]<_a[parent])
        {
            swap(_a[child],_a[parent]);
            child = parent;
            child = parent*2 + 1;
        }
        else
        {
            return;
        }
    }
}

(三)向上调整法:

void _AdjustUp(int child)
{
    int parent = (child - 1)>>1;
    while(child > 0)
    {

        if(_a[child]<_a[parent])
        {
            swap(_a[child],_a[parent]);
            child = parent;
            parent = (child - 1)>>1;
        }
        else
        {
            break;
        }
    }
}

(四)堆的插入:
每次都插入到数组的最后,插入新元素后可能破坏堆的结构,然后针对这个节点进行一次向上调整

void Push(const T& x)
{
    _a.push_back(x);
    _AdjustUp(_a.size()-1);
}

(五)堆的删除:
(1)将堆中最后一个元素替代堆顶元素
(2)将堆中元素个数减少一个,相当于将堆中最后一个元素删除
(3)此时堆结构可能被破坏,再向下调整使其满足堆的性质。

void Pop()//代替删除法
{
    assert(!_a.empty());
    swap(_a[0],_a[_a.size()-1]);//先将头与数组最后一个值相交换
    _a.pop_back();
    _AdjustDown(0);//对头结点进行向下调整法
}

完整代码:

template<class T>
//仿函数,代码复用
struct less
{
    bool operator() (const T&l,const T&r) const
    {
        return l < r;
    }
};
template<class T>
struct greater
{
    bool operator() (const T&l,const T&r) const
    {
        return l > r;
    }
};
template<class T,class Compare = greater<T>>//默认建大堆
class Heap
{
public:
    Heap()
    {}
    Heap(const T* a,int n)
    {
        _a.reserve(n);
        for(int i = 0;i < n;i++)//给定数值全部压入
        {
            _a.push_back(a[i]);
        }
        for(int i = _a.size()-2>>1;i >= 0;--i)//找到最后一个节点的父亲
        {
            _AdjustDown(i);
        }
    }
    void Push(const T& x)
    {
        _a.push_back(x);
        _AdjustUp(_a.size()-1);
    }
    void Pop()//代替删除法
    {
        assert(!_a.empty());
        swap(_a[0],_a[_a.size()-1]);//先将头与数组最后一个值相交换
        _a.pop_back();
        _AdjustDown(0);//对头结点进行向下调整法
    }
    const T& Top() const
    {
        assert(!_a.empty());
        return _a[0];
    }
    size_t Size()
    {
        return _a.size();
    }
    bool Empty()
    {
        return _a.empty();
    }
    void IsHeap()
    {
        return _IsHeap(int root);
    }
    void AdjustDown(int root)
    {
        return _Adjustdown(int root);
    }
    void AdjustUp(int child)
    {
        return _AdjustUp(int child);
    }
    void Prinrt()
    {
        for(int i = 0;i < _a.size();i++)
        {
            cout<<_a[i]<<" ";
        }
        cout<<endl;
    }
protected:
    bool _IsHeap(int root)//判断是否是小堆
    {
        for(size_t = 0;i <= (a.size()-2)/2;i++)
        {
            if(_a[i] < _a[i*2+1] || ((i*2+2)<_a.size()&&_a[i] < _a[i*2+2]))
            {
                return false;
            }
        }
        return true;
    }

    void _AdjustUp(int child)
    {
        Compare Com;
        int parent = (child - 1)>>1;
        while(child > 0)
        {

            if(Com(_a[parent],_a[child]))
            {
                swap(_a[child],_a[parent]);
                child = parent;
                parent = (child - 1)>>1;
            }
            else
            {
                break;
            }
        }
    }
    void _AdjustDown(int root)
    { 
        Compare Com;
        int parent = root;
        int child = parent*2 + 1;
        while(child < _a.size())
        {
            if(child+1<_a.size()&&Com(_a[child],_a[child+1]))
                child++;
            if(Com(_a[parent],_a[child]))
            {
                swap(_a[child],_a[parent]);
                //让孩子节点作为父亲节点,判断是否存在子树
                parent = child;
                child = parent*2 + 1;

            }
            else
                break;
        }
    }
private:
    vector<T> _a;
};
int main()
{
    int a[]={10,11,12,13,14,15,16,17,18,19};
    Heap<int,less<int>> h1(a,sizeof(a)/sizeof(a[0]));//这里是建小堆
    h1.Prinrt();
    h1.Push(21);
    h1.Prinrt();
    h1.Pop();
    h1.Prinrt();
}

运行结果:
这里写图片描述
堆排序
这里写图片描述

//堆排序(以调整为大堆为例)
void AdjustDown(int* a,int i,int end)
{
    int parent = i;
    int child = i*2+1;
    while(child<=end)
    {
        if((child+1<end)&&(a[child]<a[child+1]))
        {
            child++;
        }
        if(a[parent]<a[child])                                                                                                                                                                                                                                                                                                                                                                                                                              
        {
            swap(a[parent],a[child]);
            parent = child;
            child = parent*2+1;
        }
        else
        {
            break;
        }
    }
}
void HeapSort(int *a,int n)
{
    for(int i = (n-2)/2; i>=0; i--)//从最后一个叶子节点的父节点开始调整
    {
        AdjustDown(a,i,n-1);
    }
    int end = n-1;依次上调
}
int main()
{
    int a[] = {10,11,13,7,16,18,15,12,14,9};
    HeapSort(a,sizeof(a)/sizeof(a[0]));
    for(size_t i = 0;i < sizeof(a)/sizeof(a[0]);i++)
    {
        cout<<a[i]<<" ";
    }
    cout<<endl;
}

这里写图片描述
TopK问题
问题是统计中很常见的问题:例如老师要从全校10000名学生中挑选10个最高的成绩。
思路:
(1)创建一个k大小的小堆,将数组中前K个元素push进去,进行调堆。
(2)遍历剩余的所有数据,当遇到的数据大于堆顶元素时,让此数据替换堆顶元素,再进行向下调整。

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;
        }
        else
        {
            break;
        }
    }
}
const size_t N = 10000;
const size_t K = 10;
void TopK()
{
    int a[N] = {0};
    for(size_t i = 0;i < N;++i)
    {
        a[i] = rand()%N;
    }
    int heap[K] = {0};
    for(size_t i = 0;i < K;++i)
    {
        heap[i] = a[i];
    }
    //建堆
    for(int i = (K-2)/2;i>=0;--i)
    {
        AdjustDown(heap,K,i);
    }
    for(size_t i = K;i < N;++i)
    {
        if(a[i] > heap[0])
        {
            heap[0] = a[i];
            AdjustDown(heap,K,0);
        }
    }
    for(size_t i = 0;i < K;++i)
    {
        cout<<heap[i]<<" ";
    }
}
int main()
{
    TopK();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值