最小堆

介绍

    堆是一种数据结构,它是一颗完全二叉树。最小堆则是在堆的基础增加了新的规则,它的根结点的值是最小的,而且它的任意结点的父结点的值都小于或者等于其左右结点的值。

代码

    把堆看做一颗树的数组对象,相对于任意的一个结点(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”]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Z小偉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值