图解数据结构——二叉堆

转载自图解数据结构(8)——二叉堆

================================================================================================================================

十二、二叉堆(Binary Heap)
经历了上一篇实现AVL树的繁琐,这篇就显得非常easy了。
首先说说数据结构概念——堆(Heap),其实也没什么大不了,简单地说就是一种有序队列而已,普通的队列是先入先出,而二叉堆是:最小先出。
这不是很简单么?如果这个队列是用数组实现的话那用打擂台的方式从头到尾找一遍,把最小的拿出来不就行了?行啊,可是出队的操作是很频繁的,而每次都得打一遍擂台,那就低效了,打擂台的时间复杂度为Ο(n),那如何不用从头到尾fetch一遍就出队呢?二叉堆能比较好地解决这个问题,不过之前先介绍一些概念。
完全树(Complete Tree):从下图中看出,在第n层深度被填满之前,不会开始填第n+1层深度,还有一定是从左往右填满。


再来一棵完全三叉树:


这样有什么好处呢?好处就是能方便地把指针省略掉,用一个简单的数组来表示一棵树,如图:


那么下面介绍二叉堆:二叉堆是一种完全二叉树,其任意子树的左右节点(如果有的话)的键值一定比根节点大,上图其实就是一个二叉堆。
你一定发觉了,最小的一个元素就是数组第一个元素,那么二叉堆这种有序队列如何入队呢?看图:


假设要在这个二叉堆里入队一个单元,键值为2,那只需在数组末尾加入这个元素,然后尽可能把这个元素往上挪,直到挪不动,经过了这种复杂度为Ο(logn)的操作,二叉堆还是二叉堆。
那如何出队呢?也不难,看图:


出队一定是出数组的第一个元素,这么来第一个元素以前的位置就成了空位,我们需要把这个空位挪至叶子节点,然后把数组最后一个元素插入这个空位,把这个“空位”尽量往上挪。这种操作的复杂度也是Ο(logn),比Ο(n)强多了吧?
尝试自己写写代码看,当然了,我也写(这个得动动手啦,比AVL容易多了):

#include "stdio.h"
#define SWAP_TWO_INT(a, b) \
a^=b; b^=a; a^=b;
class CBinaryHeap
{
  public:
    CBinaryHeap(int iSize = 100);
    ~CBinaryHeap();

    //Return 0 means failed.
    int Enqueue(int iVal);
    int Dequeue(int &iVal);
    int GetMin(int &iVal);

    #ifdef _DEBUG
      void PrintQueue();
    #endif

  protected:
    int *m_pData;
    int m_iSize;
    int m_iAmount;
};

CBinaryHeap::CBinaryHeap(int iSize)
{
  m_pData = new int[iSize];
  m_iSize = iSize;
  m_iAmount = 0;
}

CBinaryHeap::~CBinaryHeap()
{
  delete[] m_pData;
}

#ifdef _DEBUG
int CBinaryHeap::Enqueue(int iVal)
{
  if(m_iAmount==m_iSize)
  return 0;

  //Put this value to the end of the array.
  m_pData[m_iAmount] = iVal;
  ++m_iAmount;

  int iIndex = m_iAmount - 1;
  while(m_pData[iIndex] < m_pData[(iIndex-1)/2])
  {
    //Swap the two value
    SWAP_TWO_INT(m_pData[iIndex], m_pData[(iIndex-1)/2])
    iIndex = (iIndex-1)/2;
  }

  return 1;
}
#endif

int CBinaryHeap::Dequeue(int &iVal)
{
  if(m_iAmount==0)
  return 0;

  iVal = m_pData[0];
  int iIndex = 0;
  while (iIndex*2<m_iAmount)
  {
    int iLeft = (iIndex*2+1 < m_iAmount)?(iIndex*2+1):0;
    int iRight = (iIndex*2+2 < m_iAmount)?(iIndex*2+2):0;
    if(iLeft && iRight) // Both left and right exists.
    {
      if(m_pData[iLeft]<m_pData[iRight])
      {
        SWAP_TWO_INT(m_pData[iIndex], m_pData[iLeft])
        iIndex = iLeft;
      }
      else
      {
        SWAP_TWO_INT(m_pData[iIndex], m_pData[iRight])
        iIndex = iRight;
      }
    }
    else if(iLeft) //The iRight must be 0
    {
      SWAP_TWO_INT(m_pData[iIndex], m_pData[iLeft])
      iIndex = iLeft;
      break;
    }
    else
    {
      break;
    }
  }

  //Move the last element to the blank position.
  //Of course, if it is the blank one, forget it.
  if(iIndex!=m_iAmount-1)
  {
    m_pData[iIndex] = m_pData[m_iAmount-1];

    //Try to move this element to the top as high as possible.
    while(m_pData[iIndex] < m_pData[(iIndex-1)/2])
    {
      //Swap the two value
      SWAP_TWO_INT(m_pData[iIndex], m_pData[(iIndex-1)/2])
      iIndex = (iIndex-1)/2;
    }
  }

  --m_iAmount;

  return 1;
}

int CBinaryHeap::GetMin(int &iVal)
{
  if(m_iAmount==0)
    return 0;
  iVal = m_pData[0];
  return 1;
}

void CBinaryHeap::PrintQueue()
{
  int i;
  for(i=0; i<m_iAmount; i++)
  {
    printf("%d ", m_pData[i]);
  }
  printf("\n");
}

int main(int argc, char* argv[])
{
  CBinaryHeap bh;
  bh.Enqueue(4);
  bh.Enqueue(1);
  bh.Enqueue(3);
  bh.Enqueue(2);
  bh.Enqueue(6);
  bh.Enqueue(5);
  #ifdef _DEBUG
    bh.PrintQueue();
  #endif

  int iVal;
  bh.Dequeue(iVal);
  bh.Dequeue(iVal);
  #ifdef _DEBUG
  bh.PrintQueue();
  #endif
  return 0;
}



展开阅读全文

没有更多推荐了,返回首页