堆排序
2012-04-24 14:27
155 查看
/*
ary是存储记录的数组, start是需要调整为大顶堆的根记录下标, end是
它的最后一个叶子记录的下标。
注意,传入的start到end之间的记录,除去根记录,根记录的左右子二叉树都
是大顶堆, 要完全符合大顶堆的性质调用此函数才有效。
下面函数要做的就是调整以start为根记录,end为最后一个叶子记录的完全二叉树为大顶堆。
*/
void Heapify(int ary[], unsigned int start, unsigned int end)
{
unsigned int left = 0;
unsigned int right = 0;
unsigned int max = 0;
left = start * 2;
right = start * 2 + 1;
/* 如果存在左子记录 */
while (left <= end)
{
/* 左右子记录中,先默认左子记录的关键字值最大,保存其下标 */
max = left;
/* 如果左右子记录都存在,改写max使其保存了拥有最大关键字值记录的下标 */
if (right <= end)
{
if (ary[left] < ary[right])
{
max = right;
}
else
{
max = left;
}
}
/* 现在max中是保存了左右子记录中(假设存在)关键字值最大的下标 */
if (ary[start] < ary[max])
{
/* 根记录的关键字值小于左右子记录中关键字值最大的 */
ary[0] = ary[max];
ary[max] = ary[start];
ary[start] = ary[0];
/* 从被交换的子记录中开始调整 */
start = max;
}
else
{
/* 二叉树符合大顶堆性质, 调整结束 */
break;
}
left = start * 2;
right = start * 2 + 1;
}
/* 调整到最二叉树的最后一个叶子结点上, 二叉树也符合大顶堆性质, 调整结束 */
}
/* 进行堆排序, 完成后ary中的记录下标从1到size,它们的关键字值是非递减的 */
void HeapSort(int ary[], unsigned int size)
{
/* 建堆 */
BuildHeap(ary, size);
for (unsigned int i = size; i > 0; i--)
{
/* 把根记录和最后一个叶子记录交换 */
ary[0] = ary[1];
ary[1] = ary[i];
ary[i] = ary[0];
/*
调整从1到i- 1组成的二叉树为大顶堆。
注意:
交换只是破坏了以ary【1】为根的二叉树大顶堆性质, 它的左右
子二叉树还是具 备大顶堆性质。
*/
Heapify(ary, 1, i - 1);
}
}
/*
调整整棵二叉树(完全无序状态)使之成为大顶堆。
策略:
首先这课二叉树的结构是完全二叉树, 我们可以从最后一个非叶子记录调整,
直到整棵二叉树的根记录。
比如二叉树有size个结点记录, 那么最后一个非叶子结点的下标就
是size / 2(取整), 我们就从size / 2, size / 2 - 1, ... ,
直到调整以1为下标的整棵二叉树为大顶堆。
因为以最后一个叶子记录为根的二叉树必定符合调用函数Heapify的条件,
位于同一层的非叶子结点必定也符合调用条件,Heapify函数处理完后这一层后,
上一层的非叶子记录对应的二叉树也符合了调用条件,
这样直到以整棵二叉树的根(即ary【1】)记录为根的二叉树也符合大顶堆的性质,
整个大顶堆就建立起来。
现在整棵二叉树的根记录就是该记录集中关键字值最大的记录。
*/
void BuildHeap(int ary[], unsigned int size)
{
/* 传入下标为i(size / 2 >= i >= 1)的记录为根,
下标为size的最后一个叶子记录的二叉树进行调整 */
for (unsigned int i = size / 2; i > 0; i--)
{
Heapify(ary, i, size);
}
}
/*
算法分析:
1.堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,
它们均是通过调用Heapify实现的。
堆排序的最坏时间复杂度为O(n * lgn)。堆排序的平均性能较接近于最坏性能。
BuildHeap最坏情况下时间复杂度为O(n),但是for循环在最坏情况下却
要O(n * lgn)的时间。
2.由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
3.堆排序是就地排序,辅助空间为O(1)。
4.它是不稳定的排序方法。
*/
ary是存储记录的数组, start是需要调整为大顶堆的根记录下标, end是
它的最后一个叶子记录的下标。
注意,传入的start到end之间的记录,除去根记录,根记录的左右子二叉树都
是大顶堆, 要完全符合大顶堆的性质调用此函数才有效。
下面函数要做的就是调整以start为根记录,end为最后一个叶子记录的完全二叉树为大顶堆。
*/
void Heapify(int ary[], unsigned int start, unsigned int end)
{
unsigned int left = 0;
unsigned int right = 0;
unsigned int max = 0;
left = start * 2;
right = start * 2 + 1;
/* 如果存在左子记录 */
while (left <= end)
{
/* 左右子记录中,先默认左子记录的关键字值最大,保存其下标 */
max = left;
/* 如果左右子记录都存在,改写max使其保存了拥有最大关键字值记录的下标 */
if (right <= end)
{
if (ary[left] < ary[right])
{
max = right;
}
else
{
max = left;
}
}
/* 现在max中是保存了左右子记录中(假设存在)关键字值最大的下标 */
if (ary[start] < ary[max])
{
/* 根记录的关键字值小于左右子记录中关键字值最大的 */
ary[0] = ary[max];
ary[max] = ary[start];
ary[start] = ary[0];
/* 从被交换的子记录中开始调整 */
start = max;
}
else
{
/* 二叉树符合大顶堆性质, 调整结束 */
break;
}
left = start * 2;
right = start * 2 + 1;
}
/* 调整到最二叉树的最后一个叶子结点上, 二叉树也符合大顶堆性质, 调整结束 */
}
/* 进行堆排序, 完成后ary中的记录下标从1到size,它们的关键字值是非递减的 */
void HeapSort(int ary[], unsigned int size)
{
/* 建堆 */
BuildHeap(ary, size);
for (unsigned int i = size; i > 0; i--)
{
/* 把根记录和最后一个叶子记录交换 */
ary[0] = ary[1];
ary[1] = ary[i];
ary[i] = ary[0];
/*
调整从1到i- 1组成的二叉树为大顶堆。
注意:
交换只是破坏了以ary【1】为根的二叉树大顶堆性质, 它的左右
子二叉树还是具 备大顶堆性质。
*/
Heapify(ary, 1, i - 1);
}
}
/*
调整整棵二叉树(完全无序状态)使之成为大顶堆。
策略:
首先这课二叉树的结构是完全二叉树, 我们可以从最后一个非叶子记录调整,
直到整棵二叉树的根记录。
比如二叉树有size个结点记录, 那么最后一个非叶子结点的下标就
是size / 2(取整), 我们就从size / 2, size / 2 - 1, ... ,
直到调整以1为下标的整棵二叉树为大顶堆。
因为以最后一个叶子记录为根的二叉树必定符合调用函数Heapify的条件,
位于同一层的非叶子结点必定也符合调用条件,Heapify函数处理完后这一层后,
上一层的非叶子记录对应的二叉树也符合了调用条件,
这样直到以整棵二叉树的根(即ary【1】)记录为根的二叉树也符合大顶堆的性质,
整个大顶堆就建立起来。
现在整棵二叉树的根记录就是该记录集中关键字值最大的记录。
*/
void BuildHeap(int ary[], unsigned int size)
{
/* 传入下标为i(size / 2 >= i >= 1)的记录为根,
下标为size的最后一个叶子记录的二叉树进行调整 */
for (unsigned int i = size / 2; i > 0; i--)
{
Heapify(ary, i, size);
}
}
/*
算法分析:
1.堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,
它们均是通过调用Heapify实现的。
堆排序的最坏时间复杂度为O(n * lgn)。堆排序的平均性能较接近于最坏性能。
BuildHeap最坏情况下时间复杂度为O(n),但是for循环在最坏情况下却
要O(n * lgn)的时间。
2.由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
3.堆排序是就地排序,辅助空间为O(1)。
4.它是不稳定的排序方法。
*/