堆排序
堆排序算法简介:
堆可以看为一颗完全二叉树,满足,任何一个非叶结点的值都不大于(或不小于)其左右孩子结点的值。若父亲大孩子小,则称为大顶堆;若父亲小孩子大,则称为小顶堆。
根据堆的定义,其根结点值是最大的(或最小的),因此,将一个无序序列调整为一个堆,就可以找出这个序列的最大(或最小)值,然后将找出的这个值交换到序列的最后(或最前),这样有序的序列元素增加1,无序序列元素减少1,重复操作,就可以实现排序。
举例:
原始序列:49,38,65,97,76,13,27,49(1)
构建大顶堆:原始序列对应的完全二叉树为:
结点76,13,27,49(1)是叶子结点,没有左右孩子,所以满足堆的定义。从97开始调整,按97,65,38,49的顺序依次调整非叶子结点。
(1)调整97,因为97>49(1),所以97和它的孩子49(1)满足堆的定义,不需要调整;
(2)调整65,同97;
(3)调整38,38<97, 38<76, 不满足大顶堆的定义,需要调整,因为它的左右孩子都大于它本身,应该和两者最大的那个交换,即和97交换。因为如果和76交换,76<97还是不满足大顶堆的定义。又38和97交换后,38成了49(1)的根结点,且38<49(1),所以继续交换,变为:
(4)调整49,原理同调整38的步骤,调整后结果:
此时我们已经建立一个大顶堆,对应序列为:97,76,65,49(1),49,13,27,38。然后将堆顶97和序列的最后一个元素38交换。第一趟堆排序完成,97达到最终的位置。将除97外的其他序列,再次调整为大顶堆。现在只需要调整顶点38即可(因为其他的元素没有变化,满足大顶堆的定义)。
调整38后:
现有序列:76,49(1),65,38,49,13,27,97。交换堆顶76和序列最后一个元素27,序列变为:27,49(1),65,38,49,13,76,97,则76达到最终的位置。,依次重复上述步骤直到完成排序。
代码:
#include <iostream>
using namespace std;
void Sift(int a[], int low, int high) {
int left = low * 2 + 1;//左孩子
int right = left + 1;//右孩子
int index = low; //记录当前位
while (left < high && a[left] > a[index]) index = left;
while (right < high && a[right] > a[index]) index = right;
if (index != low) {//位置有调整
int temp = a[index];
a[index] = a[low];
a[low] = temp;
Sift(a, index, high);
}
}
void heap(int a[], int n) {
if (n <= 0) return;
for (int i = n / 2 - 1; i >= 0; --i) {
//建立最大堆,将堆中最大的值交换到根节点
Sift(a, i, n);
}
for (int i = n - 1; i >= 1; --i) {
//将当前堆的根节点交换到堆尾的指定位置
int temp = a[0];
a[0] = a[i];
a[i] = temp;
//建立下一次的最大堆
Sift(a, 0, i);
}
}
int main()
{
int a[8] = { 49,38,65,97,76,13,27,49 };
int n = 8;
heap(a, n);
for (int i = 0; i < n; i++)
{
cout << a[i] << " ";
}
cout << endl;
return 0;
}
结果:
时间复杂度分析
对于Sift()函数,完全二叉树的高度为
空间复杂度分析
只需要一个额外的空间temp,因此空间复杂度为