基础数据结构————堆

代码和注释写完了但是图没画QAQ

简单介绍

  堆的本质是一个完全二叉树,除了最下面一层以外,其他的每层(假设第\(n\)层)都有\(2^n\)个结点。节点存的值每层都是递增或者递减的。递增的话就是大顶堆,递减的话就是小顶堆。那么对于大顶堆来说,每个节点的儿子节点上的值都要小于等于该节点,否则它就不是一个堆。堆需要维护所有元素中的最大(小)值,我们可以用堆来实现优先队列。

算法设计

堆常用的操作有两个:添加元素,删除堆顶元素。不过在此之前我们要先看看对于给定的一个序列,我们如何建堆。

建树

假定我们下面说的堆都是大顶堆。我们可以很清楚的发现,对于一个堆的子树而言,它也必然满足所有节点的儿子结点都小于等于它。也就是说这个子树也是一个堆,那么我们在建树的时候考虑率需要考虑每个节点和它儿子结点的大小,如果不满足父节点最大,就要将左右儿子节点中较大的那个和父节点交换。即保证父节点是最大的,儿子节点哪个小我们并不关心。

这样考虑的话我们可以从堆的下面网上遍历,对于叶子结点而言我们不必考虑。然后我们比较每个结点和它两个(或者一个)儿子结点。如果儿子结点更大,那么将其和父节点交换。但是这样交换的话,儿子结点会换的更小,那么久无法保证下面的结构也满足堆了,所以这里我们还需要继续向下搜儿子结点的儿子结点,直到叶子结点。这样才能保证每次从上面换下来的小一点的结点能到它该去的地方。我们将这个操作写成一个函数,取名ShiftDown()

插入

删除

具体实现

因为写成了一个类,所以就直接在注释里写了

#include<iostream>
using namespace std;

template<typename T>
class Heap
{
public:
    Heap() {}
    Heap(T *a, int num);
    ~Heap() { delete heap; }
    T top() { return heap[0]; }
    void pop();
    void push(const T &x);
    int size()const { return currentSize; }
private:
    void ShiftDown(int start);
    void ShiftUp(int start);
    T *heap;
    int currentSize;
};

template<typename T>
Heap<T>::Heap(T *a, int num)
{
    heap = new T[num];  //内存分配
    for (int i = 0; i < num; i++) heap[i] = a[i];   //获取数据
    currentSize = num;
    int currentPos = (num - 2) / 2; //得到最后一个非叶子结点
    //从这结点开始往上更新,直到叶子结点
    while (currentPos>=0) { ShiftDown(currentPos); currentPos--; }
}

template<typename T>
void Heap<T>::ShiftDown(int start)
{
    int i = start, j = start * 2 + 1;//i是父节点,j是左儿子
    T tmp = heap[i];//用于交换的中间变量,保存的是最初的父节点的值
    while (j < currentSize)
    {
        //如果j<currentSize-1那么保证有右儿子
        if (j + 1 < currentSize&&heap[j] < heap[j + 1]) j++;
        //父节点最大了,就可保证以该节点为根结点的子树,一定满足堆的性质,因为我们是从下往上shiftDown的
        if (heap[i] >= heap[j])break;
        else { heap[i] = heap[j]; i = j; j = 2 * j + 1; }
    }
    //循环退出要么是因为父节点已经最大了,要么是因为比到叶子结点了
    //1.对于前者,我们已经让更大的子节点覆盖到了父节点中,那么heap[i]应该是换上去的较大值,
    //  换完了以后这里应该保存最开始的父节点的较小值,所以我们需要将其赋值为tmp
    //2.对于后者,既然已经到了叶子结点,说明这个叶子结点还比我们当初的父节点的值更大,所以依然同上
    heap[i] = tmp;
}

template<typename T>
void Heap<T>::ShiftUp(int start)
{
    int j = start;//儿子结点,一开始是我们要调整的叶子结点
    int i = (j - 1) / 2; //j的父节点
    T tmp = heap[j];     //tmp保存我们要调整的那个值
    while (j > 0)
    {
        if (heap[i] >= tmp) break;  //如果当前的父节点已经大于等于儿子结点了那么就不需要调整了
        else { heap[j] = heap[i]; j = i; i = (i - 1) / 2; } //否则继续向上延伸
    }
    //最后调整完了以后将我们开始的值存入目的位置
    heap[j] = tmp;
}

template<typename T>
void Heap<T>::pop()
{
    //将最后一个结点替换堆顶,然后从上往下调整
    heap[0] = heap[currentSize - 1];
    currentSize--;
    ShiftDown(0);
}

template<typename T>
void Heap<T>::push(const T &x)
{
    //将插入的数放在最后一个结点的下一个位置,从下往上调整
    heap[currentSize] = x;
    ShiftUp(currentSize);
    currentSize++;
}

int main()
{
    int a[] = { 1,2,3,4,5 };
    Heap<int> heap(a, 5);
    while (heap.size()) {
        cout << heap.top() << endl; heap.pop();
    }

    heap.push(3);
    heap.push(5);
    heap.push(1);
    heap.push(4);
    cout << "在添加3,5,1,4后堆顶元素是:" << heap.top() << endl;
}

猜你喜欢

转载自www.cnblogs.com/destimind/p/10306742.html