• 堆学习笔记


    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!
      ";
             }
          }
          return 0;
      }
  • 相关阅读:
    使用Fiddler捕获Java程序中的HTTP请求
    js解析json对象和json字符串
    写代码 在与思考
    sql 逻辑读取次数
    WbeAPI 学习笔记
    大话设计模式阅读笔记
    行转列
    sql server 的多表查询(left join )
    union 和union all比较
    sql 跨库和域插入数据库
  • 原文地址:https://www.cnblogs.com/vsky/p/5007353.html
Copyright © 2020-2023  润新知