算法导论之堆排序

    (二叉)堆,是一个数组,可以被看成一个近似的完全二叉树,树上的每一个节点对应于数组中的一个元素。除了最底层之外,该树是完全充满的,而且是从左向右填充,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张图片

如图则分别表示了最大堆和最小堆。

        本文章为大家介绍堆排序的两种实现方式:第一种则是真真切切的创建一个堆,其步骤大概有以下几步:

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;
}

执行结果如下图:


两种方式的堆排序已经实现,我还是比较喜欢第二种,逻辑上的数据结构,正是因为数组可以利用下标直接访问,使得堆排序变得简单。堆的应用远不止如此,下一节将会介绍优先队列,也是堆的应用,尽管堆排序是比较好的算法,但在实际应用中,快速排序的性能一般会优于堆排序,快速排序也会在以后的博客进行实现,大家敬请期待!!!

你可能感兴趣的:(算法导论之堆排序)