(二叉)堆,是一个数组,可以被看成一个近似的完全二叉树,树上的每一个节点对应于数组中的一个元素。除了最底层之外,该树是完全充满的,而且是从左向右填充,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"); }
接下来我们看主程序:
#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; }
两种方式的堆排序已经实现,我还是比较喜欢第二种,逻辑上的数据结构,正是因为数组可以利用下标直接访问,使得堆排序变得简单。堆的应用远不止如此,下一节将会介绍优先队列,也是堆的应用,尽管堆排序是比较好的算法,但在实际应用中,快速排序的性能一般会优于堆排序,快速排序也会在以后的博客进行实现,大家敬请期待!!!