一、简介
完全二叉树:若假设二叉树的深度为h,除第h层外,其他各层(1~h-1)的节点数都达到最大个数(即1~h-1)层为一个满二叉树,第h层所有节点都连续集中在最左边,这就是完全二叉树。
堆的定义:堆(heap),这里所说的堆是数据结构中的堆,而不是内存模型中的堆。堆通常是一个可以被看做一棵树,它满足下列性质:
- [性质一] 堆中任意节点的值总是不大于(不小于)其子节点的值;
- [性质二] 堆总是一棵完全树。
将任意节点不大于其子节点的堆叫做最小堆或小根堆,而将任意节点不小于其子节点的堆叫做最大堆或大根堆。
二叉堆的定义:二叉堆是完全二元树或者是近似完全二元树,它分为两种:最大堆和最小堆。
- 最大堆:父结点的键值总是大于或等于任何一个子节点的键值;
- 最小堆:父结点的键值总是小于或等于任何一个子节点的键值。
二叉堆存储结构:二叉堆一般都通过"数组"来实现。数组实现的二叉堆,父节点和子节点的位置存在一定的关系。有时候,我们将"二叉堆的第一个元素"放在数组索引0的位置,有时候放在1的位置。当然,它们的本质一样(都是二叉堆),只是实现上稍微有一丁点区别。
1.假设"第一个元素"在数组中的索引为 0 的话,则父节点和子节点的位置关系如下:
- 索引为i的左孩子的索引是 (2*i+1);
- 索引为i的右孩子的索引是 (2*i+2);
- 索引为i的父结点的索引是 floor((i-1)/2);
2.假设"第一个元素"在数组中的索引为 1 的话,则父节点和子节点的位置关系如下:
- 索引为i的左孩子的索引是 (2*i);
- 索引为i的右孩子的索引是 (2*i+1);
- 索引为i的父结点的索引是 floor(i/2);
二、二叉堆操作
1.插入操作:采用上滤策略,先创建一个空穴代表插入元素,并把它放到堆尾,如果它不破坏堆的性质,则插入成功;否则,把空穴的父节点移入该空穴,这样空穴朝着根方向冒一步。继续该过程直到X能被放入空穴中为止。
时间复杂度:最坏O(logN) 平均 2.607
代码
// 插入元素
void insert( const Comparable & x )
{
if( currentSize == array.size( ) - 1 ) // array[0]不存值 所以要-1
array.resize( 2 * array.size( ) );
// 上滤
int hole = ++currentSize;
Comparable copy = x;
array[0] = std::move( copy ); // 用于插入值比堆中所有元素小时结束for循环
for( ; x < array[ hole / 2 ]; hole /= 2 ) // 因为 hole = 1时 array[hole/2] = array[0] = x 不满足 x < array[ hole / 2 ]
array[ hole ] = std::move( array[ hole / 2 ] );
array[ hole ] = std::move( array[0] );
}
2.删除操作:采用下滤策略,
时间复杂度:最坏O(logN) 平均 O(logN)
代码
void deleteMin( Comparable & minItem )
{
if( !isEmpty( ) )
throw UnderflowException( );
minItem = std::move( array[1] );
array[1] = std::move( array[ currentSize-- ] );
percolateDown( 1 );
}
// 下滤
void percolateDown( int hole )
{
int child;
// 树最低曾最右边的值
Comparable tmp = std::move( array[hole] );
for( ; hole * 2 <= currentSize; hole = child )
{
child = hole * 2;
// child != currentSize 保证堆中偶数个元素也能成功
// array[child] > array[child+1] 选取一个较小的儿子
if( child != currentSize && array[child] > array[child+1] )
child ++;
if( tmp > array[ child ] )
array[ hole ] = std::move( array[ child ] );
else
break;
}
array[ hole ] = std::move( tmp );
}
3.构建堆(buildHeap):有时二叉堆是由一些项的初始集合构造而得的,这种构造函数N项作为输入,并把它们放到一个堆中。
时间复杂度:最坏O(N)
代码:
// 创建一个堆序的树
explicit BinaryHeap( const std::vector<Comparable> & items )
: array( items.size( ) + 10 ), currentSize{ items.size( ) }
{
for (size_t i = 0; i < items.size( ); i++)
array[i+1] = items[i];
buildHeap( );
}
void buildHeap( )
{
for (size_t i = currentSize / 2; i > 0; --i)
percolateDown( i );
}