一、前言
在实现堆排序之前,我们首先要知道堆的一些相关知识。
1、 简而言之,堆就是一种特殊的完全二叉树,可分为最大堆和最小堆。
2、 最大堆就是该二叉树的每一个双亲结点都大于它的左右孩子结点,最小堆反之。
3、从而堆的根节点必然是所有结点的最大(小)值。
正是由于最值的出现,让我们可以利用堆这个数据结构实现排序操作。
二、堆的创建
在堆排序之前,我们首先要创建一个堆,在这里我们采用向下调整的方法(还有其他方法)
所谓向下调整,就是从根节点开始,沿着二叉树结构逐渐向下与孩子结点进行比较并作出调整,使得每一个双亲节点始终是最大(小)值。
由于排序一般是对数组结构进行操作,我们这里在数组结构的基础之上模拟堆结构(以最大堆为例),并进行向下调整。
代码如下:
1.向下调整(最大堆)
void AdjustDown(int a[],int n,int root)
{
int parent = root; //从根节点开始
int child = 2 * parent + 1; //根节点数组下标从0开始,则左孩子结点为2*parent+1;
//若根节点数组下标从1开始,则左孩子结点为2*parent;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child]) //若右孩子结点存在,且值大于左孩子结点的值,将右孩子与根节点比较
{
child++;
}
if (a[parent] < a[child]) //若堆顶元素不是最大值.交换孩子结点与双亲结点,并将孩子结点作为新的根结点
{
swap(a[parent], a[child]);
parent = child;
child = 2 * parent + 1;
}
else break; //否则则说明堆已建成
}
}
2.建堆(最大堆)
我们可以发现,上述代码中,只有当根节点的左右结点已经为最大堆时,才能实现最大堆的创建。 那对于一般的情况,我们应该如何建堆呢? 事实上,我们只需要从最后一个非叶节点开始,直到整个树的根节点结束,将其作为根节点向下调整,最后就可以得到一个堆了!
代码如下:
void HeapCreat(int a[],int n)
{
for (int i = (n - 1 - 1) / 2; i >= 0; i--) //从倒数第一个非叶节点开始向下调整
{
AdjustDown(a, n, i);
}
}
三、堆排序
通过上述步骤,我们就得到了一个最大堆了,现在我们就可以正式进行堆排序了。
1.原理
实际上,堆排序的原理很简单:还记得前言说的堆顶元素是最大(小)值吗?于是我们就只需要将最大值交换至数组末尾,并再次向下调整堆结构,使得堆顶元素再次成为最大值(除了已经交换至最后的元素),重复上述步骤,最后我们就可以得到一个递增的数列了!
2.代码
void HeapSort(int a[], int n)
{
HeapCreat(a, n); //建堆之后,堆顶即为最大元素
for (int i = n - 1; i > 0; i--) //每次将堆顶元素与数组最后位置交换,然后再次调整堆,使得堆顶每次都是当前最大值
{
swap(a[i], a[0]);
AdjustDown(a, i, 0); //注意每次向下调整至当前元素即可
}
}
四、测试与总结
1.测试
我们最后对我们的堆排序进行测试-------
int main()
{
int a[8] = { 1,7,8,3,9,2,34,4 };
HeapSort(a, 8);
for (int x : a)
{
cout << x << " ";
}
return 0;
}
结果如下:
数组递增,排序完成,测试成功!
2.全部代码
#include<iostream>
using namespace std;
void AdjustDown(int a[],int n,int root)
{
int parent = root;
int child = 2 * parent + 1; //根节点数组下标从0开始,则左孩子结点为2*parent+1;
//若根节点数组下标从1开始,则左孩子结点为2*parent;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child]) //若右孩子结点存在,且值大于左孩子结点的值,将右孩子与根节点比较
{
child++;
}
if (a[parent] < a[child]) //若堆顶元素不是最大值.交换孩子结点与双亲结点,并将孩子结点作为新的根结点
{
swap(a[parent], a[child]);
parent = child;
child = 2 * parent + 1;
}
else break; //否则则说明堆已建成
}
}
void HeapCreat(int a[],int n)
{
for (int i = (n - 1 - 1) / 2; i >= 0; i--) //从倒数第一个非叶节点开始向下调整
{
AdjustDown(a, n, i);
}
}
void HeapSort(int a[], int n)
{
HeapCreat(a, n); //建堆之后,堆顶即为最大元素
for (int i = n - 1; i > 0; i--) //每次将堆顶元素与数组最后位置交换,然后再次调整堆,使得堆顶每次都是当前最大值
{
swap(a[i], a[0]);
AdjustDown(a, i, 0);
}
}
int main()
{
int a[8] = { 1,7,8,3,9,2,34,4 };
HeapSort(a, 8);
for (int x : a)
{
cout << x << " ";
}
return 0;
}
读者可自行分析时间复杂度🙂