微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

【数据结构】堆&优先级队列

说实话,之前看数据结构的时候,并没有更多的关注到堆,直到现在......

堆数据结构是一种数组现象,可以看成是一种完全二叉树。

堆的分类;

最大堆:每个父节点都大于其孩子结点。

最小堆:每个父节点都小于其孩子结点。

注意注意:区分与二叉排序树的区别!!!

堆也有很多应用,比如优先级队列,堆排序等等。再多的应用,都是先需要有堆。

堆的底层是一个数组,了解STL之后可以将底层写成vector,可以动态增容。

堆的创建:将一个数组中的元素进行向下调整,调整成大堆或者小堆。

向下调整算法:

void _HeapDown(size_t index)
	{
		size_t parent = index;
		size_t child = 0;
		while (child < _heap.size())
		{
			child = parent * 2 +1 ;
			if (child + 1 < _heap.size() && _heap[child] < _heap[child + 1])
				++child;
			if (child < _heap.size()&&_heap[parent] < _heap[child])
			{
				swap(_heap[parent],_heap[child]);
				parent = child;
			}
			else
				break;
		}
	}


只要一个结点有左右孩子,就得去比较,看是否需要交换。只能从倒数第一个非叶子结点开始。

堆也有自己的Push和Pop操作。Push操作就和栈,队列的Push一样,只是Push了之后需要调整成一个堆。Pop操作,不

删除最后一个元素,而是删除0号下标的元素。

Pop:将最后一个元素和0号下标的元素交换,删除最后一个元素,然后采用向下调整的算法进行调整。

Push:在数组的最后插入一个新的元素,采用向上调整的算法进行调整。

向上调整算法:

void _HeapUp(size_t child)
	{
		assert(_heap.size()>0);
		size_t parent = 0;
		Compare com;
		while (child > 0)
		{
			parent = (child - 1) / 2;
			if (_heap[parent] < _heap[child])
			{
				swap(_heap[parent],_heap[child]);
				child = parent;
			}
			else
				break;
		}
	}


这个比起向下调整就比较简单了。从给定结点开始,只需比较他和他的parent的大小关系(大堆时,parent下标的

值小于child下标的值就进行交换,并记住child值的变动,小堆同理)。

有时候,我们既需要大堆,也需要小堆,当然可以实现两个类,大堆类和小堆类。然而我们又发现大堆和小堆最大的

区别就在于个结点与孩子结点的大小关系,其他的思路什么的都是一样的,两个类就达不到代码的复用性。

这里我们采用仿函数,又叫函数对象,通过它来实现代码复用。

下边给出代码

template<typename T>
struct Less
{
	bool operator()(const T& l,const T& r)
	{
		return l < r;
	}
};
template<typename T>
struct Greater
{
	bool operator()(const T& l,const T& r)
	{
		return l > r;
	}
};

template<typename T,typename Compare = Greater<T>>
class Heap
{
public:
	Heap(T* _a = NULL,size_t size = 0)
	{
		for (size_t i = 0; i < size; ++i)
		{
			_heap.push_back(_a[i]);
		}
		for (int i = (size-2) / 2; i >= 0; --i)
		{
			_HeapDown(i);//向下调整
		}
	}
	void Show()
	{
		if (_heap.size())
		{
			for (size_t i = 0; i < _heap.size();++i)
				cout << _heap[i] << " ";
			cout << endl;
		}
	}
	void Push(const T& x)
	{
		_heap.push_back(x);
		_HeapUp(_heap.size()-1);
	}
	void Pop()//删除堆顶的元素
	{
		assert(_heap.size()>0);
		swap(_heap[0],_heap[_heap.size()-1]);
		_heap.pop_back();
		_HeapDown(0);
	}
	size_t Size()
	{
		return _heap.size();
	}
	const T& Top()
	{
		return _heap[0];
	}
protected:
	void _HeapDown(size_t index)
	{
		size_t parent = index;
		size_t child = 0;
		Compare com;
		while (child < _heap.size())
		{
			child = parent * 2 +1 ;
			//if (child + 1 < _heap.size() && _heap[child] < _heap[child + 1])
			if (child + 1 < _heap.size() && com(_heap[child+1],_heap[child]))
				++child;
			//if (child < _heap.size()&&_heap[parent] < _heap[child])
			if (child < _heap.size() && com(_heap[child],_heap[parent]))
			{
				swap(_heap[parent],_heap[child]);
				parent = child;
			}
			else
				break;
		}
	}
	void _HeapUp(size_t child)
	{
		assert(_heap.size()>0);
		size_t parent = 0;
		Compare com;
		while (child > 0)
		{
			parent = (child - 1) / 2;
			//if (_heap[parent] < _heap[child])
			if(com(_heap[child],_heap[child]);
				child = parent;
			}
			else
				break;
		}
	}
private:
	vector<T> _heap;
};

void testHeap()
{
	int a[] = { 3,4,5,1,2,6,7 };
	//测试大堆
	Heap<int> h1(a,7);
	h1.Show();
	h1.Push(10);
	h1.Show();
	h1.Pop();
	h1.Show();
	//测试小堆
	Heap<int,Less<int>> h2(a,7);
	h2.Show();
	h2.Push(0);
	h2.Show();
	h2.Pop();
	h2.Show();
}


这里就可以实现大小堆。

时间复杂度:

建堆:O(N*lgN)

插入:0(lgN)

删除:O(lgN)

优先级队列:

我们知道,队列是一种先进先出的数据结构,然而有时候先进先出并不能满足于我们。我们需要优先级最高的元素先

出队列,下边给出两种方法

Push:插入的时候就将插入的元素按照优先级放在合适的位置。时间复杂度O(N)

Pop:直接从队头删除。时间复杂度O(1)


Push:直接插在队尾。时间复杂度O(1)

Pop:找出优先级最高的元素进行删除时间复杂度O(N)

第一种方法比第二种更高效。

而这里,堆是实现优先级队列的一种更加高效的方法;

下边给出代码

template<typename T,typename Compare = Greater<T>>
class PriorityQueue
{
public:
	PriorityQueue(T* a,size_t size)
		:_q(a,size)
	{}
	void Pop()
	{
		_q.Pop();
	}
	void Push(const T& x)
	{
		_q.Push(x);
	}
	const T& Top()
	{
		return _q.Top();
	}
	void Show()
	{
		_q.Show();
	}
private:
	Heap<T,Compare> _q;
};
void testQueue()
{
	int a[] = { 3,7 };
	//测试小堆
	PriorityQueue<int,Less<int>> q1(a,7);
	q1.Show();
	q1.Push(0);
	q1.Show();
	q1.Pop();
	q1.Show();
	//测试大堆
	PriorityQueue<int> q2(a,7);
	q2.Show();
	q2.Push(10);
	q2.Show();
	q2.Pop();
	q2.Show();
}


这里就可以高效的实现优先级队列。需要注意的是,构造函数中那个成员,必须用初始化列表完成。这里就涉及到必

须使用初始化列表的几种情况~~~~

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


【啊哈!算法】算法3:最常用的排序——快速排序       上一节的冒泡排序可以说是我们学习第一个真正的排序算法,并且解决了桶排序浪费空间的问题,但在算法的执行效率上却牺牲了很多,它的时间复杂度达到了O(N2)。假如我们的计算机每秒钟可以运行10亿次,那么对1亿个数进行排序,桶排序则只需要0.1秒,而冒泡排序则需要1千万秒,达到115天之久,是不是很吓人。那有没有既不浪费空间又可以快一点的排序算法
匿名组 这里可能用到几个不同的分组构造。通过括号内围绕的正则表达式就可以组成第一个构造。正如稍后要介绍的一样,既然也可以命名组,大家就有考虑把这个构造作为匿名组。作为一个实例,请看看下列字符串: “08/14/57 46 02/25/59 45 06/05/85 18 03/12/88 16 09/09/90 13“ 这个字符串就是由生日和年龄组成的。如果需要匹配年两而不要生日,就可以把正则
选择排序:从数组的起始位置处开始,把第一个元素与数组中其他元素进行比较。然后,将最小的元素方式在第0个位置上,接着再从第1个位置开始再次进行排序操作。这种操作一直到除最后一个元素外的每一个元素都作为新循环的起始点操作过后才终止。 public void SelectionSort() { int min, temp;
public struct Pqitem { public int priority; public string name; } class CQueue { private ArrayList pqueue; public CQueue() { pqueue
在编写正则表达式的时候,经常会向要向正则表达式添加数量型数据,诸如”精确匹配两次”或者”匹配一次或多次”。利用数量词就可以把这些数据添加到正则表达式里面了。 数量词(+):这个数量词说明正则表达式应该匹配一个或多个紧紧接其前的字符。 string[] words = new string[] { "bad", "boy", "baad", "baaad" ,"bear", "b
来自:http://blog.csdn.net/morewindows/article/details/6678165/归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列
插入排序算法有两层循环。外层循环会啄个遍历数组元素,而内存循环则会把外层循环所选择的元素与该元素在数组内的下一个元素进行比较。如果外层循环选择的元素小于内存循环选择的元素,那么瘦元素都想右移动以便为内存循环元素留出位置。 public void InsertionSort() { int inner, temp;
public int binSearch(int value) { int upperBround, lowerBound, mid; upperBround = arr.Length - 1; lowerBound = 0; while (lowerBound <= upper
虽然从表内第一个节点到最后一个节点的遍历操作是非常简单的,但是反向遍历链表却不是一件容易的事情。如果为Node类添加一个字段来存储指向前一个节点的连接,那么久会使得这个反向操作过程变得容易许多。当向链表插入节点的时候,为了吧数据复制给新的字段会需要执行更多的操作,但是当腰吧节点从表移除的时候就能看到他的改进效果了。 首先需要修改Node类来为累增加一个额外的链接。为了区别两个连接,这个把指
八、树(Tree)树,顾名思义,长得像一棵树,不过通常我们画成一棵倒过来的树,根在上,叶在下。不说那么多了,图一看就懂:当然了,引入了树之后,就不得不引入树的一些概念,这些概念我照样尽量用图,谁会记那么多文字?树这种结构还可以表示成下面这种方式,可见树用来描述包含关系是很不错的,但这种包含关系不得出现交叉重叠区域,否则就不能用树描述了,看图:面试的时候我们经常被考到的是一种叫“二叉树”的结构,二叉