目录
堆的概念:
堆本质上是数据结构中二叉树的完全二叉树,它有两种类型分别是大堆和小堆
左节点和右节点在后续中都称为左孩子和右孩子,指向左右节点的节点称为父节点
注意:堆总是一个完全二叉树且堆中的某个节点的值总是大于或者小于父节点的值
大堆:父节点大于或等于孩子
小堆:父节点小于或等于孩子
堆的模拟实现:
正常二叉树都是使用指针指向节点,而完全二叉树与正常二叉树不同,它的性质可以使用数组模拟实现使其在逻辑上可以看做完全二叉树
随便给一个数组:
转换为完全二叉树:
(1)找父节点进行对比(向上调整算法)
通过数组和二叉树图可以看出3的左孩子和右孩子是5和2,在进行向上调整的过程中会不断得寻找父节点进行对比直到下标小于0,用公式:parent = (child - 1) / 2;可以找到父节点
公式可以保证孩子节点能够找到父亲,例如下标0的左右孩子下标分别是1和2
(1-1)/2=0;
(2-1)/2=0.5;
整形除法并不会包含小数所以下标2找父节点也能找得到0,后续节点同理
注意:文章里建的都是小堆,如果建大堆则修改与父节点对比的判断即可
//向上调整 时间复杂度O(N*logN) void AdjustUP(DATA*a,int child) { int parent = (child - 1) / 2; //找到父节点 while(child>=0) { if (a[parent] > a[child]) //与父节点对比 { Swap(&a[parent], &a[child]); //交换 child = parent; //交换后child现在是父节点位置 parent = (child - 1) / 2;; //再继续往上找新的父节点 } else { break; //不成立直接跳出 } } }
建成后的二叉树图:
(2)找孩子节点进行对比(向下调整算法)
通过公式:child=parent*2+1;这样可以找到左孩子,而右孩子只要在左孩子上加一即可,这样就可以直接筛选出左右孩子哪一个更小(大)进行对比
注意:在对比孩子时要注意额外增加一个判断,避免出现越界访问
//向下调整O(N) void AdjustDown(DATA* a, int x, int parent) { int child = parent * 2 + 1; //找左孩子 while (child < x) { if (child+1<x&&a[child + 1] <a[child]) //对比左右孩子和避免越界判断 { child++; } if (a[child] < a[parent]) //和父节点对比 { Swap(&a[child],&a[parent]); parent = child; child= parent * 2 + 1; } else { break; } } }
建成后的二叉树图:
堆排序:
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据
从上面的建堆可以看出我建的是小堆,如果把最小的数1去掉下一次筛出来的数就是次小的数了,
但是这个不能直接将1删去,避免打乱节点之间的关系
错误示范:将值1直接删去,后续节点的关系如下
可以看到原本是兄弟的左孩子和右孩子瞬间就有一个当父节点了,这两者关系就乱了
注意:
想要排降序,建小堆,小堆筛小值
想要排升序,建大堆,大堆筛大值
正确做法:将已经建好的堆的堆顶和最后的叶子节点交换,然后进行向下调整
//排序 int end = x - 1; //数组的个数减一避免越界 while(end>0) { Swap(&a[0], &a[end]); //交换最后一个节点 AdjustDown(a, end, 0); //调整 --end; //交换后最后一个节点就不包含在内了 }
在循环中会将最小的数放到最后面,end--,在下次循环中就不会包含已经放到后面的数,同时交换的最后一个节点也会变成倒数第二个节点,然后将次小值进行交换,这样一直筛选直到停止就可以变成降序
堆排序的特性总结:
1. 时间复杂度:O(N*logN)
2. 空间复杂度:O(1)
3. 稳定性:不稳定
本篇文章到这里就结束了,谢谢你的阅读