介绍
堆是一种数据结构,它是一颗完全二叉树。最小堆则是在堆的基础增加了新的规则,它的根结点的值是最小的,而且它的任意结点的父结点的值都小于或者等于其左右结点的值。
代码
把堆看做一颗树的数组对象,相对于任意的一个结点(i),它都满足以下3个特性,如果它存在左右结点和父结点的话,那么其左结点的位置就是i*2,右结点的位置是i*2 +1,父结点的位置是i/2。
/// 左结点
int left(int i) const
{
return i << 1;
}
/// 右结点
int right(int i) const
{
return (i << 1) + 1;
}
/// 父结点
int parent(int i) const
{
return i >> 1;
}
定义一个最小堆的模板类min_stack,默认它的初始大小为DEFAULT_SIZE=1024,采用c++中容器vector来缓存堆元素(方便扩展),length_表示堆的长度。
/// 最小堆 非线程安全
template<class T>
class min_stack
{
/// 默认初始大小
static const int DEFAULT_SIZE = 1024;
private:
std::vector<T> heap_; ///< 堆
int32_t length_; ///< 堆长度
};
insert方法往堆里插入一个数据,当插入时,先判断当前堆长度是否已经等于默认长度,如果等于,则把容器的容量扩大一倍(这里没有做缩小处理,即当元素数量少于多少时容量减半)。然后把数据插入到数组的最后一位,用heap_decrease_key方法根据堆的特性把它移动到正确的位置。
/// 插入一个数据
void insert(const T& t)
{
/// 如果容量达到上限,则扩大一倍
if (length_ == DEFAULT_SIZE)
{
heap_.resize(length_ << 1);
}
/// 长度加1,并且插入
++ length_;
heap_decrease_key(length() - 1, t);
}
heap_decrease_key方法表示把i的原来的值替换成t值,然后依据堆的特性把它移动到正确的位置,移动的过程就是递归和它的父结点做比较,如果比父结点值小,则它的值就和父结点做交换,如果它的值比父结点大,则递归结束,当前的i值就是它的位置。
//某个结点替换值
void heap_decrease_key(int i, const T& t)
{
heap_[i] = t;
while(i >= 0 && heap_[i] < heap_[parent(i)])
{
std::swap(heap_[i], heap_[parent(i)]);
i = parent(i);
}
}
min_visitor获取最小堆的最小值,就是返回它的根结点的值,因为它的根结点就是它的最小值。
//返回最小值
bool min_visitor(T& t)
{
if (0 == length())
{
return false;
}
t = heap_[0];
return true;
}
min_pop返回最小堆的最小值,并且删除最小值,删除完成以后则需要重新计算出当前堆的最小值,因为删除最小值破坏了堆的特性。我们把数组最后一个元素放到堆的根结点(原最小堆的位置)并对堆的长度进行减1操作,然后用min_heapify方法对这个结点进行求最小堆的过程。
/// 返回最小值,并且删除
bool min_pop(T& t)
{
if (0 == length())
{
return false;
}
t = heap_[0];
heap_[0] = heap_[length() - 1];
-- length_;
min_heapify(0, length());
return true;
}
min_heapify方法是对某个结点求最小堆的过程:首先算出当前结点的左右结点,并把左右结点的值和当前结点的值作比较,计算出当中最小结点的值,如果当前结点的值不是最小,则把最小结点的值和当前结点的值进行交换,然后递归去判断交换以后的值和它的左右结点的值的大小,如果当前结点的值就是最小值,则递归结束。
//对某结点求最小堆的过程
void min_heapify(int i, int length)
{
int l = left(i);
int r = right(i);
int smallest = i;
if (l < length && heap_[i] > heap_[l])
{
smallest = l;
}
if (r < length && heap_[smallest] > heap_[r])
{
smallest = r;
}
if (smallest != i)
{
std::swap(heap_[i], heap_[smallest]);
min_heapify(smallest, length);
}
}
build_min_heap是一个建堆的过程,只需要对0-length/2调用min_heapify方法进行求最小堆的过程就可以了,因为length/2以后的都是子节点(由left,right方法可知),都会经过求最小堆的过程,所以只需要对0-length/2进行求最小堆就行了。
//建最小堆的过程
void build_min_heap()
{
for (int i = length() / 2; i >= 0; -- i)
{
min_heapify(i, length() - 1);
}
}
heap_sort堆排序是建立在堆的基础上的,所以首先得建立一个堆,然后再排序。排序的过程:每次都把当前最小的结点和数组的末尾的结点值进行交换,交换完成以后减少堆的长度1,然后对堆的根结点进行一个求最小堆的过程,又求出一个最小堆以后,重复刚刚动作,直至整个堆排序的结束。这样,一个排序的数组就出来了,通过print函数就可以输出整个数组。
//堆排序
void heap_sort()
{
build_min_heap();
int heap_length = length();
for (int i = length() - 1; i > 0 ; -- i)
{
std::swap(heap_[i], heap_[0]);
-- heap_length;
min_heapify(0, heap_length);
}
}
void print()
{
for (int i = 0; i < length_; i ++) {
std::cout << heap_[i] << " ";
}
std::cout << std::endl;
}
最小堆代码的测试用例:
int main()
{
min_stack<int> int_stack;
int_stack.insert(12);
int_stack.insert(13);
int_stack.insert(1);
int_stack.insert(21);
int_stack.insert(31);
int_stack.insert(11);
int_stack.insert(15);
int_stack.insert(111);
int_stack.insert(51);
int_stack.insert(41);
int_stack.insert(12);
int_stack.insert(13);
int_stack.insert(1);
int_stack.insert(21);
int_stack.insert(31);
int_stack.insert(11);
int_stack.insert(15);
int_stack.insert(111);
int_stack.insert(51);
int_stack.insert(41);
int_stack.insert(12);
int_stack.insert(13);
int_stack.insert(1);
int_stack.insert(21);
int_stack.insert(31);
int_stack.insert(11);
int_stack.insert(15);
int_stack.insert(111);
int_stack.insert(51);
int_stack.insert(41);
int_stack.insert(0);
cout << int_stack.length() << endl;
int min = 0;
int_stack.min_pop(min);
cout << min << endl;
cout << int_stack.length() << endl;
int_stack.min_visitor(min);
cout << min << endl;
cout << int_stack.length() << endl;
int_stack.insert(33);
int_stack.insert(min);
int_stack.heap_sort();
int_stack.print();
return 0;
}
总结
堆分2种,最小堆和最大堆,其实最大堆的实现和最小堆基本一样。最小堆的根是最小值,而最大堆的根是最大值,最小堆在某些判断上符号改变下就是最大堆了。最小堆的作用有很多,比如排序,比如事件驱动,例如当服务器判断某个socket是否超时时,就把最后通信时间存储在最小堆中,如果最小值和当前时间差大于某个值时,socket就超时。
[download id=”1141″ template=”button”]