首页 > 代码库 > 二叉堆
二叉堆
二叉堆
我们知道堆栈是一种LIFO(后进先出)结构,队列是一种FIFO(先进先出)结构,而二叉堆是一种最小值先出的数据结构,因此二叉堆很适合用来做排序。
二叉树的性质:二叉堆是一棵完全二叉树,且任意一个结点的键值总是小于或等于其子结点的键值,
二叉堆采用数组来存储(按广度优先遍历的顺序),而没有像普通的树结构使用指针来表示节点间的关系。
如下图所示:
如图,最小的一个元素就是数组第一个元素。
将二叉堆按最广优先遍历的顺序编号从0到N-1,根据完全二叉树的性质,则第n个结点的子结点分别是2n+1和2n+2,其父结点是(n-1)/2。并且,叶子结点的下标是从 (N-1)/2开始,一直到N-1。这样,对于数组中的任意一个元素,我们可以很方便的计算出它的父节点、孩子节点所在的位置(即数组下标)。
插入
如果要在二叉堆中插入或删除一个元素,必须保证堆性质仍能满足。
假如要在上面的二叉堆里插入一个键值为2的新元素,那只需在数组末尾加入这个元素,然后尽可能把这个元素往上挪,直到挪不动,经过了这种复杂度为Ο(logn)的操作,二叉堆还是二叉堆。如下图:
插入过程:
如上图,将键值为2的元素放在数组最后一个位置(下标为12,下标从0开始计数),
其父节点是下标为5的元素,且其键值为6>2,不满足堆性质,将两个元素交换;
现在键值为2的新元素的下标变成了5,其父节点是下标为2的元素,该元素键值为5>2,继续交换!
删除
删除操作一定是踢出数组的第一个元素,这么来第一个元素以前的位置就成了空位,我们需要把这个空位挪至叶子节点,然后把数组最后一个元素插入这个空位,把这个“空位”尽量往上挪,如下图所示:
最后意:删除和插入一样,都是O(n)的时间复杂度。
C++的一个实现:
#include <iostream>using namespace std;#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); void PrintQueue(); 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;}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; int iIndex = m_iAmount - 1; while (m_pData[iIndex] < m_pData[(iIndex-1)/2]) { SWAP_TWO_INT(m_pData[iIndex], m_pData[(iIndex-1)/2]) iIndex = (iIndex-1)/2; } return 1;}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++) { cout<<m_pData[i]<<"\t"; } cout<<endl;}int main(){ int iVal; CBinaryHeap bh; bh.Enqueue(4); bh.Enqueue(1); bh.Enqueue(3); bh.Enqueue(2); bh.Enqueue(6); bh.Enqueue(5); bh.PrintQueue(); bh.Dequeue(iVal); bh.Dequeue(iVal); bh.PrintQueue(); return 0;}
最大堆:父结点的键值总是大于或等于任何一个子结点的键值;最大堆常用于排序算法。
最小堆:父结点的键值总是小于或等于任何一个子结点的键值;最小堆常用于优先队列。
二叉堆