堆排序
堆排序(heapsort)也是一种相对高效的排序方法,堆排序的时间复杂度为O(n lgn),同时堆排序使用了一种名为堆的数据结构进行管理。
二叉堆
二叉堆是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆。
如上图显示,(a)是一个二叉堆(最大堆), (b)是这个二叉堆在数组中的存储形式。
通过给个一个节点的下标i, 很容易计算出其父节点,左右子节点的的下标,为了方便,二叉堆中的下标从1开始计算
伪代码如下:
PARENT(i)
// 得到父节点的下标,结果向下取整
return i/2
LEFT(i)
// 得到左子节点的下标
return 2i
RIGHT(i)
// 得到右子节点的下标
return 2i+1
在大多数计算机上可以通过位移操作来快速得到结果:
- LEFT操作: i向左位移一位
- RIGHT操作: i向左位移一位并且在最低位加1
- PARENT操作: i向右位移一位
并且这三个函数最好以宏或者内联函数的形式实现,比如说在c语言中使用宏来实现:
#define PARENT(i) i >> 1 // 取得父节点的下标
#define LEFT(i) i << 1 // 取得左边子节点的下标
#define RIGHT(i) (i << 1) + 1 // 取得右边子节点的下标
二叉堆的分类
二叉堆分为两类:最大堆和最小堆
最大堆除了根节点外都要满足一下性质:
A[PARENT(i)] >= A[i]
最小堆除了根节点外都要满足一下性质:
A[PARENT(i)] <= A[i]
在堆排序算法中我们使用的是最大堆
堆排序算法
堆排序算法主要应用到三个函数:
void maxHeapify(int [], int); // 用于维护堆的性质
void bulidMaxHeap(int []); // 建堆
void heapSort(int []); // 堆排序
maxHeapify函数
该函数的功能是维护堆的性质,其第一个参数为存放数据的数组,第二个参数为某个节点的下标。该函数的实现如下:
void maxHeapify(int nums[], int i){
int l = LEFT(i); // 取得左子节点的下标
int r = RIGHT(i); // 取得右子节点的下标
int largest; // 当前节点和左右两个子节点中最大的值的下标
if(l <= heapSize && nums[l] > nums[i])
largest = l;
else
largest = i;
if(r <= heapSize && nums[r] > nums[largest])
largest = r;
if(largest != i){
swap(nums+i, nums+largest);
maxHeapify(nums, largest);
}
}
该函数实现了维护堆的性质的功能,首先找出当前的节点和其左右子节点这三个节点中的最大的一个节点,如果当前节点为最大节点则结束程序,否则交换当前节点和三个节点中的最大值,交换后对该节点继续判断,直到满足条件或者该节点是叶子节点的时候。
如图所示当执行maxHeapify(nums, 2)时的过程
bulidMaxHeap函数
此函数的作用是将普通的数组变成一个二叉堆,如下c语言的实现:
void bulidMaxHeap(int nums[]){
int i;
for(i = MAX_N/2; i >= 1; i--){
maxHeapify(nums, i);
}
}
从下往上,从右到左的第一个非叶子节点开始调用maxHeapify函数来维护这个二叉堆
heapSort函数
这个函数实现了排序功能,以下是c语言的实现:
void heapSort(int nums[]){
bulidMaxHeap(nums);
int i;
heapSize = MAX_N;
for(i = MAX_N; i >= 2; i--){
swap(nums + 1, nums + i);
heapSize--;
maxHeapify(nums, 1);
}
}
根据最大堆的性质,根节点总是整个堆中最大的数,所以从当前二叉堆中最后一个叶子节点和根节点交换,完成交换后将最后这个节点从二叉堆中删去,就是heapSize–操作,然后利用maxHeapify维护交换后的二叉堆,循环到只剩一个节点的时候就完成了排序。
基本上堆排序就是这样的,最后是堆排序的动态图和完整的c语言代码
堆排序算法的演示。首先,将元素进行重排,以匹配堆的条件。图中排序过程之前简单的绘出了堆树的结构。
完整的c语言实现代码:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX_N 10
#define PARENT(i) i >> 1 // 取得父节点的下标
#define LEFT(i) i << 1 // 取得左边子节点的下标
#define RIGHT(i) (i << 1) + 1 // 取得右边子节点的下标
void swap(int *, int *); // 交换函数
void maxHeapify(int [], int); // 用于维护堆的性质
void bulidMaxHeap(int []); // 建堆
void heapSort(int []); // 堆排序
int heapSize; // 堆大小,排序时自减
int main(){
int nums[MAX_N + 1]; // 这里下标从一开始
int i;
heapSize = MAX_N;
srand(time(0));
printf("Original array: ");
for(i = 1; i <= MAX_N; i++){
nums[i] = rand() % MAX_N;
printf("%d\t", nums[i]);
}
printf("\n");
bulidMaxHeap(nums);
printf("After bulid heap:");
for(i = 1; i <= MAX_N; i++){
printf("%d\t", nums[i]);
}
printf("\n");
heapSort(nums);
printf("After heap sort:");
for(i = 1; i <= MAX_N; i++){
printf("%d\t", nums[i]);
}
printf("\n");
}
void swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
void maxHeapify(int nums[], int i){
int l = LEFT(i); // 取得左子节点的下标
int r = RIGHT(i); // 取得右子节点的下标
int largest; // 当前节点和左右两个子节点中最大的值的下标
if(l <= heapSize && nums[l] > nums[i])
largest = l;
else
largest = i;
if(r <= heapSize && nums[r] > nums[largest])
largest = r;
if(largest != i){
swap(nums+i, nums+largest);
maxHeapify(nums, largest);
}
}
void bulidMaxHeap(int nums[]){
int i;
for(i = MAX_N/2; i >= 1; i--){
maxHeapify(nums, i);
}
}
void heapSort(int nums[]){
bulidMaxHeap(nums);
int i;
heapSize = MAX_N;
for(i = MAX_N; i >= 2; i--){
swap(nums + 1, nums + i);
heapSize--;
maxHeapify(nums, 1);
}
}