基本思想
堆是一种完全二叉树, 分为大顶堆和小顶堆.
本篇博客我们默认要进行一个升序排序, 那么我们要构建一个大顶堆
堆排序的算法思想就是先构建一个大顶堆之后, 堆顶元素就是该数列最大值, 然后我们将其(即array[0]
)和数组末尾元素(即array[i]
)交换, 然后将剩下的元素(即array[0] 到 array[i - 1]
)重新进行调整为一个大顶堆, 再进行交换(即array[0] 和 array[i - 1]
), 直至排序完毕
根据算法思想我们可以推出, 堆排序是选择排序的一种
选择排序的思想就是:
每次挑选出一个最大 / 最小值, 组成一个有序的子序列
时间复杂度
堆排序的时间复杂度是O(n logn)
初始建堆的过程时间复杂度为O(n)
堆排序过程中对交换后的剩余元素进行堆重建的过程时间复杂度为O(logn)
代码实现
//堆排序
//i结点的父节点下标为(i - 1) / 2,其左子结点下标为2 * i + 1,右子结点下标为2 * i + 2
//调整元素使其成为一个大顶堆/小顶堆
//i是指向的是第i个非叶子结点
void heap_adjust(vector<int>& arr, int i, int len)
{
int tmp = arr[i]; //保存当前结点值
for (int j = 2 * i + 1; j < len; j = 2 * j + 1) //遍历其子结点
{
//j是值较大的子结点下标
if (j + 1 < len && arr[j] < arr[j + 1])
j++;
//如果当前结点比其最大子结点大,就不用调整,break
if (tmp >= arr[j])
break;
//将当前结点等于其最大子结点,这时这两个结点值是一样的
arr[i] = arr[j];
//改变当前结点指向其最大子结点
i = j;
}
//交换,其最大子结点等于刚才的当前结点
arr[i] = tmp;
}
void heap_sort(vector<int>& arr)
{
//从第一个非叶子结点开始调整
//即从下往上,从右到左,将每个非叶子结点当做根节点
//将其和其子树调整为大顶堆
for (int i = arr.size() / 2 - 1; i >= 0; i--)
heap_adjust(arr, i, arr.size());
//不停交换堆顶元素和数组尾元素,再将剩下的元素重新调整编程大顶堆
for (int i = arr.size() - 1; i > 0; i--)
{
swap(arr[0], arr[i]); //交换堆顶元素和数组末尾元素
heap_adjust(arr, 0, i); //对剩下的元素重新进行调整为大顶堆
}
}