堆排序(C语言版)

来源:互联网 发布:linux 播放器剪辑 编辑:程序博客网 时间:2024/05/01 17:00

既然是堆排序,那么我们先来看看什么是

       n个元素序列{k1,k2...ki...kn},当且仅当满足下列关系时称之为堆:
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)

       若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:
树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。

堆分为大顶堆(ki >= k2i,ki >= k2i+1)和小顶堆(ki <= k2i,ki <= k2i+1)。

堆排序的思想

       利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

堆排序算法的步骤如下

       1.将待排序列R[1]R[2]....R[n]构建为大顶堆;

       2.将堆顶元素R[1]和最后一个元素交换;

       3.由于交换后有可能会破坏堆的性质,所以要进行自顶向下进行调整,使剩余元素成为新的大顶堆;

       4.重复执行第二步和第三步直至n减小为1,则整合数列排序完成。

稳定性和复杂度:

       堆排序是不稳定的排序算法;平均时间复杂度、最优时间复杂度和最差时间复杂度都是O(n*logn),最优空间复杂度为O(1),最差空间复杂度为O(n)。

       下面是我从《算法:C语言实现》中看到的代码,有一些改动,如果有什么错误的地方,请大家指出,谢谢。

#include <stdio.h>#include <assert.h>#include <stdlib.h>#include <time.h>#define RANDMAX 1000000void getRandArray(int array[], int size);void heapSort(int array[], int size);void fixDown(int array[], int index, int size);void swap(int *value1, int *value2);void isSorted(int array[], int size);int main(int argc, char const *argv[]){    int size = 0;    scanf("%d", &size);    assert(size > 0);    int *array = (int *)calloc(size, sizeof(int));    getRandArray(array, size);    clock_t begin;    clock_t end;    begin = clock();    heapSort(array, size);    end = clock();    //打印排序所花费的时间,在linux下单位为ms    printf("heapSort: %ldms\n", (end - begin) / 1000);    isSorted(array, size);    free(array);    return 0;}//利用伪随机数填充数组arrayvoid getRandArray(int array[], int size){    assert(array != NULL && size > 0);    srand((unsigned) time(NULL));    int i = 0;    for (i = 0; i < size; ++i) {        if (rand() % 2 == 0) {            array[i] = rand() % RANDMAX;        } else {            array[i] = -rand() % RANDMAX;        }    }}//堆排序,利用大顶堆,从小到大排序void heapSort(int array[], int size){    //让heapPointer指向array第一个元素的前一个位置    int *heapPointer = array - 1;    //自底向上调整数组,使其成为一个堆    int index = 0;    for (index = size / 2; index > 0; --index) {        fixDown(heapPointer, index, size);    }    //交换堆顶元素和最后一个元素并调整堆结构    while (size > 1) {        swap(&heapPointer[1], &heapPointer[size--]);        fixDown(heapPointer, 1, size);    }}//从下标为index的节点开始向下调整,使树变成堆有序的void fixDown(int array[], int index, int size){int i = index;int j = 2 * index;    while (2 * i <= size) {//当下标为i的节点有孩子时    j = 2 * i;//让j指向左孩子    //当i有右孩子并且右孩子比左孩子大时,让j指向右孩子        if (j + 1 <= size && array[j] < array[j + 1]) {            ++j;        }        //如果待调整元素的值大于等于较大孩子时,调整结束退出循环        if (array[i] >= array[j]) {        break;        }        //否则交换待调整元素和其较大子节点        swap(&array[i], &array[j]);        i = j;//让i指向调整后的待调整节点    }}void swap(int *value1, int *value2){    int tempValue = *value1;    *value1 = *value2;    *value2 = tempValue;}//判断数组array是否已经是有序的void isSorted(int array[], int size){    assert(array != NULL && size > 0);    int unsorted = 0;    int i = 0;    for (i = 1; i < size; ++i) {        if (array[i] < array[i - 1]) {            unsorted = 1;            break;        }    }    if (unsorted) {        printf("the array is unsorted!\n");    } else {        printf("the array is sorted!\n");    }}

heapSort函数里有一个小技巧

因为堆排序里堆的开始下标为1,而数组的开始下标为0,这就给堆排序造成了一定的麻烦

       要不让客户程序在传参时约定数组的第一个元素不在排序范围内,虽然可以,但是这样会导致其他人调用这个接口时不知道这个约定而造成排序不充分的错误;或者在heapSort函数里重新申请一个大小为size+1的数组,用这个新申请的空间作为堆,虽然可以,但是这样就牺牲了堆排序不需要额外空间的优点了;或者把arra[1...size-1]进行堆排序,然后在堆排序结束后用插入排序把array[0]插入到正确的位置,这个方法看上去比上面两个要好,那么有没有更好的方法呢?答案是有的:

       定义一个指向数组第一个元素的前一个位置的指针,这样就可以把这个指针想象成一个大小为size+1的数组,并且可以对这个数组的下标从1size的元素(也就是array下标从0size-1的元素)进行堆排序了。

其他八种排序算法的博客:

常见的9种内部排序(C语言实现)


1 0
原创粉丝点击