1.历史
堆栈是为了减少程序内存占用的问题而发明的
机器上电后,所有的变量都需要copy到内存中运行,但是机器的内存大小一直都是比较有限的,所以堆栈和局部变量两个概念被提出来
2.栈
每次手动创建、删除变量非常麻烦,于是栈被发明出来
3.堆
逻辑概念上一棵完全二叉树,从左往右依次遍历满的树也叫完全二叉树
大根堆:在这棵完全二叉树里,每一棵子树的最大值就是头节点的值
小根堆:。。。
用malloc 申请的内存块,用free来释放
用new 申请的内存块,用delete 来释放
4.heap inset
在堆的最后添加一个节点,然后和父节点比较,如果当前节点大,就交换
如果父节点大,就停止
void heap_insert(int arr[], int n)
{
while(arr[n] > arr[(n-1)/2])
{
swap(arr[n],arr[(n-1)/2]);
n = (n-1)>>1;
}
}
5.heapify(堆化)
堆中找到最大值,并去掉:找最大最很简单,因为本来就是最大堆;
关键在于去掉最大值之后,还要形成最大堆:取堆上的最后一个数字,先放在0位置处, 然后比较0位置和其左右节点的大小,谁大就和谁交换位置,然后对后面的节点执行相同操作,
那什么时候停止呢:左右节点对比父节点的值小时 or 不再有左右节点
heapify //堆化过程
void heapify(int arr[],int index,int heapsize)
{
//index从哪个位置开始做堆化
int left = index*2+1;
while(left<heapsize)
{
int max = left+1<heapsize&&arr[left+1] > arr[left] ? left+1 : left;
max = arr[max] > arr[index] ? max : index;
if(max == index)
break;
swap(arr,max,index);
index = max;
left = index*2+1;
}
}
Heapinsert & heapify 操作时堆操作中最重要的两个操作;
Other:
全局变量在程序执行的整个周期都存在,需要占用一定的内存空间
局部变量使用完之后就把它的内存空间释放掉,即这些变量的生存周期只需要和对应的函数相同即可
另外:由于局部变量在函数中生成,所以函数copy到别的工程里面还能直接用,如果把函数要用到的变量声明在函数外,那么copy的时候就会比较麻烦
应用:
1.最大堆顶节点删除
将堆最后的节点放在根节点,然后对跟节点进行heapify
设现有一个数组,数据全部有序,且符合大根堆的排布,问:如果把其中一个数修改,怎么让他恢复成大根堆的排布
如果修改的这个值比之前大了,那么就对这个值之前的堆进行heapinsert 操作;
如果修改的这个值比之前小了,那么就对这个值之后的堆进行heapify操作;
2.如果完全二叉树一共有N个节点,那么这个树的高度是多少
Log(N)级别
时间复杂度也是和log(N)相关
那么最后的叶节点,基本上会有N/2个数
倒数第二层叶节点,会有N/4个数
T(N) = N/2*1 + N/4*2 + N/8*3 + N/16*4 +…
等式左右两边分别*2 ,然后用2*T(N) - T(N) = T(N) = O(N)时间复杂度
3.堆排序
给定一个数组,首先0 - 0 范围是堆
然后保证0 - 2 范围是堆,同时heapsize一直在变大
使整个数组变成大根堆,然后把最大值和堆上的最后一个值作交换,同时把heapsize--;
意思是:当最大值来到堆最后的置位后,把它断开联系,因为它已经到了正确的位置,不需要和堆有联系;
然后对剩下的堆做heapify,调完之后,继续把堆的最大值和最后位置作交换
//堆排序
void heapsort(int arr[],int length)
{
if(length<2)
return;
for(int i=0;i<length;i++)
{
heap_insert(arr,i) //log(N)
}
int heapsize = length;
swap(arr,0,--heapsize);
while(heapsize>0)
{
heapify(arr,0,heapsize);//log(N)
swap(arr,0,--heapsize); //额外空间复杂度O(1),因为没有申请额外空间
}
}