图解数据结构(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 ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值