您的位置:首页 > 其它

算法导论—排序算法学习(3)

2015-03-18 17:14 239 查看

三.堆排序

  堆排序与归并排序一样,他们的时间复杂度都是o(log n),但是堆排序优于归并排序且与插入排序相同的是,任何时候都只需要申请常数个临时空间来存储数据。因此,堆排序是集合了归并排序与插入排序优点的一种排序方法。

  

  涉及到堆排序,首先要了解堆这种数据结构。堆是一个数组,它可以被看成一个近似的完全二叉树,树上的每一个节点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且是从左向右填充。需要注意的是,当根节点是从1开始计算个数时,我们很容易计算的到它的父节点,左孩子和右孩子的下标。假设父节点下标是i,左孩子下标应该是2i,右孩子下标应该是2i+1。如果根节点是从0开始计算个数时,左孩子下标应该是2i+1,右孩子是2i+2。

  

  堆可以分为两种形式:最大堆和最小堆。最大堆性质是指除了根以外的所有节点 i 都要满足A[parent[i]]>=A[i];而最小堆性质是指除了根以外的所有节点 i 都要满足A[parent[i]]<=A[i];在堆排序中,我们使用最大堆。

  下面介绍堆排序需要的过程以及代码:

  MaxHeapify是用于维护最大堆性质的重要过程,它的输入为一个数组A和下标 i 。在调用此函数时,我们假定根节点为Left(i)和Right(i)的二叉树都是最大堆,但这时A[i]有可能小于其孩子,这样就违反了最大堆的性质。MaxHeapify通过让A[i]的值在最大堆中逐级下降,从而使得下标 i 为根节点的子树重新遵循最大堆的性质。

void MaxHeapify(int *Array,int num,int i)   //num为数组长度,i为待最大堆化的下标
{
int left,right,mid,media;
left = 2*i+1;           //将该下标对应的左孩子下标赋给left
right = 2*(i+1);        //将该下标对应的右孩子下标赋给right
if(left<=(num-1) && Array[i]<Array[left])
//当左右孩子不为空时,找出父节点与左右孩子之间的最大值
mid = left;
else mid = i;
if(right<=(num-1) && Array[mid]<Array[right])
mid = right;
if(i != mid)            //若父节点不为最大值,则进行交换
{
media = Array[i];
Array[i] = Array[mid];
Array[mid] = media;
MaxHeapify(Array,num,mid);
//将交换过后的值进行递归操作,继续进行最大堆化
}
}


  有了最大堆化的操作过程,将任意一个数组变成一个最大堆的操作就成为可行的了,由二叉树的性质可知,子数组A[0,1,2…(num/2)-1]都不是叶节点,即只需要对这些元素遍历最大堆化即可:

void BuildMaxHeap(int *Array,int num)
{
int i;
for(i=(num/2)-1;i>=0;i--)
MaxHeapify(Array,num,i);
}


  下面是堆排序的过程:

void HeapSort(int *Array,int num)
{
int i,media;
BuildMaxHeap(Array,num);    //首先将一个无序数组建成一个最大堆
for(i=num-1;i>=1;i--)       //此时,数组首元素必为最大元素
{                           //将数组末尾元素与首元素进行交换
media = Array[i];       //交换后则当前数组最大元素位置正确
Array[i] = Array[0];
Array[0] = media;
num -= 1;             //排序好一个元素后将最大堆减少一个节点
MaxHeapify(Array,num,0);
//因为末尾元素此时在根节点需要重新进行最大堆化
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: