本文将介绍另一种平均时间复杂度是O(nlogn)的排序方法——堆排序(Heap Sort)。堆排序使用了一种被称为“堆”的数据结构,这也是它相比其他两种排序方法的特殊之处。堆这种数据结构不仅可以用于排序,也可以用来维护优先级队列。本文最后还简要对比了快速排序和堆排序的优缺点。
堆排序就是升级版的选择排序,选择排序基本就是两个for循环,内循环没循环一次确定列表中最小的元素,之后循环n次就可以确定整个列表的排序。今天重点介绍的时堆排序。
首先介绍的是MaxHeapify函数,这个函数是维护最大堆的关键,她的输入为一个数组A和一个下标i,调用MaxHeapify的时候我们假定left(i)和right(i)的二叉树都是最大堆,但这个A[i]有可能小于孩子,违背了最大堆的性质,这个函数的目的就是通过让A[i]在最大堆中逐级下降,从而使得下标i为根节点的子树符合最大堆的性质。函数采用递归的方式来实现逐级递减,首先和左右孩子相比如果小于孩子则和他交换,之后在调用这个函数处理这个孩子的子树,从而达成目的。
// 堆化,保持堆的性质
// MaxHeapify让a[i]在最大堆中"下降",
// 使以i为根的子树成为最大堆
void MaxHeapify(int *a, int i, int size)
{
int lt = 2*i, rt = 2*i+1;
int largest;
if(lt <= size && a[lt] > a[i])
largest = lt;
else
largest = i;
if(rt <= size && a[rt] > a[largest])
largest = rt;
if(largest != i)
{
int temp = a[i];
a[i] = a[largest];
a[largest] = temp;
MaxHeapify(a, largest, size);//函数的关键,递归调用。
}
}
之后我们就进入了舰队的过程中,因为数组中A([n/2]+1.....n) ps:其中n/2向下取整 都是叶子结点,我们将每个叶子结点都看成只包含一个元素的堆。之后我们对每个非叶子结点都调用一次MaxHeapify就完成了建堆的过程。
// 建堆
// 自底而上地调用MaxHeapify来将一个数组a[1..size]变成一个最大堆
//
void BuildMaxHeap(int *a, int size)
{
for(int i=size/2; i>=1; --i)
MaxHeapify(a, i, size);
}
下面我们进入了今天的主题就是堆排序,HeapSort函数的输入为一个数组,在函数的开始我们调用一次BuildMaxHeap,数组就变成了最大堆,我们将根结点与最后一个结点交换,下次建立最大堆的时候就不会加上已经排好序的结点,之后我们利用一个for循环完成下面的事,循环n-1次,每一次都取出根结点与排序的最后一个结点交换,因为交换之后的堆不符合最大堆的性质,我们每一次都要调用一次MaxHeapify函数来使堆符合最大堆的性质。
// 堆排序
// 初始调用BuildMaxHeap将a[1..size]变成最大堆
// 因为数组最大元素在a[1],则可以通过将a[1]与a[size]互换达到正确位置
// 现在新的根元素破坏了最大堆的性质,所以调用MaxHeapify调整,
// 使a[1..size-1]成为最大堆,a[1]又是a[1..size-1]中的最大元素,
// 将a[1]与a[size-1]互换达到正确位置。
// 反复调用Heapify,使整个数组成从小到大排序。
// 注意: 交换只是破坏了以a[1]为根的二叉树最大堆性质,它的左右子二叉树还是具备最大堆性质。
// 这也是为何在BuildMaxHeap时需要遍历size/2到1的结点才能构成最大堆,而这里只需要堆化a[1]即可。
void HeapSort(int *a, int size)
{
BuildMaxHeap(a, size);
PrintArray(a, size);
int len = size;
for(int i=size; i>=2; --i)
{
int temp = a[1];
a[1] = a[i];
a[i] = temp;
len--;
MaxHeapify(a, 1, len);
cout << "中间过程:";
PrintArray(a, size);
}
}
#include
using namespace std;
// 输出当前堆的排序状况
void PrintArray(int data[], int size)
{
for (int i=1; i<=size; ++i)
cout < a[i])
largest = lt;
else
largest = i;
if(rt <= size && a[rt] > a[largest])
largest = rt;
if(largest != i)
{
int temp = a[i];
a[i] = a[largest];
a[largest] = temp;
MaxHeapify(a, largest, size);
}
}
// 建堆
// 自底而上地调用MaxHeapify来将一个数组a[1..size]变成一个最大堆
//
void BuildMaxHeap(int *a, int size)
{
for(int i=size/2; i>=1; --i)
MaxHeapify(a, i, size);
}
// 堆排序
// 初始调用BuildMaxHeap将a[1..size]变成最大堆
// 因为数组最大元素在a[1],则可以通过将a[1]与a[size]互换达到正确位置
// 现在新的根元素破坏了最大堆的性质,所以调用MaxHeapify调整,
// 使a[1..size-1]成为最大堆,a[1]又是a[1..size-1]中的最大元素,
// 将a[1]与a[size-1]互换达到正确位置。
// 反复调用Heapify,使整个数组成从小到大排序。
// 注意: 交换只是破坏了以a[1]为根的二叉树最大堆性质,它的左右子二叉树还是具备最大堆性质。
// 这也是为何在BuildMaxHeap时需要遍历size/2到1的结点才能构成最大堆,而这里只需要堆化a[1]即可。
void HeapSort(int *a, int size)
{
BuildMaxHeap(a, size);
PrintArray(a, size);
int len = size;
for(int i=size; i>=2; --i)
{
int temp = a[1];
a[1] = a[i];
a[i] = temp;
len--;
MaxHeapify(a, 1, len);
cout << "中间过程:";
PrintArray(a, size);
}
}
int main()
{
int size;
int arr[100];
cout << "Input the num of elements:\n";
cin >> size;
cout << "Input the elements:\n";
for(int i=1; i<=size; ++i)
cin >> arr[i];
cout << endl;
HeapSort(arr, size);
cout << "最后结果:";
PrintArray(arr, size);
}