堆排序
前言
堆排序和归并排序一样,时间复杂度都是O(nlg(n)),和插入排序相同的是具有空间的原址性:任何时候都只需要常数个额外的空间元素存储临时数据。因此堆排序可以说是结合了之前讨论的两种排序算法的优点。
(二叉)堆又是一个数组,它可以被堪称一个近似的完全二叉树,树上的每一个节点都对应着数组中的一个元素。从图中可以看到,除了叶节点之外,该树是完全充满的,而且是从左向右的填充。很容易就能够通过一个节点得到其父节点,左孩子和右孩子。
再利用数学归纳法稍加证明,则能够得到下面的结论:对于一个堆a[n]来说,从a[n/2]到a[n]均为叶节点,a[0]到a[n/2-1]的都为根节点。
堆排序原理
最大堆是用来实现堆排序的先决条件,最大堆的概念:在最大堆中,一个节点的值小于等于其父节点的值。,最小堆的概念则正好相反,这里不再赘述。
那么这个最大堆最上面的一个根节点即整个数组最大的节点,利用这一特性即可进行排序,将最大的那个节点和位于堆最后的一个节点值交换,整个数组最大的数就移动到了堆的最后。
在交换了之后会出现最大堆被破坏的问题,因此每次提取最大值后需要对其a[0~n-1]子堆进行维护,即最大堆维护。提取一次,维护一次,直到子堆缩小成一个元素,这时整个堆呈升序排列。
最大堆维护:维护最大堆即针对某一结点来对堆进行调整,使堆呈现最大堆的特性。对于一个节点i,假设其左孩子为left(i),右孩子为right(i),则需要找出a[i],a[left(i)],a[right(i)]中最大值的下标,并记录这个下标为max_index,如果max_index==i,则以i为根节点的树呈最大堆状态,不必理会,否则就将a[max_index]与a[i]交换,再以max_index作为根节点,一直递归查找下去,将下面的子树都调整为最大堆状态。
C语言的实现
//针对节点i进行最大堆的调整
void max_heapify(int* a, int i, int count)
{
int left_index , right_index ;
int max_index = i;
left_index = 2 * i + 1;
right_index = left_index + 1;
if ((left_index <= count) && (a[left_index] > a[max_index]))
max_index = left_index;
if ((right_index <= count) && (a[right_index] > a[max_index]))
max_index = right_index;
if (max_index != i)
{
int t = a[i];
a[i] = a[max_index];
a[max_index] = t;
max_heapify(a, max_index, count);
}
}
//建立最大堆
void build_max_heap(int* a, int count)
{
for (int i = count / 2; i >= 0; i--)
{
//对每个子根节点进行堆调整
max_heapify(a, i, count);
}
}
//开始堆排序
void heap_sort(int* a, int count)
{
int t;
for (int i = count; i >= 1; i--)
{
t = a[0];
a[0] = a[i];
a[i] = t;
max_heapify(a, 0, i - 1);
}
}
void main()
{
int count, *p;
printf("请输入需要排序的数的个数 :");
scanf_s("%d", &count);
p = (int *)malloc(count * 2);
printf("\n请输入需要排序的%d个数字:",count);
for (int i = 0; i < count; i++)
{
scanf_s("%d", p+i);
}
//依据输入的一列数字,建出一个最大堆
build_max_heap(p, count - 1);
printf("建立最大堆后的数组:");
for (int i = 0; i < count; i++)
{
printf("%d ", p[i]);
}
printf("\n\n");
//开始堆排序
heap_sort(p, count - 1);
printf("堆排序后的数组:");
for (int i = 0; i < count; i++)
{
printf("%d ", p[i]);
}
printf("\n\n");
system("pause");
}