堆常用来实现优先队列,在这种队列中,待删除的元素为优先级最高(最低)的那个。在任何时候,任意优先元素都是可以插入到队列中去的,是计算机科学中一类特殊的数据结构的统称。

  一、堆的定义

  最大(最小)堆是一棵每一个节点的键值都不小于(大于)其孩子(如果存在)的键值的树。大顶堆是一棵完全二叉树,同时也是一棵最大树。小顶堆是一棵完全完全二叉树,同时也是一棵最小树。

注意:

  • 堆中任一子树亦是堆。
  • 以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k叉堆。

下图分别给出几个最大堆和最小堆的例子:

  二、支持的基本操作

  堆支持以下的基本操作:

  • build: 建立一个空堆;
  • insert: 向堆中插入一个新元素;
  • update:将新元素提升使其符合堆的性质;
  • get:获取当前堆顶元素的值;
  • delete:删除堆顶元素;
  • heapify:使删除堆顶元素的堆再次成为堆。

  某些堆实现还支持其他的一些操作,如斐波那契堆支持检查一个堆中是否存在某个元素。

   三、存储结构与操作

  堆里面的元素可以使用数组来存储,同时还要存储对的容量和长度。因此,可以定义如下结构体:

struct  HeapStruct{
     int *Element;
     int size;
     int capacity;
};

  在创建堆时,

  将堆中元素保存在数组中时,从数组的第一个元素开始存储数据,数组的长度为 堆的长度+1,数组的第0号元素存储的是比堆中所有元素大的哨兵,以便于后面更快的操作

HeapStruct  Creat_heap(int Maxsize)
{/*创建容量为Maxsize的空最大堆*/ HeapStruct
*h=new (HeapStruct); h->Element=new int[Maxsize+1]; h->size=0; h->capacity=Maxsize; h->Element[0]=MaxData; /*MaxData 位大于堆中所有可能的值,以便于后面更快的操作*/

   return h; }

  堆的插入:

  每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,现在的任务是将这个新数据插入到这个有序数据中——这就类似于直接插入排序中将一个数据并入到有序区间中。

  插入的步骤如下:

      1、将数据插入到数组的末尾

      2、比较其父节点与插入点的数据大小,使其满足大(小)堆的条件

      3、一次向上递归直到父节点大(小)于琦子节点

void Heap_Insert(HeapStruct *h,int t)
{/*将元素t插入堆h,其中h->Element[0]已设置为哨兵*/
       
        if(IsFull())
        {
               cout << "堆已满"<<endl;
               return ;
         }

        int i=++ h->size; /*插入数据的位置*/

        while( i>0&&h->Element[i/2]<t)  /*父节点小于子节点,由于根节点从1开始编号,故i的父节点的编号为i/2*/
        {
       h
->Element[i]=h->Element[i/2];
i/=2; /*向下过滤节点*
     }
    h-Element[i]=t; /*将t插入*/
}

  堆的删除:

  堆的删除通常是取出根节点的数据,并删除节点,其实删除其他节点也是类似的,只是左后需要将最后一个数与删除节点的父节点和子节点都比较(应该是先与子节点比较,然后与父节点比较,因为与父节点比较时,需要保证父节点是堆)。对于删除根节点,由于根节点没有父节点,因此只需和其父节点比较。

int Heap_Delete(Heap_Struct *h)
{
     int MaxData=h->Element[1];
     int tmp=h->Element[h->size--];/*取出最后一个元素并将堆的长度减1*/
     int i=1;
     while(2*i<=h->size && h->Element[2*i]>tmp)
     {
            if((2*i+1)<=h->size && h->Element[2*i+1]>h->Element[2*i] && h->Element[2*i+1]>tmp)  /*右节点存在且大于左节点且大于插入的值,则将右节点移到其父节点并继续向上过滤*/
           { 
                    h->Element[i]=h->Element[2*i+1];;
                    i=2*i+1;
           }
            else if(h->Element[2*i]>tmp)   /*左节点大且大于插入的值,则将左节点移到其父节点并继续向上过滤*/
           {
                   h->Element[i]=h->Element[2*i];
                   i=2*i;
            }
            else
                   break;
     }

      h->Element[i]=tmp;   /*将最后一个数插到堆中*/
      return MaxData;
}                    

  最大(小)堆的建立

  在应用最大堆时(譬如堆排序),需要先建立最大堆。建立最大堆有两种方法,

  一种是通过堆的插入操作,将N个元素一个个相继插入到一个初始为空的堆中,其时间复杂对为O(NlogN)。

  另一种方法是将N个元素按顺序存入,先满足完全二叉树的结构特性,然后,调整各节点的位置以满足最大堆的有序特性,建堆的时间复杂度为O(N)。

     建堆过程:根据堆中某根节点,其左右节点也为堆。因此反过来,应该先保证子节点为堆,然后在调整父节点使其为堆。以下面的一个堆为例:

  根据上面分析,首先看最后一个子节点与其父节点是否构成堆,然后依次往前遍历,确保每个节点满足堆的特性。所以首先考虑87,然后考虑30、83、43、66、79

Heap_Struct*  Heap_Construct(int *arr,int len)
{/*根据长度为len的数组创建最大堆*/
    
      Heap_Struct  *H_tmp= new Heap_Struct;
      H_tmp->Element=new int[len+1];
      H_tmp->size=len;
      H_tmp->capacity=len;

      int i,start,tmp=0;
      int parent,child;
      for (i=1;i<len+1;i++)
        {
            H_tmp->Element[i]=arr[i-1];
            if(tmp<arr[i])
                tmp=arr[i];
        }
      H_tmp->Element[0]=tmp+5;  
      start=i/2;
      for(i=start;i>0;i--)
      {
           parent=i;
         while(2*parent<=H_tmp->size)  //存在子节点,则按照前面删除根节点后重构堆的办法,以第i个节点为根,重构子树的堆
         {
            if(2*parent+1<=len && H_tmp->Element[2*parent+1]>H_tmp->Element[2*parent] && H_tmp->Element[2*parent+1]>H_tmp->Element[parent])
            {
                   tmp=H_tmp->Element[parent];
                   H_tmp->Element[parent]=H_tmp->Element[2*parent+1];
                   H_tmp->Element[2*parent+1]=tmp;      
                   parent=2*parent+1;
            }
           else if(H_tmp->Element[2*parent]>H_tmp->Element[parent])
            {
                    tmp=H_tmp->Element[parent];
                    H_tmp->Element[parent]=H_tmp->Element[2*parent];
                    H_tmp->Element[2*parent]=tmp; 
                    parent=2*parent;      
            }
           else 
               break;
         }
      }
       return H_tmp;
}        

例:

#include <iostream>

using namespace std;

struct  Heap_Struct{
     int *Element;
     int size;
     int capacity;
};

Heap_Struct*  Heap_Construct(int *arr,int len)
{/*根据长度为len的数组创建最大堆*/
    
      Heap_Struct  *H_tmp= new Heap_Struct;
      H_tmp->Element=new int[len+1];
      H_tmp->size=len;
      H_tmp->capacity=len;

      int i,start,tmp=0;
      int parent,child;
      for (i=1;i<len+1;i++)
        {
            H_tmp->Element[i]=arr[i-1];
            if(tmp<arr[i])
                tmp=arr[i];
        }
      H_tmp->Element[0]=tmp+5;  
      start=i/2;
      for(i=start;i>0;i--)
      {
           parent=i;
            while(2*parent<=H_tmp->size)  //存在子节点 
         {
            if(2*parent+1<=len && H_tmp->Element[2*parent+1]>H_tmp->Element[2*parent] && H_tmp->Element[2*parent+1]>H_tmp->Element[parent])
            {
                   tmp=H_tmp->Element[parent];
                   H_tmp->Element[parent]=H_tmp->Element[2*parent+1];
                   H_tmp->Element[2*parent+1]=tmp;      
                   parent=2*parent+1;
            }
           else if(H_tmp->Element[2*parent]>H_tmp->Element[parent])
            {
                    tmp=H_tmp->Element[parent];
                    H_tmp->Element[parent]=H_tmp->Element[2*parent];
                    H_tmp->Element[2*parent]=tmp; 
                    parent=2*parent;      
            }
           else 
               break;
         }
      }
       return H_tmp;
}        

int main()
{
    int arr[]={79,66,43,83,30,87,38,55,91,72,49,9};
    for(int i=0;i<12;i++)
        cout << arr[i] <<' ';
    cout << endl;
    int len=12;
    Heap_Struct* h=Heap_Construct(arr,len);
    for(int i=1;i<13;i++)
        cout << h->Element[i] << ' ';
    cout <<endl;
    
    return 0;
}

输出为:

原文地址:https://www.cnblogs.com/wujing-hubei/p/6071779.html