(二叉)堆,是一个数组,可以被看成一个近似的完全二叉树,树上的每一个节点对应于数组中的一个元素。除了最底层之外,该树是完全充满的,而且是从左向右填充,A.length表示数组元素的个数,树的根结点是A[1],
这样给定一个节点的下标i,我们很容易计算出其父节点(i / 2),左子女(2 * i),右子女(2 * i + 1);二叉堆又分为两种形式:最大堆(也称为大根堆)和最小堆(也称为小根堆)。在最大堆中,最大堆性质是指除了根以外的所有节点i都满足: A[PARENT(i)] >= A[i] (即就是根结点的值比左右子女的值都大);最小堆则恰恰相反,最小堆性质是指除了根以外的所有节点i都有:A[PARENT(i)] <= A[i], (即就是最小的元素存放在根结点)。
在堆排序算法中,我们使用的是最大堆,最小堆通常用于构造优先队列。
如图则分别表示了最大堆和最小堆。
本文章为大家介绍堆排序的两种实现方式:第一种则是真真切切的创建一个堆,其步骤大概有以下几步:
1.首先借助队列按层创建出一个完全二叉树,2.对数据域的值用非递归方式调整成一个大根堆,3.在进行堆排序; 第二种则是比较简单的一种方式,所有的操作都在数组上进行,利用下标可以将数组逻辑看成一个完全二叉树,然后用递归方式调整成一个堆,最后进行排序。总的来说,第二种方式比较好(算法导论上也是这么介绍的)。
先来看第一种的实现:
heapsort.h 和heapsort.c 的具体实现如下:
#ifndef _HEAP_H
#define _HEAP_H
#define N 10
typedef struct node{
int data;
struct node *left;
struct node *right;
}Heapnode;
//创建完全二叉树
Heapnode *Creat_tree(int data[],int n,Heapnode ** Q);
//堆排序
void Heapsort(Heapnode *root,Heapnode **Q,int n);
//打印
void Print(Heapnode ** Q,int n);
#endif
#include <stdio.h>
#include <stdlib.h>
#include "tools.h"
#include "heapsort.h"
//创建完全二叉树
Heapnode *Creat_tree(int data[],int n,Heapnode ** Q)
{
Heapnode *root = NULL,*newp = NULL,*cp = NULL;
int front = 0;
int real = 0;
int pa = 1;
int i = 0;
//创建根结点
for(i = 0;i < n;++i){
newp = (Heapnode *)Malloc(sizeof(Heapnode));
newp ->data = data[i];
newp ->right = NULL;
newp ->left = NULL;
if(!root){
root = newp;
}else{
cp = Q[pa];
if(!cp->left){
cp ->left = newp;
}else{
cp ->right = newp;
pa++;
}
}
//入队
Q[++real] = newp;
}
return root;
}
//堆排序
void Heapsort(Heapnode *root,Heapnode **Q,int n)
{
int r = 0,pa = 0;
int tag = 0,t = 0;
r = n;
while(r > 1){
while(1){
//调整成堆
pa = r / 2;
tag = 0;
while(pa > 0){
if(Q[pa] ->data < Q[pa] ->left ->data){
t = Q[pa] ->data;
Q[pa] ->data = Q[pa] ->left ->data;
Q[pa] ->left ->data = t;
tag = 1;
}
if(Q[pa] ->right && Q[pa] ->data < Q[pa] ->right ->data){
t = Q[pa] ->data;
Q[pa] ->data = Q[pa] ->right ->data;
Q[pa] ->right ->data = t;
tag = 1;
}
pa--;
}
if(tag == 0){
break;
}
}
//交换
t = Q[1] ->data;
Q[1] ->data = Q[r] ->data;
Q[r] ->data = t;
if(Q[r / 2] ->right){
Q[r / 2] ->right = NULL;
}else{
Q[r / 2] ->left = NULL;
}
r--;
}
}
//打印
void Print(Heapnode ** Q,int n)
{
int i = 0;
for(i = n;i > 0;--i){
printf("%5d",Q[i] ->data);
}
}
主函数:
#include <stdio.h>
#include <stdlib.h>
#include "heapsort.h"
#include "tools.h"
int main(int argc,char** agrv)
{
Heapnode **Q = NULL;
Heapnode *root = NULL;
int a[] = {3,2,5,8,4,7,9,0,6,1};
int n = sizeof(a) / sizeof(a[0]);
Q = (Heapnode **)Malloc(sizeof(Heapnode *) * (n + 1));
root = Creat_tree(a,n,Q);
printf("排序前:\n");
Print(Q,n);
printf("\n");
Heapsort(root, Q ,n);
printf("排序后:\n");
Print(Q,n);
printf("\n");
return 0;
}
tools.h 和tools.c 的实现如下:
#ifndef _TOOLS_H_
#define _TOOLS_H_
#include <stdio.h>
#include <stdlib.h>
//定义布尔类型
#define TRUE (1)
#define FALSE (0)
typedef unsigned char Boolean;
//定义接口
void *Malloc(size_t size);
void *Realloc(void * ptr,size_t size);
void print_int(void *value);
#endif
#include <stdio.h>
#include <stdlib.h>
#include "tools.h"
void *Malloc(size_t size)
{
void *result = malloc(size);
if(result == NULL){
fprintf(stderr,"the memory is full!\n");
exit(1);
}
return result;
}
void *Realloc(void * ptr,size_t size)
{
void *result = realloc(ptr,size);
if(result == NULL){
fprintf(stderr,"the memory is full!\n");
exit(1);
}
return result;
}
void print_int(void *value)
{
int *p = (int *)value;
printf("%5d",*p);
}
其中Q为一个动态申请的队列,Creat_tree为创建完全二叉树的过程,(借助于队列),Heapsort为堆排序的过程,首先将创建好的二叉树调整成一个完全二叉树,其调整过程为一个非递归。
接下来,我们看程序的执行结果:
虽然实现了排序过程,但是我们是完全没有必要创建出二叉树,我们完全可以当成是逻辑上的二叉树。
接下来我们看第二种实现方式:
heapsort.h 和heapsort.c的具体实现如下:
#ifndef _HEAP_H
#define _HEAP_H
#define N 10
//调整为大根堆的过程
void Max_heapify(int *data,int i, int n);
//从无序的输入数据数组中构造一个最大堆
void Build_max_heap(int *data,int n);
//堆一个数组进行原址排序
void Heap_sort(int *data,int n);
//打印数组
void print_array(int *data,int n);
#endif
#include <stdio.h>
#include <stdlib.h>
#include "tools.h"
#include "heapsort.h"
static void swap(int *a,int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
static int Left(int i)
{
return 2 * i;
}
static int Right(int i)
{
return 2 * i + 1;
}
static int Parent(int i)
{
return i / 2;
}
//调整为大根堆
void Max_heapify(int *data,int i,int n)
{
int left = Left(i);
int right = Right(i);
int largest = 0;
int length = n;
if(left <= length && data[left - 1] > data[i - 1]){
largest = left;
}else{
largest = i;
}
if(right <= length && data[right - 1] > data[largest - 1]){
largest = right;
}
if(largest != i){
swap(&data[i - 1],&data[largest - 1]);
Max_heapify(data,largest,n);
}
}
void Build_max_heap(int *data,int n)
{
int i = 0;
for(i = n / 2;i >= 1;i--){
Max_heapify(data,i,n);
}
}
void Heap_sort(int *data,int n)
{
int length = n;
int i = 0;
Build_max_heap(data,n);
for(i = length;i >= 1;--i){
swap(&data[i - 1], &data[0]);
n--;
Max_heapify(data,1,n);
}
}
void print_array(int *data,int n)
{
int i = 0;
for( i = 0; i < n; ++i){
printf("%5d",data[i]);
}
printf("\n");
}
其中Build_max_heap函数为逻辑上的大根堆,该函数调用了Max_heapify函数,该函数主要是进行调整使得根结点的值大于左右子女的值,Heap_sort为堆排序过程,遍历数组,将逻辑上的大根堆根节点的值和最后一个叶子的值进行交换,则最大值已经位于数组的尾部,然后长度减1,继续调用Max_heapify调整成大根堆,继续循环。
接下来我们看主程序:
#include <stdio.h>
#include <stdlib.h>
#include "heapsort.h"
#include "tools.h"
int main(int argc,char **argv)
{
int data[N];
int i = 0;
srand(time(0));
for(i = 0;i < N; ++i){
data[i] = rand() % 100;
}
printf("排序前:\n");
print_array(data,N);
Heap_sort(data,N);
printf("排序后:\n");
print_array(data,N);
return 0;
}
执行结果如下图:
两种方式的堆排序已经实现,我还是比较喜欢第二种,逻辑上的数据结构,正是因为数组可以利用下标直接访问,使得堆排序变得简单。堆的应用远不止如此,下一节将会介绍优先队列,也是堆的应用,尽管堆排序是比较好的算法,但在实际应用中,快速排序的性能一般会优于堆排序,快速排序也会在以后的博客进行实现,大家敬请期待!!!