堆的向上与向下调整

目录

一、堆

1、概念

2、性质

二、向上调整

 三、向下调整

四、建堆的比较

1.向上调整建堆

2.向下调整建堆

3.比较

五、总结


一、堆

1、概念

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

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

2、性质

堆是一颗完全二叉树。

堆的根(父亲)结点的值总是不大于或不小于孩子结点的值。

左孩子=父亲*2+1____ 右孩子=父亲*2+2。

堆的向上与向下调整_第1张图片

堆的向上与向下调整_第2张图片

二、向上调整

如果已经存在堆,再插入一个结点,从插入的结点开始,向上依次调整。

调整:如果插入的结点不需要调整,即插入该结点,依旧是堆。

           如果插入的结点不满足堆结构,就要对该结点的父亲结点调整,

调整完因为改变了父亲结点,就要迭代调整新的孩子结点。

图解:

堆的向上与向下调整_第3张图片

 代码实现:

//建大堆
void AdjustUp(HPDataType*a, int child)
{
	assert(a);
	int parent = (child - 1) / 2; //父亲结点
	while (child>0)
	{
		if (a[parent] < a[child])//大堆,孩子大于父亲就换
		{
			Sweap(&a[parent], &a[child]);
			child = parent;  //向上走 父亲变成孩子
		}
		else
		{
			break;
		}
		parent = (child - 1) / 2;//迭代求出新父亲结点
	}
}

循环结束的条件是孩子不为根结点或者已经是堆结构

 三、向下调整

从根开始向下调整,但是根的左子树和右子树必须是堆。

思路:

parent从跟结点开始

找到左右孩子中大的一个,如果左右孩子中大的一个大于根结点,就交换

由于交换了父亲结点和孩子结点,就要对交换的孩子子树进行向下调整

在调整中,必须注意孩子结点越界问题。

图解:
堆的向上与向下调整_第4张图片

 代码:

void AdjustDown(HPDataType* a, int parent, int n)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child

四、建堆的比较

建堆有俩种方式,向上和向下调整建堆,衡量一个算法的好坏,通常关注时间复杂度和空间复杂度。本文将以时间复杂度的比较,细说向上调整建堆和向下调整建堆算法。

1.向上调整建堆

向上调整建堆的思路:

1.依次插入结点。从第二个结点开始,比较孩子和父亲,如果是堆就继续比较下一个结点。如果不是就交换父亲和孩子的值,迭代向上比较。

2.比较完(孩子为0),比较下一对。

图解:

堆的向上与向下调整_第5张图片

 代码实现:

 typedef int HPDataType;

 void Sweap(HPDataType* a, HPDataType* b)
 {
     HPDataType tmp = *a;
     *a = *b;
     *b = tmp;
 }
 //建大堆
 void AdjustUp(HPDataType* a, int child)
 {
     assert(a);
     int parent = (child - 1) / 2; //父亲结点
     while (child > 0)
     {
         if (a[parent] < a[child])//大堆,孩子大于父亲就换
         {
             Sweap(&a[parent], &a[child]);
             child = parent;  //向上走 父亲变成孩子
         }
         else
         {
             break;
         }
         parent = (child - 1) / 2;//迭代求出新父亲结点
     }
 }

 void CreatTree(int* a, int n)
 {
     for (int i = 1; i < n; i++)
     {
         AdjustUp(a, i);
     }

 }

向上建堆时间复杂度:
图解:
堆的向上与向下调整_第6张图片

 

2.向下调整建堆

思路:

最后一个孩子的父亲结点开始依次进行向下调整,直到根结点。

过程相对简单,画图请自行推导

代码实现:

 typedef int HPDataType;

 void Sweap(HPDataType* a, HPDataType* b)
 {
     HPDataType tmp = *a;
     *a = *b;
     *b = tmp;
 }
 //建大堆
 void AdjustDown(HPDataType* a, int parent, int n)
 {
     assert(a);
     int child = parent * 2 + 1;
     while (child < n)
     {
         //找左 右孩子大的一个,如果右子树不存在,大的就是左树
         if (child + 1 < n && a[child] < a[child + 1])
         {
             child++;
         }
         if (a[parent] < a[child])
         {
             Sweap(&a[parent], &a[child]);
             parent = child;   //迭代调整下一棵子树
             child = parent * 2 + 1;
         }
         else
         {
             break;
         }

     }
 }

 void CreatTree(int* a, int n)
 {
     for (int i = (n-1-1)/2; i >=0; i--)
     {
         AdjustDown(a, i,n);
     }

 }

关于最后一个孩子的父亲下标:

最后一个孩子下标:n-1

孩子的父亲坐标:(n-1-1)/2

计算时间符复杂度:

堆的向上与向下调整_第7张图片

 3.比较

同样在最坏情况下

向上建堆的时间复杂度为: N*logN

向下建堆的时间复杂度为:N

一个简单的记法:

向上建堆,第二层向上要调整一次,越深层,调整的次数越多,最后一层是2^(h-1)*(h-1)次

是大*大

向下建堆:第一层要调整h-1次,是最多,越深层越少。最后一层是2^(h-1)*0次,是大*小

因此向下调整算法是一种极为优越的算法。

在建堆,我们通常用向下调整算法。

五、总结

堆是一种优越的数据结构,在堆中学习了向上和向下调整算法。

其中向下调整是优越的算法,它的时间复杂度是O(N)

掌握堆的建立,在今后的堆排序、TopK,优先级队列都能柔韧有余。

本文到此结束,感谢阅读!

你可能感兴趣的:(数据结构,数据结构,面试)