堆的简单理解

堆的简单(通俗)理解


要了解堆的数据形式,首先要了解树的基本形式。

树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树

在这里插入图片描述上图中就是一个满二叉树的基本结构图片,以其为例,满二叉树中每一结点的序号满足以下条件:若父结点的序号为i,则左子结点的序号为2i,右子结点的序号为2i+1。(其它类型的树可以尝试推断一下,这里不多做解释)

堆数据结构,可以看做是将一些数据按照一定要求存放在满二叉树中。以最小堆为例,其要求如下:父结点中存放的数据一定比两个子结点中存放的数据要小。(但两结点中的数据不一定要按照大小存放)下图就是一个简单的堆数据示例在这里插入图片描述
最大堆则与最小堆相反:父结点中存放的数据一定比两个子结点中存放的数据要大。

以排序代码为例

要求使用堆的性质原理对一个一维数组进行由小到大的排序:
该排序程序分为以下几部分:
1.交换函数
2.向下调整函数(后面两步都是基于向下调整函数实现的)
3.构建堆函数
4.删除最小元素并输出

交换函数:

//交换函数,用来交换堆中的两个元素的值
void swap(int x, int y)
{
 int t;
 t = h[x]; h[x] = h[y]; h[y] = t;
}

向下调整函数:
在理解这个函数前首先要记住以下两点
1、若父结结的序号为i,则左子结点的序号为2i,右子结点的序号为2i+1。
2、父结点中存放的数据一定比两个子结点中存放的数据要小。(最小堆)
实现方式:使用循环结构完成一个数的位置调整。先从父结点和两个子结点中找到最小值所在结点的序号(数组下标),然后再将最小值与父结点中的值调换位置(如果父结点中存放的就是最小值则不用进行这一步,直接退出循环),然后以此时参加过交换的子结点作为父结点进行下一轮循环,循环结束后,原来位于根结点的数就会被下调到满足上述条件的位置中。

void siftdown(int i)
{
 	//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整
	 int t, flag = 0; // flag用来标记是否需要继续向下调整
	 // 当i结点有子结点(其实是至少有左子结点)并且有需要继续调整的时候循环就执行
	 while (i * 2 <= n && flag == 0)
	 {
	  // 首先判断它和左子结点的关系,并用t记录值较小的结点编号
		if (h[i] > h[i * 2])
  			t = i * 2;
		else
 			t = i;
  		// 如果它有右子结点,再对右儿子进行讨论
 		 if (i * 2 + 1 <= n)
 		 {
   		// 如果右子结点的值更小, 更新较小的结点编号
   			if (h[t] > h[i * 2 + 1])
   				 t = i * 2 + 1;
  		}//到这一步,获取到了父结点和两个子节点
  	// 如果发现最小的结点编号不是自己,说明子结点中有比父结点更小的
  		if (t != i)
  		{
  			swap(t, i);//交换它们,注意swap函数需要自己来写
   			i = t; //更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整
 		 }
 		 else
   		flag = 1; //否则说明当前的父结点已经比两个子结点都要小了,不需要再进行调整了
	}
}

构建堆函数:
满二叉树恒成立条件:假如有n层结点,则最多有 2n-1个结点,叶子结点(没有子结点的结点)2n-1个,简单概括就是一个满二叉树中叶子结点永远比度数为2的结点(即这里有子结点的结点)多一个
示例:
在这里插入图片描述

根据这一特性列出构建函数

void creat()
{
 int i;
 //从最后一个非叶结点到第1个结点依次进行向上调整
 for (i = n / 2; i >= 1; i--)
  siftdown(i);
}

删除最小元素并输出:
使用临时变量保存最小值(此时最小值位于根结点)后,删除最小值。使用下调函数不断调整堆的结构,使得此时数组中的最小值处于根结点。

int deletemax()
{
 int t;
 t = h[1];//用一个临时变量记录堆顶点的值
 h[1] = h[n];//将堆的最后一个点赋值到堆顶
 n--; //堆的元素减少1
 siftdown(1); //向下调整
 return t;//返回之前记录的堆的顶点的最大值
}

主函数:

int main()
{
 int i, num;
 // 读入要排序的数字的个数scanf("8d- , &num) ;
 cin>>num;
 for (i = 1; i <= num; i++)
  cin>>h[i];
 n = num;
 //建堆
 creat();
 //删除顶部元素,连续删除n次, 其实也就是从大到小把数输出来
 for (i = 1; i <= num; i++)
  printf("%d ", deletemax());
 while (1); return 0;
}

相对于冒泡排序法,其时间复杂度更低,为Nlog(n)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值