堆是结点间具有层序关系的完全二叉树。
最大堆(maxheap):每个父节点的都大于孩子节点。在最大堆中,根的元素最大。
最小堆(minheap):每个父节点的都小于孩子节点。在最小堆中,根的元素最小。

堆实现了插入、删除的操作,但在插入删除之后必须对树进行更新,维护堆排序。我主要讲插入和删除操作。

首先我们要建堆,现在有一组数:10, 11, 13, 12, 16, 18, 15, 17, 14, 19   

我们把这组数构成如下图所示的树状结构。现在我们要用向下调整算法使它成为一个小堆(或大堆)。这里我们以大堆为例。


向下调整算法的思想是:从第一个非叶子节点开始,判断它的左右孩子是否比自己大,若比自己大,就交换。再依次向下调整,最终我们可以得到大堆。


Push:在一个大堆里插入一个数据的时候,我们利用向上调整算法(思路与向下调整算法相似),不同的是向上调整算法是根据孩子的下标求出父亲的下标,不断调整,使它最终还是一个大堆。例如,在上图的大堆中插入20。


Pop:这里是先把第一个数据与最后一个交换,再把最后一个数据删除。然后向下调整,使它依旧还是大堆。例如:我们删除19。


下面是实现的代码:
为了让代码复用,这里我用到了仿函数,在建堆的时候,根据你传的参数来建大堆还是小堆。

#pragma once
#include<iostream>
#include<vector>
using namespace std;
template<class T>
struct Less
{
     bool operator()(const T& l, const T& r)
     {
          return l < r;
     }
};
template<class T>
struct Greater
{
     bool operator()(const T& l, const T& r)
     {
          return l > r;
     }
};
template<class T, class Compare>//编写与类型无关的代码
class Heap
{
public:
     Heap()
     {}
     Heap(T* a, size_t n)
     {
          _a.reserve(n);  //reserve()只开空间   resize()开空间并初始化
          for (size_t i = 0; i < n; i++)
          {
              _a.push_back(a[i]);
          }
          //建堆
          for (int i = (_a.size() - 2) / 2; i >= 0; i--)
          {
              AdjustDown(i);//从第一个非叶子节点开始
          }
     }
     void Push(const T& x)
     {
          _a.push_back(x);
          AdjustUp(_a.size() - 1);
     }
     void Pop()
     {
          swap(_a[0], _a[_a.size() - 1]);
          _a.pop_back();
          AdjustDown(0);
     }
     bool Empty()
     {
          return _a.empty();
     }
     size_t Size()
     {
          return _a.size();
     }
     const T& Top()
     {
          return _a[0];
     }
     void Print()
     {
          for (size_t i = 0; i < _a.size(); i++)
          {
              cout << _a[i] << " ";
          }
     }
protected:
     void AdjustDown(size_t root)//向下调整算法
     {
          Compare com;
          size_t  parent = root;
          size_t  child = parent * 2 + 1;
          while (child < _a.size())
          {
              if (child + 1 < _a.size() && com(_a[child + 1], _a[child]))
              if ((child + 1 < _a.size()) && (_a[child + 1] > _a[child]))
              {
                   child++;   //只需得到大的(小的)孩子的下标即可
              }
              if (com(_a[child], _a[parent]))
              if (_a[child] > _a[parent])
              {
                   swap(_a[child], _a[parent]);
                   parent = child;        //子问题
                   child = parent * 2 + 1;
              }
              else
              {
                   break;
              }
          }
     }
     void AdjustUp(size_t child)//向上调整算法
     {
          Compare com;
          size_t parent = (child - 1) / 2;
          while (child > 0)
          {
              if (com(_a[child], _a[parent]))
              if (_a[child] > _a[parent])
              {
                   swap(_a[child], _a[parent]);
                   child = parent;
                   parent = (child - 1) / 2;
              }
              else
              {
                   break;
              }
          }
     }
private:
     //T* _a;
     //size_t _size;
     //size_t _capacity;
     vector<T> _a;
};
//测试
//void TestHeap()
//{
//   int a[] = { 10, 11, 13, 12, 16, 18, 15, 17, 14, 19 };
//   //Heap<int> hp();
//   //Heap<int> hp1(a, sizeof(a) / sizeof(int));
//   Heap<int,Less<int>> hp1(a, sizeof(a) / sizeof(int));
//   Heap<int, Greater<int>> hp1(a, sizeof(a) / sizeof(int));
//
//   hp1.Push(20);
//   hp1.Push(14);
//   hp1.Pop();
//   hp1.Print();
//}
我们再讲一下堆的应用TopK问题以及堆排序
堆在实现优先级队列以及对一维数组元素排序方面发挥着重要作用。

TopK问题:从100w个数中找出最大的前K个数。
求最大的前K个数,我们要建小堆,让最大的前K个数进堆。这样就可以找出最大的前K个数。

这里我用的是面向过程的方法写的
//TopK问题
//找最大的前K个,建小堆
void GetTopK(int* a, size_t n, size_t k)
{
     assert(a);
     //建小堆
     int* heap = new int[k];
     for (size_t i = 0; i < k; i++)//把前K个数入堆
     {
          heap[i] = a[i];
     }
     for (int i = (k - 2) / 2; i >= 0; i--)//利用前K个数建小堆
     {
          AdjustDown(heap, k, i);
     }
     for (size_t i = 0; 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] << " ";
     }
}

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

堆排序(用到上面TopK问题的向下调整算法,所以我们排降序(建小堆))

思路:把堆里的第一个元素与最后一个元素交换,把最后一个不看做堆里的,然后再把剩下的元素进行向下调整,以此类推......

//排降序 (建小堆)
//把第一个数放在最后一个数的位置
void HeapSort(int* a, size_t n)
{
     for (int i = (n - 2) / 2; i >= 0; i--)
     {
          AdjustDown(a, n, i);
     }
     int end = n - 1;
     while (end)
     {
          swap(a[0], a[end]);
          --end;
          AdjustDown(a, end, 0);
     }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值