复习梗概
什么是堆思想?
- 最大堆:树形结构,每一个结点元素都比子结点大
- 堆常常用动态数组存储
- 二叉最大堆能够应用于动态数组,是因为应用了完全二叉树的两个特性:i为当前结点序号,左子结点 = 2i+1,右子结点=2i+2,父结点=floor((i-1)/2),这个是根结点序号为0的情况,这个特质不用死记硬背,想用的时候画个图自己标下序号就试出来了
- 完全二叉树,共n个结点,叶子结点序号从n/2开始(根结点为0)
- 堆常常用来解决求数组里的最大值,最小值问题(Top K)
- 堆的笔记 恋上数据结构
堆排序算法怎么来的?
选择排序算法优化来的
相比冒泡排序,选择排序无法在内循环过程中,通过比较确定前面是否已经形成有序序列,因此我认为这里难以优化
但实际上可以从另一个地方优化:即选取未排序序列最大值这个过程,如果用堆完成的话,时间复杂度只有O(logn)(主要是下滤,结点的高度logn),整体复杂度O(nlogn)
因此,优化后的选择排序算法 即为 堆排序
什么是下滤?代码
- 为了确保堆的特性
- 使不符合堆的特性的某个元素与其子结点元素比较,若子结点元素较大,则交换二者,更新二者索引,不断重复与子结点比较,直到没有子结点或已经比子结点大
- 这里如何处理只有左子结点的结点?看代码
void siftDown(vector<int> &array, int index, int end)
{
//! 这里指对array的index元素进行下滤操作,这里的end指的是未排序的序列的末尾元素的index,因为vector容器的size是私有的不能改
int biggerChildIndex = index * 2 + 1;
while (biggerChildIndex <= end)
{
if (biggerChildIndex + 1 <= end && array[biggerChildIndex] < array[biggerChildIndex + 1])
{
// if确保若该结点存在右子结点,且右结点值大于左结点,则biggerChildIndex更新为右结点的index
//若没有右子结点,该语句不会触发,且biggerChildIndex默认就是左子结点,妙啊
biggerChildIndex++;
}
if (array[index] < array[biggerChildIndex])
{
int temp = array[index];
array[index] = array[biggerChildIndex]; //发现子结点大于该结点,进行调换
array[biggerChildIndex] = temp;
index = biggerChildIndex;
biggerChildIndex = index * 2 + 1; //若发生下滤,则更新index和childIndex,进行下一次下滤比较
}
else
{
break; //若子结点大于该结点,停止本次下滤
}
}
}
什么是建堆?代码
- 用一段数组作为输入
- 建成一个堆(即符合堆特性的数组)
- 具体过程可以由上至下每个结点上滤,也可以由下至上(从非叶子结点开始)每个结点下滤,后者效率较高,详情见恋上数据结构二叉堆笔记
void BuildHeap(vector<int> &array)
{
int index = array.size() / 2 -<