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

【数据结构】二叉堆堆

二叉堆(也叫堆)是一个部分排序的二叉树,其排序规则体现在它的堆序性质上:最大堆和最小堆,最大堆就是其对于任一节点,每个节点的键值都大于等于它的孩子节点,所以根节点键值最大。最小堆则相反。

堆是一棵完全二叉树,具备完全二叉树的性质,可以用一个数组表示而不需要指针,在起始位置为 0 的数组中任一位置 i 上的元素,其左儿子在位置 2*1+1 上,右儿子在左儿子的后面邻近位置上,它的父节点则在位置 (i-1)/2。因此,一个堆数据结构将由一个数组(不管键值是什么类型)、一个代表最大容量的整数以及当前的堆大小组成。

下面用C++ 来实现堆,并补充基于堆这一数据结构上的堆排序,不同于前面介绍的堆排序

1. 堆的类结构

template <class Elem>
class BinaryHeap
{
public:
	BinaryHeap(int MaxSize = 50);
	BinaryHeap(const BinaryHeap<Elem> &rhs);
	BinaryHeap(Elem *Array,int ElemNum,int MaxSize);
	~BinaryHeap(void);

	Elem *Sort(void);              //堆排序
	bool Add(const Elem &Item);    //添加元素
	Elem Remove(void);             //移除(堆顶)元素

	inline int GetSize(void);      //获取当前堆大小

protected:
	Elem *Data;                    
	int CurrentNum;
	const int MAX_SIZE;

	void HeapifyUp(int Node);            //向上调整堆  
	void HeapifyDown(int Node);          //向下调整堆

	inline int ParentOf(int Node);       //得到节点的父节点位置
	inline int LeftChildOf(int Node);    //得到节点的左儿子位置
};
2. 构造函数,拷贝构造函数,析构函数

//constructor function
template <class Elem>
BinaryHeap<Elem>::BinaryHeap(int MaxSize) :MAX_SIZE(MaxSize)
{
	Data = new Elem[MAX_SIZE];
	CurrentNum = 0;
}

//copy constructor function
template <class Elem>
BinaryHeap<Elem>::BinaryHeap(const BinaryHeap<Elem> &rhs) :MAX_SIZE(rhs.MAX_SIZE),CurrentNum(rhs.CurrentNum)
{
	Data = new Elem[MAX_SIZE];
	strcpy(Data,rhs.Data);
}

//array constructor
//将数组元素构建成最大堆
template <class Elem>
BinaryHeap<Elem>::BinaryHeap(Elem *Array,int MaxSize) :MAX_SIZE(MaxSize)
{
	Data = new Elem[MAX_SIZE];
	CurrentNum = ElemNum;

	for (int i = 0; i < ElemNum; ++i)
		Data[i] = Array[i];

	for (int i = ParentOf(CurrentNum - 1); i >= 0; --i)   //单步向前,数组从后往前,树从上到下调整
		HeapifyDown(i);
}

template <class Elem>
BinaryHeap<Elem>::~BinaryHeap(void)
{
	if (Data)
		delete[] Data;
}
3. 最大堆调整(最小堆只需修改比较即可)

template <class Elem>
int BinaryHeap<Elem>::ParentOf(int Node)
{
	//assert(Node > 0);
	return (Node - 1) / 2;
}

template <class Elem>
int BinaryHeap<Elem>::LeftChildOf(int Node)
{
	return (Node * 2) + 1;
}

//最大堆调整:从Node位置向上调整(数组向前,Node后不管)
template <class Elem>
void BinaryHeap<Elem>::HeapifyUp(int Node)
{
	int Current = Node;
	int Parent = ParentOf(Node);
	Elem Item = Data[Current];

	while (Current > 0)
	{
		if (Item > Data[Parent])
		{
			Data[Current] = Data[Parent];
			Current = Parent;
			Parent = ParentOf(Current);
		}
		else
			break;
	}
	Data[Current] = Item;
}

//最大堆调整:从Node位置向下调整(数组向后,Node前不管)
template <class Elem>
void BinaryHeap<Elem>::HeapifyDown(int Node)
{
	int Current = Node;
	int Child = LeftChildOf(Node);
	Elem Item = Data[Current];

	while (Child < CurrentNum)
	{
		if (Child < (CurrentNum - 1))
		{
			if (Data[Child] < Data[Child + 1])
				++Child;
		}

		if (Item < Data[Child])
		{
			Data[Current] = Data[Child];
			Current = Child;
			Child = LeftChildOf(Current);
		}
		else
			break;
	}
	Data[Current] = Item;
}
此处给出最大堆向上调整 HeapifyUp 演示:Node = ParentOf(CurrentNum - 1);


那什么时候向上调整,什么时候向下调整呢?很明显构建二叉堆的时候必须向上迭代(递归)调整,另外添加元素也必须向上调整;而移除堆顶元素(排序)后,则需要从上向下调整。也就是说当原数组不是二叉堆或是往后面添加元素打乱稳定时,需要从下往上调整;移除元素后,虽然数组不是二叉堆形式了,但是有一子树是不需移动的,只需调整另外一边子树。通俗点就是抽走前面的需要向下调整,后面添加需要向前调整。

4. 添加、移除元素

template <class Elem>
bool BinaryHeap<Elem>::Add(const Elem &Item)
{
	if (CurrentNum >= MAX_SIZE)
		return false;

	Data[CurrentNum] = Item;
	HeapifyUp(CurrentNum++);
	return true;
}

template <class Elem>
Elem BinaryHeap<Elem>::Remove(void)
{
	assert(CurrentNum > 0);

	Elem Temp = Data[0];
	Data[0] = Data[--CurrentNum];  //此处是用数组最后一个填补
	HeapifyDown(0);                //从上往下调整
	return Temp;

	//或选择键值大的儿子填补空位,重复该步骤直到最后一个元素填补了空位 	
}
5. 堆排序
template <class Elem>
inline int BinaryHeap<Elem>::GetSize(void)
{
	return CurrentNum;
}

template <class Elem>
Elem* BinaryHeap<Elem>::Sort(void)
{
	Elem *NewArray = new Elem[CurrentNum];

	//升序
	for (int ElemNum = CurrentNum - 1; ElemNum >= 0; --ElemNum)
		NewArray[ElemNum] = Remove();

	/*降序
	int ElemNum = CurrentNum;
	for (int i = 0; i < ElemNum; ++i)
		NewArray[i] = Remove();
	*/

	return NewArray;
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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)树,顾名思义,长得像一棵树,不过通常我们画成一棵倒过来的树,根在上,叶在下。不说那么多了,图一看就懂:当然了,引入了树之后,就不得不引入树的一些概念,这些概念我照样尽量用图,谁会记那么多文字?树这种结构还可以表示成下面这种方式,可见树用来描述包含关系是很不错的,但这种包含关系不得出现交叉重叠区域,否则就不能用树描述了,看图:面试的时候我们经常被考到的是一种叫“二叉树”的结构,二叉