堆学习笔记

蹲街弑〆低调 提交于 2020-01-28 04:01:13
  1. 堆的基础知识

  • 堆是一种有序的二叉树,分为大堆和小堆。其中大堆的父结点的值大于或等于子结点的值,而小堆父结点的值则小于或等于孩子结点的值。堆的特点就是根结点要么最大,要么最小,所以经常用堆的特性来求最值
  • 堆可以用数组存储。若用a[0:n]来存储堆的元素,若已知a[i]是父结点(2*i+2<=n),则a[2*i+1]和a[2*i+2]分别为左右孩子结点。或者已知a[i]为孩子结点(0<i<=n),则父结点为a[ (i-1)/2]。
  • 堆结点的插入。堆把要插入的元素放在堆最后的叶子结点,然后再进行Up操作完成插入。若用len表示堆结点的个数,用a[n]来存储堆,堆插入num可以用一句代码实现。
  • a[len++] = num;

     

  • 堆的Up。以大堆为例,堆的Up参考代码如图2-1所示。类似,小堆只需对 "t > a[j]" 的 ">" 换成 "<",再修改函数名称即可。
  • void MaxHeapUp(int a[], int i)
    {//a[0 : n]中第i个元素进行up操作
       int t = a[i];
       int j = ( i -1) / 2;
       while( i > 0 && t > a[j])
       {
           a[i] = a[j];
           i = j;
           j = ( i - 1) / 2;
       }
       a[i] = t;
    }

    图2-1    大堆Up

  • 堆结点的删除。删除堆结点通常是删除根结点,即把堆的最后一个叶子结点覆盖根结点,然后再对新的根结点进行Down操作。参考代码如图2-2所示。
  1. void MaxHeapDown(int a[], int i, int n)
    {//对a[0:n]中的第i个元素进行down操作
        int t = a[i];
        int j = 2 * i + 1;
        while(j+1 <= n)
        {
            j = a[j] > a[j+1] ? j : j+1;
            if( t >= a[j])break;
            a[i] = a[j];
            i = j;
            j = 2 * i + 1;
        }
        a[i] = t;
    }

     

  2. 堆的编程练习

  • 题目:TOJ 2196 Nuanran's Idol 
  • 大意:Nuanran喜欢收集照片,他根据不同的喜欢程度,给每张照片评定一个分数,分数越高,代表越喜欢。而他送给朋友的照片都是分数最低的。
  • 算法:小堆。构建一个小堆,每次买进(Buy)新照片,插入到小堆最后一个结点,然后进行Up操作;每次送给(Give)朋友的照片分数即根结点的值,用最后叶子结点覆盖根结点,并对新的根结点进行Down操作。我的代码如图2-3所示。
  • #include <iostream>
    using namespace std;
    const int maxn = 100000;//a[i]进行up操作
    void MinHeapUp(int a[], int i);
    void MinHeapDown(int a[], int i, int Length);
    int main()
    {
        int n, score, heap[maxn], Length;
        char c;
        while(cin>>n && n)
        {
            Length = 0;
            while(n --)
            {
                cin>>c;
                if(c == 'B')
                {
                    cin>>score;
                    //insert score and update Length               heap[Length++]= score;
                    //a[Length-1] up
                    MinHeapUp(heap, Length-1);
                }
                else if(c == 'G')
                {
                      //output what the given score is
                      cout<<heap[0]<<endl;
                    //delete root, swap[a[0], a[i]] and down
                    heap[0] = heap[Length-1];
                    MinHeapDown(heap, 0, Length);
                }
                else
                    cout<<"Invalid input!"<<endl;
            }
        }
        return 0;
    }
    void MinHeapUp(int a[], int i)
    {
        int t = a[i];
        int j = ( i - 1) / 2;
        while( i > 0 )
        {
            if(t >a[j]) break;
            a[i] = a[j];
            i = j;
            j = ( i - 1) / 2;
        }
        a[i] = t;
    }void MinHeapDown(int a[], int i, int Length)
    {
        int t = a[i];
        int j = 2 * i + 1;
        while( j + 1 <= Length)
        {
            j = a[j] > a[j+1] ? j+1: j ; //取左右孩子较小者
            if( t < a[j]) break;
            a[i] = a[j];
            i = j;
            j = 2 * i +1;
        }
        a[i] = t;
    }

     图2-3    TOJ 2096的参考代码

  • 题目:TOJ 3488 Stone
  • 大意:把N堆石头搬到某地成为一堆。每次把两堆石头合并成一堆,需要消耗的能量值是石堆中石头的数量之和。给定N堆石头以及每堆石头的石头数量,计算最终搬石头消耗能量的最小值。
  • 算法:把每堆石头的数量值作为结点的值建立小堆。搬走一堆石头块数最少的石堆,取一次根结点的值,并删除根结点,调整,重新进行一遍。把搬走的两堆石头放在一起,插入,并重复之前的过程。直到石头的堆数只剩下一堆结束。我的代码如图2-4所示。
  • #include <iostream>
    using namespace std;
    
    void Up(int a[], int i)
    {//a[0 : n]
       int t = a[i];
       int j = ( i -1) / 2;
       while( i > 0 && t < a[j])
       {
           a[i] = a[j];
           i = j;
           j = ( i - 1) / 2;
       }
       a[i] = t;
    }
    void Down(int a[], int i, int n)
    {
        int t = a[i];
        int j = 2 * i + 1;
        while(j+1 <= n)
        {
            j = a[j] < a[j+1] ? j : j+1;
            if( t <= a[j])break;
            a[i] = a[j];
            i = j;
            j = 2 * i + 1;
        }
        a[i] = t;
    }
    int main()
    {
        int T, piles, i, a[100000], len, sum, cost;
        cin>>T;
        while(T --)
        {
            cin>>piles;
            len = 0;
            for(i=0; i<piles; i++)
            {//建立小堆
               cin>>a[i];
               len++;
               Up(a, i);
            }
           //计算搬石头的最少能量消耗
           sum = 0;
           while(len > 1)
           {
               cost = 0;
               //先搬走数量最少的石堆
               cost += a[0];
               //重新调整
               a[0] = a[--len];
               Down(a, 0, len);
               //再次搬走数量第二少的石堆
               cost += a[0];
               a[0] = a[--len];
               Down(a, 0, len);
               //把两次搬走的石堆放在一起和剩下的石堆进行调整
               a[len++] = cost;
               Up(a, len-1);
               sum += cost;
           }
           cout<<sum<<endl;
        }
        return 0;
    }

     图2-4    TOJ 3488 stone的参考代码

  • 题目:TOJ 3515 Middle Number
  • 大意:先给T次测试次数,给定N个数,M次操作(加入新的数或者输出中位数)。其中1<=N<=100000, 1<=N<=100000。
  • Sample Input
  • 1
    6
    1 2 13 14 15 16
    5
    add 5
    add 3
    mid
    add 20
    mid
  • Sample Output
  • 5
    13
  • 算法:分别建立一个大堆和小堆,让这两个堆的结点数目之差为0或1。假设len1和len2(|len1-len2|<2)分别为大堆heap1[]和小堆heap2[]的结点数, 判断中位数如图2-5所示。
  • IF len1 == len2 OR len1 - len2 == 1  mid = heap1[0]ELSE  mid = heap2[0]  

图2-5    判断中位数

  • 我的代码如图2-6所示。
  • #include <iostream>
    #include <string>
    using namespace std;
    
    void MinHeapUp(int a[], int i)
    {//a[0 : n]中第i个元素进行up操作
       int t = a[i];
       int j = ( i -1) / 2;
       while( i > 0 && t < a[j])
       {
           a[i] = a[j];
           i = j;
           j = ( i - 1) / 2;
       }
       a[i] = t;
    }
    void MaxHeapUp(int a[], int i)
    {//a[0 : n]中第i个元素进行up操作
       int t = a[i];
       int j = ( i -1) / 2;
       while( i > 0 && t > a[j])
       {
           a[i] = a[j];
           i = j;
           j = ( i - 1) / 2;
       }
       a[i] = t;
    }
    void MinHeapDown(int a[], int i, int n)
    {//对a[0:n]中的第i个元素进行down操作
        int t = a[i];
        int j = 2 * i + 1;
        while(j+1 <= n)
        {
            j = a[j] < a[j+1] ? j : j+1;
            if( t <= a[j])break;
            a[i] = a[j];
            i = j;
            j = 2 * i + 1;
        }
        a[i] = t;
    }
    void MaxHeapDown(int a[], int i, int n)
    {//对a[0:n]中的第i个元素进行down操作
        int t = a[i];
        int j = 2 * i + 1;
        while(j+1 <= n)
        {
            j = a[j] > a[j+1] ? j : j+1;
            if( t >= a[j])break;
            a[i] = a[j];
            i = j;
            j = 2 * i + 1;
        }
        a[i] = t;
    }
    
    int main()
    {
        int T, N, M, i, a[100000], mid, num, len1, len2, heap1[100000], heap2[100000];
        string op;
        cin>>T;
        while(T--)
        {
            //输入N个正整数
            cin>>N;
            for(i=0; i<N; i++)
                cin>>a[i];
    
            //建立大堆heap1和小堆heap2
            len1=1, len2=0, heap1[0] = a[0], mid = heap1[0];
            for(i=1; i<N; i++)
            {
                 if(a[i] <= mid)
                 {
                     heap1[len1++] = a[i];
                     MaxHeapUp(heap1, len1-1);
                 }
                 else
                 {
                     heap2[len2++] = a[i];
                     MinHeapUp(heap2, len2-1);
                 }
                 //平衡堆的整数数量,找出中位数
                   while( len1 - len2 > 1)
                   {// if (len1 - len2) > 1 then 大堆顶元素插入到小堆并删除大堆顶元素
                       heap2[len2++] = heap1[0];
                        MinHeapUp(heap2, len2-1);
                       heap1[0] = heap1[len1-1];
                       MaxHeapDown(heap1, 0, --len1);
                   }
                   while( len2 - len1 >1)
                   {// if (len2 - len 1) > 1 then 小堆顶元素插入到大堆并删除小堆顶元素
                       heap1[len1++] = heap2[0];
                       MaxHeapUp(heap1, len1-1);
                       heap2[0] = heap2[len2-1];
                       MinHeapDown(heap2, 0, --len2);
                   }
                   if(len1 == len2 || len1 - len2 == 1)
                       mid = heap1[0];
                   else
                        mid = heap2[0];
            }
           //输入M个操作, 并更新中位数, op=add x , op = mid
           cin>>M;
           for(i=0; i<M; i++)
           {
               cin>>op;
               if(op == "add")
               {//num <= mid, 插入大堆heap1,否则插入小堆heap2
                   cin>>num;
                   if(num <= mid)
                    {
                        heap1[len1++] = num;
                        MaxHeapUp(heap1, len1-1);
                    }
                    else
                    {
                        heap2[len2++] = num;
                        MinHeapUp(heap2, len2-1);
                    }
                   while( len1 - len2 > 1)
                   {// if (len1 - len2) > 1 then 大堆顶元素插入到小堆并删除大堆顶元素
                       heap2[len2++] = heap1[0];
                        MinHeapUp(heap2, len2-1);
                       heap1[0] = heap1[len1-1];
                       MaxHeapDown(heap1, 0, --len1);
                   }
                   while( len2 - len1 >1)
                   {// if (len2 - len 1) > 1 then 小堆顶元素插入到大堆并删除小堆顶元素
                       heap1[len1++] = heap2[0];
                       MaxHeapUp(heap1, len1-1);
                       heap2[0] = heap2[len2-1];
                       MinHeapDown(heap2, 0, --len2);
                   }
                   if(len1 == len2 || len1 - len2 == 1)
                       mid = heap1[0];
                   else
                        mid = heap2[0];
               }
               else if(op == "mid")
               {
                    cout<<mid<<endl;
               }
               else
                    cout<<"Invalid!\n";
           }
        }
        return 0;
    }

     

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!