小根堆(Heap)的详细实现

堆的介绍

Heap是一种数据结构具有以下的特点:

1)完全二叉树
2)heap中存储的值是偏序

Min-heap: 父节点的值小于或等于子节点的值

Max-heap: 父节点的值大于或等于子节点的值

图1

堆的存储

一般都用数组来表示堆,i结点的父结点下标就为(i–1)/2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。

图2

由于堆存储在下标从0开始计数的数组中,因此,在堆中给定下标为i的结点时:

(1)如果i=0,结点i是根结点,无父结点;否则结点i的父结点为结点(i-1)/2;

(2)如果2i+1>n-1,则结点i无左子女;否则结点i的左子女为结点2i+1;

(3)如果2i+2>n-1,则结点i无右子女;否则结点i的右子女为结点2i+2。

堆的操作:小根堆插入元素

插入一个元素:新元素被加入到heap的末尾,然后更新树以恢复堆的次序。

每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,现在的任务是将这个新数据插入到这个有序数据中——这就类似于直接插入排序中将一个数据并入到有序区间中。需要从下网上,与父节点的关键码进行比较,对调。

图3

堆的操作:删除小根堆堆的最小元素

按定义,堆中每次都删除第0个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,堆的元素个数-1,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。

图4

堆的操作:创建堆

对于叶子节点,不用调整次序,根据满二叉树的性质,叶子节点比内部节点的个数多1.所以i=n/2 -1 ,不用从n开始。就是从最后一个有叶子结点的结点开始。

堆排序

如果从小到大排序,创建大堆建好之后堆中第0个数据是堆中最大的数据。取出这个数据,放在数组最后一个元素上,将当前元素数-1,再执行下堆的删除操作。这样堆中第0个数据又是堆中最大的数据,重复上述步骤直至堆中只有一个数据时,数组元素就已经有序。

小根堆的实现

#include <iostream>
using namespace std;

const int DefaultSize = 50;

template<typename T>
class MinHeap
{
public:
    //构造函数:建立空堆
    MinHeap(int sz=DefaultSize)
    {
        maxHeapSize = (DefaultSize < sz) ? sz : DefaultSize;
        heap = new T[maxHeapSize];
        currentSize = 0;
    }

    //构造函数通过一个数组建立堆
    MinHeap(T arr[],int n)
    {
        maxHeapSize = (DefaultSize < n) ? n : DefaultSize;
        heap = new T[maxHeapSize];
        for(int i=0;i<n;i++)
        {
            heap[i] = arr[i];
        }
        currentSize = n;
        int currentPos = (currentSize - 2) / 2; //找最初调整位置:最后分支结点
        while (currentPos>=0)   //自底向上逐步扩大形成堆
        {
            siftDowm(currentPos, currentSize - 1);  //局部自上向下下滑调整
            currentPos--;   //再向前换一个分支结点
        }
    }

    //将x插入到最小堆中
    bool Insert(const T& x)
    {
        if(currentSize==maxHeapSize)
        {
            cout << "Heap Full!" << endl;
            return false;
        }
        heap[currentSize] = x;  //插入
        siftUp(currentSize);    //向上调整
        currentSize++;  //堆计数+1
        return true;
    }

    bool RemoveMin(T& x)
    {
        if(!currentSize)
        {
            cout << "Heap Empty!" << endl;
            return false;
        }
        x = heap[0];    //返回最小元素
        heap[0] = heap[currentSize - 1];    //最后元素填补到根结点
        currentSize--;
        siftDowm(0, currentSize - 1);   //自上向下调整为堆
        return true;
    }

    void output()
    {
        for(int i=0;i<currentSize;i++)
        {
            cout << heap[i] << " ";
        }
        cout << endl;
    }

protected:

    //最小堆的下滑调整算法
    void siftDowm(int start, int end)   //从start到end下滑调整成为最小堆
    {
        int cur = start;
        int min_child = 2 * cur + 1;    //先记max_child是cur的左子女位置
        T temp = heap[cur];
        while (min_child <=end)
        {
            if (min_child<end&&heap[min_child]>heap[min_child + 1]) //找到左右孩子中最小的一个
                min_child++;

            if(temp<=heap[min_child])
                break;
            else
            {
                heap[cur] = heap[min_child];
                cur = min_child;
                min_child = 2 * min_child + 1;
            }
        }
        heap[cur] = temp;
    }

    //最小堆的上滑调整算法
    void siftUp(int start)  //从start到0上滑调整成为最小堆
    {
        int cur = start;
        int parent = (cur - 1) / 2;
        T temp = heap[cur];
        while (cur>0)
        {
            if(heap[parent]<=temp)
                break;
            else
            {
                heap[cur] = heap[parent];
                cur = parent;
                parent = (parent - 1) / 2;
            }
        }
        heap[cur] = temp;   //回放temp中暂存的元素
    }
private:    //存放最小堆中元素的数组
    T* heap;
    int currentSize;    //最小堆中当前元素个数
    int maxHeapSize;    //最小堆最多允许元素个数
};

//------------------------主函数-------------------------
int main(int argc, char* argv[])
{
    MinHeap<int> h;
    h.Insert(8);
    h.Insert(5);
    h.Insert(7);
    h.Insert(9);
    h.Insert(6);
    h.Insert(12);
    h.Insert(15);
    h.output();

    int out;
    cout << static_cast<int> (h.RemoveMin(out)) << endl;
    h.output();

    int arr[10] = { 15,19,13,12,18,14,10,17,20,11 };
    MinHeap<int> h1(arr,10);
    h1.output();
}

转载于:https://www.cnblogs.com/WindSun/p/11444446.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是将上面的大根堆实现改成小根堆的代码: ```cpp template <typename T> class PriorityQueue { public: // 构造函数 PriorityQueue() { // 初始化堆大小 capacity = 10; // 初始化堆数组 heap = new T[capacity]; // 初始化堆大小为0 size = 0; } // 析构函数 ~PriorityQueue() { delete[] heap; } // 入队 void push(const T& value) { // 如果堆已满,则进行扩容 if (size == capacity) { resize(); } // 将元素插入堆尾 heap[size++] = value; // 上浮操作 siftUp(size - 1); } // 出队 void pop() { // 如果堆为空,则直接返回 if (empty()) { return; } // 将堆尾元素移到堆顶 heap[0] = heap[--size]; // 下沉操作 siftDown(0); } // 获取队列大小 int size() const { return size; } // 判断队列是否为空 bool empty() const { return size == 0; } // 获取队头元素 T top() const { return heap[0]; } private: // 堆数组 T* heap; // 堆的大小 int size; // 堆的容量 int capacity; // 上浮操作 void siftUp(int index) { // 父节点的索引 int parent = (index - 1) / 2; // 当前节点比父节点小,则交换它们 while (index > 0 && heap[index] < heap[parent]) { std::swap(heap[index], heap[parent]); index = parent; parent = (index - 1) / 2; } } // 下沉操作 void siftDown(int index) { while (true) { // 左子节点的索引 int leftChild = index * 2 + 1; // 右子节点的索引 int rightChild = index * 2 + 2; // 用于比较的最小值索引 int minIndex = index; // 如果左子节点比当前节点小,则更新最小值索引 if (leftChild < size && heap[leftChild] < heap[minIndex]) { minIndex = leftChild; } // 如果右子节点比当前节点小,则更新最小值索引 if (rightChild < size && heap[rightChild] < heap[minIndex]) { minIndex = rightChild; } // 如果最小值索引不是当前节点,则交换它们,并继续下沉 if (minIndex != index) { std::swap(heap[index], heap[minIndex]); index = minIndex; } else { // 否则,已经满足堆的性质,退出循环 break; } } } // 扩容操作 void resize() { // 新容量为原来的两倍 capacity *= 2; // 新建一个更大的数组 T* newHeap = new T[capacity]; // 将原有元素复制到新数组中 for (int i = 0; i < size; i++) { newHeap[i] = heap[i]; } // 删除原有数组 delete[] heap; // 更新指针 heap = newHeap; } }; ``` 在这个实现中,我们将比较运算符由“>”改为“<”,同时将最大值的概念改为最小值的概念。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值