структура данных двоичной кучи — это объект массива, который мы можем рассматривать как почти полное двоичное дерево, как показано на следующем рисунке.

обратите внимание, что каждый узел в дереве соответствует элементу массива

массив A, который представляет кучу, представляет собой объект с двумя атрибутами A.length, который представляет размер массива, и A.heapsize, который представляет, сколько элементов в куче хранится в массиве A.

то есть A[0….A.length] может содержать числа

только элементы в A[0…A.heapSize] являются допустимыми элементами кучи, корень дерева всегда в A[0]

учитывая индекс i узла, мы можем легко вычислить индексы левого и правого потомков

Левый ребенок (я) = 2 * (я + 1)

Правый ребенок = 2(i+1)+1

Родитель(я) = int((я+1)/2)

существует два вида кучи max-heaps и min-heaps, и в обоих типах значения удовлетворяют свойству кучи

для свойства max-heap является то, что для каждого узла i, отличного от корня A[Parent(i)≥A[i]]

это означает, что значение узла не больше значения родителя, поэтому самый большой элемент в максимальной куче - это корень, поддерево с корнем в узле содержит значения, не превышающие значения, содержащиеся в самом узле.

Минимальная куча организована противоположным образом: свойство минимальной кучи состоит в том, что для каждого узла i

А[Родительский[я]‹А[я]]

поэтому наименьший элемент хранится в корне

рассматривая кучу как дерево, мы определяем высоту узла в куче как количество ребер на самом длинном простом нисходящем пути от узла к листу, и мы определяем высоту кучи как высоту ее корня.

поскольку куча из n элементов основана на полном двоичном дереве, его высота (logn)

Поддержание свойства кучи

чтобы сохранить свойство максимальной кучи, мы вызываем процедуру Max-Heapify с входными данными в виде массива A и индексом i в массиве, когда он вызывается. Max-Heapify предполагает, что двоичные деревья с корнями слева (i) и справа (i) являются максимальными кучами

но это A [i] может быть меньше, чем его дочерние элементы, что нарушает свойство максимальной кучи, поэтому, когда мы вызываем Max-Heapify, A [i] плавает вниз в максимальной куче, так что поддерево, сгнившее в индексе i, подчиняется свойству максимальной кучи.

void Max_Heapify(int index){
        int left = leftChild(index);
        int right = RightChild(index);
        int largest = index;
        
        if(left<heap.size()&&heap[left]>heap[index]) largest = left;
        else largest = index;

        if(right<heap.size()&&heap[right]>heap[largest]) largest = right;

        if(largest != index){
            int tmp = heap[index];
            heap[index] = heap[largest];
            heap[largest] = tmp;
            Max_Heapify(largest);
        }
    }

Создание кучи

мы можем использовать процедуру Max-Heapify снизу вверх для преобразования массива размера n в максимальную кучу

обратите внимание, что куча размера n все элементы в подмассиве A[(n/2+1)…..n] являются листьями дерева, поэтому каждый из них представляет собой кучу из 1 элемента

доказательство

  • Итак, в основном в представлении кучи LEFT(i) относится к индексу is левого дочернего элемента. Мы хотим показать, что индекс ⌊n/2⌋+1 является листом, а не промежуточным узлом, что можно доказать, если мы сможем показать, что индекс левого дочернего элемента больше, чем количество элементов в куче. С другой стороны, LEFT(⌊n/2⌋+1)=2(⌊n/2 ⌋+1)=2⌊n/2⌋+2, и, удалив эти скобки вокруг n/2, мы можем показать, что оно больше 2( n/2−1)+2=n.

алгоритм

void BuildMaxHeap(){
        for(int i = (heap.size()/2)-1;i>=0;i--){
            Max_Heapify(i);
        }
 }

доказательство правильности

Чтобы показать, почему BuildMaxHeap работает правильно, мы используем следующий цикл
инвариант
В начале каждой итерации цикла for каждый узел i +1;
i+2,i+3 … n является корнем максимальной кучи

этап инициализации: до первой итерации цикла i = (n/2)-1 каждый узел n/2 , n/2 +1 ,n/2 +2 …… n-1 листовой узел и, следовательно, корень тривиальной максимальной кучи

поддерживать инвариант цикла: чтобы увидеть, что каждая итерация поддерживает инвариант цикла, обратите внимание, что все дочерние элементы узла i имеют номера выше, чем i , поэтому по инварианту цикла они оба являются корнями максимальной кучи, и это условие для использования MaxHeapify, чтобы сделать узел i максимальным корнем кучи

Продолжительность

мы можем вычислить простую верхнюю границу времени работы BuildMaxHeap следующим образом: мы знаем, что MaxHeapify равно O(n log n), и мы вызываем его O(n) раз, поэтому BuildMaxHeap имеет верхнюю границу O(n log n)

но мы можем найти более точную верхнюю границу, используя тот факт, что куча из n элементов имеет высоту log(n) и не более n/(2^h+1) узлов на высоте h

время, требуемое MaxHeapify при вызове на узле высоты h, равно O(h)

мы можем выразить время работы BuildMaxHeap следующим образом

Алгоритм сортировки кучей

Алгоритм сортировки кучи начинается с создания максимальной кучи, поскольку максимальный элемент хранится в A[0], мы можем обменять его на последний элемент A[n-1], а затем отбросить последний элемент из кучи, а затем вызвать MaxHeapify

static vector<int> heapSort(Heap h){
        vector<int>sorted;
        h.BuildMaxHeap();
        for(int i = h.heap.size()-1;i>0;i--){
            sorted.push_back(h.heap[0]);
            h.heap[0] = h.heap[i];
            h.heap.pop_back();
            h.printHeap();
            h.Max_Heapify(0);
        }
        return sorted;
    }

процедура сортировки кучи занимает O (n log (n)) время выполнения, так как maxHeapify занимает O (log (n)) и выполняется n-1 раз

эффективная реализация Priority Queue с использованием кучи

приоритетная очередь — это структура данных для хранения набора значений, называемых ключами.

МаксимальныйЭлемент

возвращает элемент кучи с наибольшим ключом

int MaxElement(){return heap[0];}

ЭкстрактМакс

удаляет и возвращает элемент кучи с наибольшим ключом.

int HeapExtractMax(){
        if(heap.size()<1){return 0;}//ERROR
        int max = heap[0];
        heap[0] = heap[heap.size()-1];
        heap.pop_back();
        Max_Heapify(0);
        return max;
    }

УвеличитьКлюч

увеличивает значение ключа элемента x до нового значения k,
которое, как предполагается, не меньше текущего значения ключа x.

void HeapIncreseKey(int index,int key){
        if (heap[index]>key){return;}//Error:new key is samller than current
        heap[index] = key;
        int i = Parent(index);
        while(heap[i]<key && i>=0){
            heap[index]=heap[i];
            heap[i]=key;
            index = i;
            i = Parent(i);
        }
    }

ВставитьЭлемент

вставляет элемент x в кучу

void MaxHeapInsert(int key){
     heap.push_back(INT32_MIN);
     HeapIncreseKey(heap.size()-1,key);
}

полный исходный код класса кучи

#include<iostream>
#include<vector>
using namespace std;
class Heap
{
private:
    vector<int> heap;
public:
    Heap(vector<int> heap){
        this->heap = heap;
    }
    Heap(int size){
        heap.assign(size,0);
    }
    void printHeap(){
        for(int i = 0;i<heap.size();i++){
            cout<<heap[i]<<" ";
        }
        cout<<endl;
    }
    int leftChild(int index) {return (2*index+1);}
    int RightChild(int index) {return (2*index+1)+1;}
    int Parent(int index) {return (index-1)/2;}
    void setHeap(int value,int index){
        heap[index] = value;
    }  
    void Max_Heapify(int index){
        int left = leftChild(index);
        int right = RightChild(index);
        int largest = index;
        
        if(left<heap.size()&&heap[left]>heap[index]) largest = left;
        else largest = index;

        if(right<heap.size()&&heap[right]>heap[largest]) largest = right;

        if(largest != index){
            int tmp = heap[index];
            heap[index] = heap[largest];
            heap[largest] = tmp;
            Max_Heapify(largest);
        }
    }
    void BuildMaxHeap(){
        for(int i = (heap.size()/2)-1;i>=0;i--){
            Max_Heapify(i);
        }
    }
    static vector<int> heapSort(Heap h){
        vector<int>sorted;
        h.BuildMaxHeap();
        for(int i = h.heap.size()-1;i>0;i--){
            sorted.push_back(h.heap[0]);
            h.heap[0] = h.heap[i];
            h.heap.pop_back();
            h.printHeap();
            h.Max_Heapify(0);
        }
        return sorted;
    }
    int MaxElement(){return heap[0];}
    int HeapExtractMax(){
        if(heap.size()<1){return 0;}//ERROR
        int max = heap[0];
        heap[0] = heap[heap.size()-1];
        heap.pop_back();
        Max_Heapify(0);
        return max;
    }
    void HeapIncreseKey(int index,int key){
        if (heap[index]>key){return;}//Error:new key is samller than current
        heap[index] = key;
        int i = Parent(index);
        while(heap[i]<key && i>=0){
            heap[index]=heap[i];
            heap[i]=key;
            index = i;
            i = Parent(i);
        }
    }

    void MaxHeapInsert(int key){
        heap.push_back(INT32_MIN);
        HeapIncreseKey(heap.size()-1,key);
    }

};


int main(){
    vector<int> vec {4,1,3,2,16,9,10,14,8,7};
    Heap heap(vec);
    heap.BuildMaxHeap();
    heap.printHeap();
    heap.HeapIncreseKey(4,30);
    heap.printHeap();
}