堆是数据结构里面一个重要的存储结构,而且堆也可以实现排序作用,其中的stl里面的priority_queue(优先级队列)的底层用的就是堆排序,所以而且建堆排序的效率也很高,所以堆很重要,今天我们就讲一下堆的实现,这里说一下堆的底层其实就是顺序表。
下面我呢就来看一下堆的数据结构是什么样子
堆的结构和顺序表的一样,因为堆的底层空间就是顺序的,但是堆的逻辑结构其实是一棵完全二叉树
这里就不讲他的结构了,下面我们会讲到他的逻辑结构
我们这里先看一下二叉树(完全二叉树)是什么样子
好了就是这个样子,这里就假设你已经知道了二叉树或者完全二叉树了
如果不知道的话可以先去了解一下
下面我们看一下他们的关系
其中每一棵树都分为根节点和孩子节点
这里是这个样子的,他的根节点就是顺序表的第一个位置,所以他就是下标为0的位置,所以让根节点为0,这样我们就可以知道每一个孩子节点和父亲节点的关系了
下面我们来看
其中假如节点3,那么他的 父亲节点就是(3-1)/2,就是这样,而他的右孩子节点也是这样计算,不相信的话可以算一下,那么如果要算2节点的孩子节点呢?
下面在看一下,如果2节点的孩子节点其中有两个,而左孩子节点就是(2*2)+1,我们可以看到确实为5这个节点,而他的右孩子节点也就是理所当然的在多加一个1就可以了
OK这里树节点已经说清楚了,这里说的后面会有用处
下面开始大概说一下里面会实现的函数有哪些
OK,就是这么一些,这个和栈比较多了一个Adjustup(向上调整算法) 和AdjustUp(向下调整算法),OK我们下面仔细看一下
首先还是老样子
初始化函数
这里和之前都是一模一样的(不懂的可以看我之前的顺序表和栈等...)
OK下面还是Push这个有意思,下面细说
这里也能看到,刚开始是断言检查,然后就是检查扩容,后面就是先把数据插入进去,那我先把扩容给大家看一下,之前也说过,这里主要讲向上调整算法
这里大家看一下吧,写这个函数主要是为了让其他函数看起来不那么臃肿,没有别的意思
那么我们就看一下向上调整算法,和为什么要像上调整
首先我们要知道,我们这里写的是一个大堆,大堆的特点就是每一个根节点的值都大于两个孩子节点,所以我们来看向上调整算法
我们也来看一下为什么要像上调整
首先假设我们要插入这些数据
就是后面这些,那么我们当然就是第一个要插入2,然后就要插入34就像下面这样
但是由于我们这个是大堆,每一个根节点都大于两个孩子节点,所以这里明显是孩子节点大于根节点的,所以这里需要把孩子节点向上调整,这里就要向上调整
我们来看一下
这里假设一个已经插入一些数据的值了,因为只有两个节点或者是一个一个插入太难讲了,我们先假设前面的已经插入好了而且也调整好了,不过这里新插入的节点和一个一个插入都是差不多的,所以我们就假设我们已经把刚才的数据插入进去了
我们假设已经把刚才的数据插入进去了,但是这里我们继续插入一个45这个值
那么我们刚开始肯定会插入到这里,但是这个节点不满足大堆,所以需要对这个节点进行向上调整
我们就来看一下如何调整
这里我们看到他比他的父亲节点的值要大,所以我们需要让他和他的父亲交换
所以需要交换他们两个
交换之后就是这样,但是他还是不满足,因为45还是大于他的父亲节点34所以还需要交换他们两个
所以继续交换
交换后就成了这样,我们现在在看,他已经满足大堆了,所以我们也不需要继续调整了
这时候我们就插入完毕,所以这时候我们在看看向上调整代码就很容易了
这里我们把孩子节点的位置传过来了,所以这时候我们就需要计算出父亲节点的位置,前面已经说过了父亲节点的位置如何计算,而且上面代码也有写,这里就不说了,然后这时候我们就需要一直判断因为这里有可能调整一次后还是不满足,就像上面插入45后调整了两次,所以这里我们是需要用循环的,这里结束条件就是直到孩子节点调整到根节点,或者就是节点已经满足大堆的条件就不用交换了,所以这里我们如果大于的话我们就用Swap函数交换,如果已经满足了大堆的条件我们就直接break退出就行了,如果已经交换一次我们就需要判断下一个满不满足,所以我们把父亲节点的位置给孩子节点,然后再计算父亲节点的位置就是这样
这里先给你们看交换函数其实很简单
下面我们看一下Pop
这里就是Pop函数这里删除我就直接说思路了,由于这个低层和顺序表类似,而且对于堆来说删除哪一个数据有意义?当然,这里我们只要稍微想一下我们这个堆是大堆所以第一个数据的值是最大的,所以我们删除的话也是删除第一个数据,而如果我们直接删除第一个数据的话那肯定是不行的,不仅会打乱后面节点之间调整好的顺序,而且头删的话效率也特别低
所以这里我们可以让第一个数据和最后一个数据交换,然后删除最后一个数据,然后就是对第一个数据进行向下调整算法,我们先看一下这向下调整算法的思路是什么样子的
现在先假设我们还是最上面的数据,假设我们没有插入45的数据
我们的数据先是这个样子的,然后就是我们第一个数据和最后一个数据位置交换
所以我们就交换
这就是交换以后,然后我们还要删除最后一个元素
然后我们删除之后,由于我们这个是大堆,每一棵树的根节点都大于两个子节点,所以我们第一个数据肯定是最大的,所以我们 这时候就要和第一个位置的两个孩子位置的数据进行比较,找两个孩子里面更大的那个,如果父亲位置的数据大于最大孩子位置的数据,那么就不用交换了,如果小于的话,就和较大孩子位置的数据交换,然后继续比较,思路就是这样这时候我们在看代码
我们这里先计算一下孩子位置,之前说过如何计算孩子的位置,如果我们孩子的位置大于数据总个数,那么就退出,然后这时候我们还是用循环,由于我们刚开始只看了一个左孩子位置的数据,因为这里还有可能有有孩子,所以这里也要看一下右孩子位置的数据和左孩子位置的数据的大小,让child变量记录两个孩子里面数据较大孩子的位置,由于这里的右孩子不一定存在,所以这里还需要判断右孩子位置是否合法,如果这时候都合法,并且孩子位置的数据大于父亲位置的数据,那么就交换,如果不大于的话就直接break,如果已经交换完了的话,那么这时候我们就更新父亲位置,把孩子位置给父亲,然后再计算孩子的位置,进去新一轮循环,知道孩子位置大于数据总个数或者,父亲位置数据已经满足大堆的条件,就是这样
这时候我们主要的内容已经讲完了,不过上面在Pop的时候用了Empty函数,这个函数也很简单
很简单,如果size等于0就说明为空
下面看一下size函数
也就是返回size就可以了
下面在看一下,取第一个位置的元素也就是取堆顶数据
就是返回a位置的第一个元素
最后就是销毁了
很简单,直接free就可以了
最后补充一句,其实还有小堆,小堆的话只需要改变向上调整和向下调整里面父亲位置和孩子位置比较的大于小于符号就可以了
例如向上调整,如果孩子小于父亲就交换
向下调整,如果父亲大于孩子就交换
就是这样,下去可以自己试一下
OK game over