赞
踩
堆排序是简单选择排序的进阶版。
前面说了,简单选择排序是每次从操作后的数组中选出最值。这个过程中,选择的对象,也就是数组,除了长度会改变外,其他未被处理的元素的相对位置是不变的。也就是没有在处理过程中保留对我们有利的信息。
而堆排序恰恰是实现了保留有用信息的这一点。
我们后续的操作就是基于堆的概念完成的,需要用数组构建堆结构。只针对简单的数据结构来说,堆是一个完全二叉树,并且可以分为大顶堆和小顶堆。
大顶堆:父节点的值大于他任意子节点的值,那么完全二叉树的根节点就是整个数组中的最大值。
小顶堆:父节点的值小于他任意子节点的值,那么完全二叉树的根节点就是整个数组的最小值。
而针对完全二叉树,访问一个节点的父节点和他的左右孩子有如下公式:
父节点:(i - 1)/ 2
左孩子:2 * i + 1
右孩子:2 * i + 2
整个排序过程的要点就是建立堆,交换根节点,维护堆。
在我们建立好堆以后,取出他的根节点,与数组最后一个节点进行交换。这时,最大值就被放到了数组的最后。我们更新数组,在逻辑上去掉此时的最后的一个元素,生成有n-1个元素的新数组。
然后根据新数组去建立堆,由于这时候的堆与初始的堆相比,只是根节点不同,且少了一个元素。故不用去按照建立堆的步骤,只需要去维护堆即可。
维护堆是根据新数组,建立堆是根据原数组,既然最终结果都是生成一个符合要求的堆,那么他们肯定有重合的部分。而前面说到,维护堆是在建立堆的基础上,那么操作更少,我们先来看维护堆。
在交换根节点,维护堆时,只有当前根节点的位置从原来的数组末尾变到了现在的位置,所以从根节入手来维护他。假设当前根节点的下标为 i
首先,根节点的左孩子2 * i + 1是否的值是否小于根节点的值?如果大于,那么就要将左孩子和根节点交换。
如果左孩子小于根节点,那右孩子的值是否小于根节点?如果大于,那么就要将右孩子和左孩子进行交换。
如果交换,那么交换后的堆还是可能不符合要求,所以需要再对交换到左孩子或右孩子的原根节点进行判断。这一步就是递归。
附上代码和注释
// 堆排序是选择排序的优化,选择排序每次寻找最大值或者最小值的方法是遍历剩下的数组 // 而堆排序寻找最大值或最小值的方法是维护堆。但在这个过程中,保留了一些排序的信息 // 也就是每次维护堆的序列与原始序列存在差别,一定程度上加快了寻找最大值或最小值的速度 // 堆排序有三个动作: // 1. 根据无序序列构建堆(大顶堆为例,也就是需要升序,因为把排序后的值放入序列最后)。是自下而上的 // 2. 将顶部元素和数组末尾元素交换(确定最大值排序后的位置),此时破坏了大顶堆, // 则需要维护大顶堆,维护大顶堆的序列是去掉已经排号序的最后一个值的序列,是自上而下进行检查的 // 3. 维护好大顶堆后,继续交换堆顶元素和新序列的最后一个值,重复下去。直到新序列只剩一个值 // 维护堆 void fixHeap(int arr[], int index, int size) { int left = 2 * index + 1; int right = 2 * index + 2; int target = 0; // 左孩子和根节点比较,左孩子比较大就交换 if (left < size && arr[index] < arr[left]) { target = left; } else { target = index; } // 右节点跟target比较,实际上是跟左孩子或父节点比较 // 在左孩子和有孩子还有根节点中,找到最大值 if (right < size && arr[target] < arr[right]) { target = right; } // 上面比较下来,如果当前节点不是父节点,就把他当前节点与父节点交换 if (index != target) { swap(arr[index], arr[target]); // 交换值以后,可能破坏大顶堆的结构,所以递归检查 fixHeap(arr, target, size); } }
维护堆是根据根节点进行的,那么我们建立堆同样可以根据根节点建立,最大的根节点是n/2,最小的根节点是0,那就从n/2到下标0都维护一遍,堆不就建立好了吗
代码
// 建立堆
void buildHeap(int arr[], int length)
{
// int length = sizeof(arr)/sizeof(int);
for (int i = length / 2; i >= 0; i--)
{
fixHeap(arr, i, length);
}
}
分析了建立堆和维护堆,那么堆排序就很简单了。首先,先建立堆,然后把根节点和数组最后一个元素交换;交换后维护新的堆,继续交换。直到最后数组长度为1(在代码中体现就是循环length遍),排序就结束了。
代码
// 堆排序 void heapSort(int arr[], int length) { // 先建立堆 // int length = sizeof(arr)/sizeof(int); buildHeap(arr, length); int j = length; while (j > 1) { // 把顶步元素和序列最后一个元素交换 swap(arr[0], arr[j - 1]); // 上述操作破坏了大顶堆,同时我们也需要下一个最大值 // 所以维护大顶堆,找到剩下队列中的最大值 j--; fixHeap(arr, 0, j); } }
int main()
{
int arr[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
int length = sizeof(arr) / sizeof(int);
heapSort(arr, length);
for (int i = 0; i < length; i++)
{
cout << arr[i] << ' ';
}
cout << endl;
system("pause");
return 0;
}
有问题评论区交流,互相进步。既要有输入,也要有输出。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。