目录
一 什么是堆排序?
堆排序就是利用堆的性质来进行的一种高效率的排序算法。
(接下来以排升序为例)
关于堆的性质:堆顶的数据一定是最大或者最小的数据,每次通过选出堆顶的数据再进行调整就可以对数据进行排序了。
二 如何建堆
如何对一个无序的数组建堆呢?这里推荐两种建堆的方法:向上建堆与向下建堆。
1 向下建堆
向下建堆的要保证该节点拥有左右子树,也就是说除了该节点之外,他的左右子树应该是有序的。
那么从宏观的角度来看的话,要求整体有序,就要求他的左右子树有序。要求左右子树有序,就要求左子树的左右子树有序和右子树的左右子树有序……以此类推,直到最后一棵子树。
因此应该从堆底开始排序。由于对叶子节点进行排序实际上没有多大的意义(要对倒数第一棵子树进行排序),因此要从倒数第一个非叶子结点来进行排序。
由于数组一共有size-1个元素,根据二叉树的性质,他的父结点就是(size-1-1)/2,排完了这一棵树就需要对它之前的树进行排序,直到排到第一个结点。
向下调整的时间复杂度:
如图 T(n)=2^0*(h-1)+2^1*(h-2) +2^2*(h-3)+……+2^(h-2)*1
2T(n)= 2^1*(h-1) +2^2*(h-2)+……+2^(h-2)*2+2^(h-1)*1
根据错位相减法,整理,得到:
T(n)=2^h-1-h
又因为n=2^h-1,所以h=log2(n+1),
原式等于n-log2(n+1)
约等于n
2 向上建堆
向上调整建堆其实可以看做是一个插入的过程。
因为第一个数据可以直接看成是堆中的数据,因此不需要进行调整。之后的数据每插入一个数据就要与前面的数据进行比较,如果子节点比父节点要大,那么就需要往上进行调整。之后子节点与父节点迭代。判断循环结束的条件是子节点>0.如果判断父节点的话,会多算一次。
如图:
向上调整的效率分析:
同理可得,
T(n)=n*logn
因此向下调整虽然思路比较复杂,但是他的效率更高
三 如何利用堆的性质进行排序?
通过上面的向上调整与向下调整,已经将无序的数组建立形成了堆。
需要特别说明的是,如果排升序的数列,那么需要建大堆。因为建大堆的话,选出最大的数据放在数组最后一个位置,之后通过向下调整就可以重新建立堆了。(如果建小堆的话,虽然每次都能从堆顶选出最小的数据,之后进行调整后选出次小的数据……但是这样一来的话每次选出堆顶的数据之后需要对剩下的数据进行建堆,时间效率比较低)
堆排序的基本思路其实就是:
每次选出堆顶的数据排在数组的最后面,之后对除了最后已经排好的数据之外对其他的数据进行向下调整建立堆。迭代排出所有的数据,直至第一个数据。
下面展示两种堆排序的算法:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
void print(int* pa, int sz)
{
assert(pa);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", pa[i]);
}
printf("\n");
}
void swap(int* pa1, int* pa2)
{
int tmp = *pa1;
*pa1 = *pa2;
*pa2 = tmp;
}
void Adjustup(int* pa, int child)
{
assert(pa);
int parent = (child - 1) / 2;
while (child > 0)
{
if (pa[child] > pa[parent])
{
swap(&pa[child], &pa[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(int* pa, int size, int parent)
{
assert(pa);
int child = parent * 2 + 1;//默认为左孩子,之后取出左右孩子中较小的一个
while (child < size)
{
if (pa[child + 1] > pa[child] && child + 1 < size)//如果右孩子比左孩子大,那么孩子+1,默认变成较大的孩子,不关心孩子左右孩子,只关心之中相对较大的那个 并且要存在右孩子才能
{
child = child + 1;
}//找出较大的孩子
if (pa[child] > pa[parent])
{
swap(&pa[child], &pa[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}//向下调整,保证排序之后的数组仍然是大堆
void HeapUpSort(int* pa, int sz)
{
assert(pa);
int i = 0;
for (i = 1; i < sz; i++)
{
Adjustup(pa, i);
}//向上调整建大堆排升序
for (i = 1; i <= sz - 1; i++)
{
swap(&pa[0], &pa[sz - i]);
AdjustDown(pa, sz - i, 0);
}
}
int main()
{
int a[] = { 11,55,44,87,62,35,94,56,78,100 };
int* pa = a;
int size = sizeof(a) / sizeof(a[0]);
HeapUpSort(pa, size);
print(pa, size);
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
void print(int* pa, int sz)
{
assert(pa);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", pa[i]);
}
printf("\n");
}
void swap(int* pa1, int* pa2)
{
int tmp = *pa1;
*pa1 = *pa2;
*pa2 = tmp;
}
void AdjustDown(int* pa, int size, int parent)
{
assert(pa);
int child = parent * 2 + 1;//默认为左孩子,之后取出左右孩子中较小的一个
while (child < size)
{
if (pa[child + 1] > pa[child] && child + 1 < size)//如果右孩子比左孩子大,那么孩子+1,默认变成较大的孩子,不关心孩子左右孩子,只关心之中相对较大的那个 并且要存在右孩子才能
{
child = child + 1;
}//找出较小的孩子
if (pa[child] > pa[parent])
{
swap(&pa[child], &pa[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapDownSort(int* pa, int size)
{
assert(pa);
int i = 0;
for (i = (size - 1 - 1) / 2; i >=0; i--)//size-1是最后一个结点的下标,那么他的父结点就是(size-1-1)/2
{
AdjustDown(pa, size, i);
}//排大堆
for (i = 1; i <= size - 1; i++)
{
swap(&pa[0], &pa[size - i]);
AdjustDown(pa, size-i, 0);
}
}
int main()
{
int a[] = { 11,55,44,87,62,35,94,56,78,100 };
int* pa = a;
int size = sizeof(a) / sizeof(a[0]);
HeapDownSort(pa, size);
print(pa, size);
}