一.堆排序基本概念
再了解堆排序之前,首先要明白堆是什么。
堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;
或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
对于完全二叉树有些性质也要提前进行了解才能充分了解堆排序,这里就不再多说了。
堆排序就是利用堆(假设为大顶堆)进行排序的方法,其思想分为两个部分:
1.大顶堆的初始构建;
2.将堆顶元素移除后,大顶堆的重建。
二.具体实现
堆排序的主要内容就是大顶堆的构建,无论是初始构建还是重构都是这一过程。
1.大顶堆的构建
构建大顶堆的核心思想就是逐步比较双亲结点和孩子结点的值来使得所有双亲结点的值都大于孩子结点
这里通过一个例子来进行理解
构建大顶堆也称为大顶堆的调整,代码如下
//s为堆顶元素的下标
e为末尾元素的下标
void heapadjust(int* a, int s, int e)
{
int tem = a[s]; //tem用来保存堆顶元素
for (int j = 2 * s; j <= e; j *= 2) // 为什么j初始化为2*s、j为什么二倍的递增,这就要了解二叉树的性质,这里的j指的是s的左孩子。
{
if (a[j] < a[j + 1] && j < e) j++; //判断左右孩子谁更大
if (tem >= a[j]) break; //如果双亲大于孩子就无需进行调整,跳出循环
a[s] = a[j]; // 此时双亲小于孩子,使双亲得到孩子的值
s = j; // 更换双亲进入下轮循环比较
}
a[s] = tem; //确定孩子的值
}
2.堆排序的主体框架
因为堆排序主要是大顶堆初始构建和重构
所以排序主体代码也有两部分组成
void sort(int* a, int len)
{
int j;
for (j = (len-1) / 2; j > 0; j--) //大顶堆的初始构建
{
adjust(a, j, len - 1);
}
for (j = len - 1; j > 0; j--) //后续大顶堆重建
{
swap(a, 1, j);//交换堆顶元素和末尾
adjust(a, 1, j - 1);//对剩下的元素进行大顶堆重建
}
}
void swap(int* a, int b, int c)
{
int tem = a[b];
a[b] = a[c];
a[c] = tem;
}
具体实现:
#include<stdio.h>
void sort(int* a, int len);
void swap(int* a, int b, int c);
void adjust(int* a, int s, int e);
int main()
{
int num[] = { 0,5,2,7,6,5,8,9,8,3,58 }; //为了使下标和堆结点对应,所以从第二项开始,第一项初始化为0
int len = sizeof(num) / sizeof(num[0]);
sort(num, len);
for (int i = 1; i < len; i++)
{
printf("%d ", num[i]);
}
return 0;
}
void sort(int* a, int len)
{
int j;
for (j = (len-1) / 2; j > 0; j--)
{
adjust(a, j, len - 1);
}
for (j = len - 1; j > 0; j--)
{
swap(a, 1, j);
adjust(a, 1, j - 1);
}
}
void swap(int* a, int b, int c)
{
int tem = a[b];
a[b] = a[c];
a[c] = tem;
}
void adjust(int* a, int s, int e)
{
int tem = a[s];
for (int j = 2 * s; j <= e; j *= 2)
{
if (a[j] < a[j + 1] && j < e) j++;
if (tem >= a[j]) break;
a[s] = a[j];
s = j;
}
a[s] = tem;
}