您的位置:首页 > 其它

【堆】二分堆的实现以及STL中的堆

2017-08-16 17:13 399 查看

堆的概念

binary_heap就是一种完全二叉树,也就是说整棵二叉树除了最底层的叶节点之外,是填满的,而最底层的叶节点由左至右又不得有空隙。

底层实现就是一个数组 vector。

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2*i+1 且 Ki<= K2*i+2(Ki >= K2*i+1 且 Ki >= K2*i+2) i = 0,1,2…,则称这个堆为最小堆(或最大堆)。



最大堆、最小堆

最小堆:任一结点的关键码均小于等于它的左右孩子的关键码,位于堆顶结点的关键码最小。从根节点到每个节点的路径,都是递增的。

最大堆:任一结点的关键码均大于等于它的左右孩子的关键码,位于堆顶结点的关键码最大。从根节点到每个节点的路径,都是递减的。

节点的左右孩子

堆存储在下标为0开始计数的数组中,因此在堆中给定小标为i的结点时:

1、如果i=0,结点i是根节点,没有双亲节点;否则结点i的双亲结点为结点(i-1)/2

2、如果2 * i+1>n-1,则结点i无左孩子,否则结点i的左孩子为结点2 * i +1

3、如果2 * i+2>n-1,则结点i无右孩子,否则结点i的右孩子为结点2 * i+2

*堆的创建

由于堆的底层是由vector实现的。所以调整堆的时候,其实就是处理数组的下标。

由于要满足堆的特性,从最后一个非叶子节点,开始调整,让以该节点为根的子树,满足堆的特性,直到到第一个非叶子节点即:根节点。

最后一个非叶子节点的下标:(size -2 )/2

那么如何调整不满足堆特性的子树呢

(1)找到最小的孩子

(2)判断孩子与根节点的大小,如果是大堆的话,根节点小于孩子节点的话,就交换(默认less比较器);如果是小堆的话,根节点大于孩子节点的话,就交换。

(3)循环上面(1)(2),直到遇到最后一个孩子节点。

自顶向下的调整

void _AdjustDown(size_t root, size_t size) //参数:根节点的下标,堆的大小
{
size_t parent = root;
size_t child = (parent * 2) + 1; // 默认最小孩子为左孩子
while (child < size)
{
//找到最小的孩子结点,判断child+1是否越界

if (child +1 < size && _heap[child + 1] < _heap[child])
{
child = child + 1;
}
//比较孩子和双亲结点

if (_heap[child] < _heap[parent])
{
std::swap(_heap[child], _heap[parent]);
parent = child;
child = child * 2 + 1;
}
else
{
break;
}
}
}


插入操作

堆的插入每次都在已经建成的而最小堆的后面插入,但插入之后,有可能破坏了堆的结构,这时就需要对堆进行重新调整。



自下而上 调整堆的结构

void _AdJustUp(size_t size) // 插入 向上调整堆
{
size_t parent = (size - 2) >> 1;
size_t child = size - 1;
while (child != 0)
{
if (_heap[child] < _heap[parent])
{
std::swap(_heap[child], _heap[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}


删除操作

堆的删除,并不是真正意义上的删除

堆的删除是:从堆中删除堆顶元素。移除堆顶元素之后,用堆的最后一个节点填补取走的堆顶元素,并将堆的实际元素个数减1。但用最后一个元素取代堆顶元素之后有可能破坏堆,因此需要将对自顶向下调整,使其满足最大或最小堆。



代码实现

template<class T>
class Less
{
public:
bool operator()(const T& left, const T& right)
{
return left <= right;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T& left, const T& right)
{
return left >= right;
}
};

template<class T,template<class> class Compare = Less> //模板的模板参数
class Heap
{
public:
Heap()
{}
Heap(const T arr[], size_t size)
{
//把arr的数据保存到 _heap中
//_heap.resize(size);
for (size_t idx = 0; idx < size; ++idx)
{
//_heap[idx] = arr[idx];
_heap.push_back(arr[idx]);
}

// 找到最后一个非叶子结点
size_t root = (size - 2) / 2;
for (int idx = root; idx >= 0; idx--)
{
_AdjustDown(idx, size);//调整堆
}
}
void Insert(const T data)
// 空 非空
{
_heap.push_back(data);
size_t size = Size();
if (size > 1)
{
_AdJustUp(size);
}
}
void Remove() //把顶端的和最后一位互换,然后删除
{
assert(!Empty());
size_t size = _heap.size();
if (size > 1)
{
std::swap(_heap[0], _heap[size - 1]);
_heap.pop_back();
_AdjustDown(0, size - 1);
}
else
{
_heap.pop_back();
}
}
bool Empty() const
{
return _heap.empty();
}
size_t Size()
{
return _heap.size();
}
T& Top()
{
return _heap[0];
}
~Heap()
{}

private:
// 调整新堆
void _AdjustDown(size_t root, size_t size) //参数:根节点的下标,堆的大小
{
size_t parent = root;
size_t child = (parent * 2) + 1; // 默认最小孩子为左孩子
while (child < size)
{
//找到最小的孩子结点,判断child+1是否越界

if (child + 1 < size && Compare<T>()(_heap[child + 1], _heap[child]))
//if (child +1 < size && _heap[child + 1] < _heap[child])
{
child = child + 1;
}
//比较孩子和双亲结点
if (Compare<T>()(_heap[child], _heap[parent]))
//if (_heap[child] < _heap[parent])
{
std::swap(_heap[child], _heap[parent]);
parent = child;
child = child * 2 + 1;
}
else
{
break;
}
}
}

void _AdJustUp(size_t size) // 插入 向上调整堆
{
size_t parent = (size - 2) >> 1;
size_t child = size - 1;
while (child != 0)
{
if (Compare<T>()(_heap[child], _heap[parent]))
//if (_heap[child] < _heap[parent])
{
std::swap(_heap[child], _heap[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}

private:
std::vector<T> _heap;
};


STL中的堆

make_heap、pop_heap、push_heap、sort_heap函数

STL中通过上面四个函数实现堆,使用的方法都是类似下面的声明。

template <class RandomAccessIterator>
void make_heap ( RandomAccessIterator first, RandomAccessIterator last );

template <class RandomAccessIterator, class Compare>
void make_heap ( RandomAccessIterator first, RandomAccessIterator last,
Compare comp );


注:sort_heap,排序后会破坏堆的结构。push_heap,要先对底层vector进行push_back;pop_heap函数,弹出后,要对vector进行pop_back

测试代码

int main () {
int myints[] = {10,20,30,5,15};
vector<int> v(myints,myints+5);

make_heap (v.begin(),v.end());
cout << "initial max heap   : " << v.front() << endl; //30

pop_heap (v.begin(),v.end()); v.pop_back();
cout << "max heap after pop : " << v.front() << endl;//20

v.push_back(99); push_heap (v.begin(),v.end()); //99
cout << "max heap after push: " << v.front() << endl;

sort_heap (v.begin(),v.end());

cout << "final sorted range :";
for (unsigned i=0; i<v.size(); i++) cout << " " << v[i];

cout << endl;

return 0;
}


堆的应用

(1)实现priority_queue优先级队列。默认是最大堆,

STL中优先级队列

(2)堆排序

交换类排序:堆排序

(3)topK问题

找出最大的前K个数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: