Jiang's C++ Space

创作,也是一种学习的过程。

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::

十二、二叉堆(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;
}
posted on 2009-10-29 14:33 Jiang Guogang 阅读(9706) 评论(12)  编辑 收藏 引用 所属分类: Knowledge

评论

# re: 图解数据结构(8)——二叉堆[未登录] 2010-07-21 17:00 rainfish
很好的文章,顶  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2010-11-21 16:08 hhhh
@rainfish
  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2010-11-21 16:09 hhhh
厉害啊,牛人  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2010-12-06 01:07 VRS
谢谢  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2010-12-09 16:48 李逍遥
收藏先  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2011-01-21 17:39 davis
编译不能通过  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆[未登录] 2012-03-08 16:49 初学者
请问按照你的算法, 这个堆怎么出堆
3 6 5 7
我的意思是当你把5 顶上去后,没有让7去顶5 这个空缺的code , 你说呢  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2012-04-23 22:22 柯杰
7楼,你还没理解这个代码


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;
}
  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2012-10-27 21:15 周冲
博主写的很好。只是出堆操作实在没有讲清楚。那几张图看不明白有点。
博主的入堆讲得还行。其实出堆操作与入堆操作思想相同,方向相反。
根节点出堆后,直接将最后一个(叶子)节点拿到根位置。然后进行“下降操作“,即将新根与其两个孩子比较,与较小孩子换位。新根即下降。直到降不动为止。  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2012-12-06 16:52 大馅儿水饺
@周冲
想法不错,表扬一下~  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2013-04-13 18:44 金马
很容易理解,谢谢  回复  更多评论
  

# re: 图解数据结构(8)——二叉堆 2013-05-10 13:57 Greedydaam
恩,同意@周冲 的说法,出堆的操作其实也就是把根节点出堆之后,最后一个节点放到跟节点然后进行下降操作。博主的入堆操作很好理解,赞一个。  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理