zoukankan      html  css  js  c++  java
  • 【数据结构】MinHeap 的实现

    面试一个大厂,让我实现最小堆的的两个功能:

    • insert:插入元素,保持最小堆特性
    • pop:推出第一个最小的元素

    首先,堆是一种二叉树结构,但经过了排序,最小的元素永远是根元素,且对所有子树都成立。

    但是对于每一个树,其左右子节点的大小则并不一定是排序的,也就是说左子节点和右子节点谁大谁小不一定。且左右子节点的值可能等于根节点。

    根据 [1] 和观摩其他大神写的最小堆,实现最小堆不需要显式的实现 Node,因为堆总是向左填充(维持排序),其数据结构一直被完好的维持。若使用显式节点实现会破坏结构也会影响性能。

    实现如下

    MInheapSource.h:

     1 #pragma once
     2 #ifndef MINHEAPSOURCE_H
     3 #define MINHEAPSOURCE_H
     4 
     5 template<class T> // 可使用多种数据类型作为模板
     6 class MinHeap {
     7 public:
     8     MinHeap(int maxSize);   // 构造空最小堆
     9     MinHeap(T* arr, int size);   // 从数组中构造堆
    10     MinHeap(MinHeap& m);    // 从其他最小堆构造
    11     ~MinHeap();
    12 
    13     // 节点位置计算
    14     inline int left(int index) { return 2 * index + 1; }
    15     inline int right(int index) { return 2 * index + 2; }
    16     inline int parent(int index) { return (index - 1) / 2; }
    17 
    18     // 返回目前MinHeap大小
    19     inline int size(){ return _currentSize + 1 ;}
    20     bool isEmpty();
    21     bool isFull();
    22     void insert(const T& t);
    23     bool pop(T& result);    // 推出第一个最小元素
    24 
    25     T* getMinHeap();
    26     // 显式数组
    27     void print();
    28 private:
    29     T* _minheap = nullptr;  // 堆本体
    30     int _currentSize; // 目前大小
    31     int _maxSize;   // 最大容量
    32     void shiftDown(const int index, const int end); // 向下调整堆以维持堆的层级
    33     void shiftUp(int index); // 向上调整堆以维持堆的层级
    34 };
    35 
    36 #endif

    MInheapSource.cpp:

      1 #include "MinHeapSource.h"
      2 #include <iostream>
      3 
      4 template<class T>
      5 MinHeap<T>::MinHeap(int maxSize) {
      6     _maxSize = maxSize;
      7     _minheap = new T[_maxSize];
      8     if (_minheap == nullptr) {
      9         std::cerr << "MinHeap Memory allocation failed" << std::endl;
     10         exit(-1);
     11     }
     12     _currentSize = 0;
     13 }
     14 
     15 template<class T>
     16 MinHeap<T>::MinHeap(T* arr, int size) {
     17     _maxSize = size;
     18     _minheap = new T[_maxSize];
     19     if (_minheap == nullptr) {
     20         std::cerr << "MinHeap Memory allocation failed" << std::endl;
     21         exit(-1);
     22     }
     23     // 逐个赋值
     24     _currentSize = size;
     25     for (int i = 0; i < _currentSize; ++i) {
     26         _minheap[i] = arr[i];
     27     }
     28 
     29     // 利用下滑算法形成最小堆
     30     // NOTE: 以为curPos指向的时下标,所以要减2
     31     int curPos = (_currentSize - 2) / 2;
     32     while (curPos >= 0) {
     33         shiftDown(curPos, _currentSize - 1);
     34         --curPos;
     35     }
     36 }
     37 
     38 template<class T>
     39 MinHeap<T>::MinHeap(MinHeap& m) {
     40     _minheap = new T[_maxSize]; // 申请新的空间
     41     if (_minheap == nullptr) {
     42         std::cerr << "MinHeap Memory allocation failed" << std::endl;
     43             exit(-1);
     44     }
     45     for (int i = 0; i < _maxSize; ++i)
     46         *_minheap[i] = *m._minheap[i];
     47     _currentSize = m._currentSize;
     48     _maxSize = m._maxSize;
     49 }
     50 
     51 template<class T>
     52 MinHeap<T>::~MinHeap() {
     53     if(_minheap != nullptr)
     54         delete []_minheap;
     55 }
     56 
     57 template<class T>
     58 bool MinHeap<T>::isEmpty() {
     59     return _currentSize == 0;
     60 }
     61 
     62 template<class T>
     63 bool MinHeap<T>::isFull() {
     64     return _currentSize == _maxSize;
     65 }
     66 
     67 template<class T>
     68 T* MinHeap<T>::getMinHeap() {
     69     return _minheap;
     70 }
     71 
     72 template<class T>
     73 void MinHeap<T>::insert(const T& t) {
     74     if (isFull()) {
     75         std::cerr << "the MinHeap is full, can't insert more element" << std::endl;
     76         return;
     77     }
     78     _minheap[_currentSize] = t;
     79     shiftUp(_currentSize);    // 自下而上调整最小堆
     80     ++_currentSize;
     81 }
     82 
     83 template<class T>
     84 bool MinHeap<T>::pop(T& result) {
     85     if (_currentSize > _maxSize) {
     86         std::cerr << "the MinHeap is empty, no element to pop" << std::endl;
     87         return false;
     88     }
     89     result = _minheap[0];
     90     // 将当前的第一个元素替换为最后一个元素,然后自上而下调整为最小堆
     91     _minheap[0] = _minheap[_currentSize - 1];
     92     _currentSize--;
     93     shiftDown(0, _currentSize - 1);  // 自上而下调整为最小堆
     94     return true;
     95 }
     96 
     97 // 自下而上调整最小堆(修复 insert)
     98 template<class T>
     99 void MinHeap<T>::shiftUp(int index) {
    100     if (isEmpty()) return;
    101 
    102     int current = index;
    103     int parentC = parent(current);
    104     // 若抵达顶点,返回
    105     if (current <= 0)    return;
    106     // 若父节点大于当前节点时,递归交换
    107     if (_minheap[parentC] > _minheap[current]) {
    108         int temp = _minheap[parentC];
    109         _minheap[parentC] = _minheap[current];
    110         _minheap[current] = temp;
    111         shiftUp(parentC);
    112     }
    113 }
    114 
    115 // 自上而下调整最小堆(修复 pop)
    116 template<class T>
    117 void MinHeap<T>::shiftDown(const int index, const int end) {
    118     if (isEmpty()) return;
    119 
    120     int current = index;    // 保留当前索引
    121     int leftC = left(current);
    122 
    123     // 若左子节点或右子节点超出范围,返回
    124     if (leftC > end)
    125         return;
    126 
    127     // 比较左右子节点(主要和左子节点作比较)
    128     if (leftC < end && _minheap[leftC] > _minheap[leftC + 1]) 
    129         ++leftC;
    130 
    131     // 若当现结点大于左子节点时,递归交换
    132     if (_minheap[current] > _minheap[leftC]) {
    133         int temp = _minheap[current];
    134         _minheap[index] = _minheap[leftC];
    135         _minheap[leftC] = temp;
    136         shiftDown(leftC, end);
    137     }
    138 }
    139 
    140 template<class T>
    141 void MinHeap<T>::print() {
    142     for (int i = 0; i < _currentSize; ++i) {
    143         std::cout << _minheap[i] << "	";
    144     }
    145     std::cout << std::endl;
    146 }

    主程序:

     1 // MinHeap.cpp : This file contains the 'main' function. Program execution begins and ends there.
     2 //
     3 #include "MinHeapSource.h"
     4 #include "MinHeapSource.cpp"    // include source file because the template class has to include definition explict in the main
     5 #include <iostream>
     6 
     7 using namespace std;
     8 
     9 int main() {
    10 
    11     int a[] = { 9, 6, 5, 4, 3, 2, 1 };
    12     MinHeap<int> heap(a, 7);
    13 
    14     cout << "Init the heap" << endl;
    15     heap.print();
    16 
    17     int p;
    18     heap.pop(p);
    19     cout << "Pop smallest element: " << p << endl;
    20     heap.print();
    21 
    22     cout << "Insert 10" << endl;
    23     heap.insert(10);
    24     heap.print();
    25 
    26     return 0;
    27 }
    28 
    29 /*
    30 运行结果:
    31 Init the heap
    32 1       3       2       4       6       9       5
    33 Pop smallest element: 1
    34 2       3       5       4       6       9
    35 Insert 10
    36 2       3       5       4       6       9       10
    37 */

    注:

    1)为什么要下滑和上滑(ShiftDown & ShiftUp)?

      这主要是因为堆结构在插入(insert)和删除最小元素(pop)操作时会破坏堆的结构,所以需要下滑和上滑两个操作。下滑用于修复插入后堆元素的破坏,上滑用于修复删除最小元素时对堆元素的破坏。[3]

    参考资料:

    [1] 实现指引:https://www.coder.work/article/2752123

    [2] 实现参考:https://blog.csdn.net/qq_37623612/article/details/88696924

    [3] 为什么要ShiftDown & ShiftUp:https://mingshan.fun/2019/05/14/heap/

  • 相关阅读:
    JAVA-初步认识-第五章-数组-常见操作-最值2
    JAVA-初步认识-第五章-数组-常见操作-最值
    JAVA-初步认识-第五章-数组-常见操作-遍历
    JAVA-初步认识-第五章-数组-第二种定义格式
    JAVA-初步认识-第四章-数组-常见问题
    JAVA-初步认识-第四章-内存图解
    日期加1程序
    发生了COMException 异常来自 HRESULT:0x80040228
    设置窗体的可见性无效
    DotNetBar RibbonControl 控件动态添加项
  • 原文地址:https://www.cnblogs.com/thdt/p/13619749.html
Copyright © 2011-2022 走看看