计算机编程技术之堆排序的分析与实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/canvas_kin/article/details/80398618

计算机编程技术之堆排序的分析与实现

排序就是将一系列无序的数据按某个关键字进行有序化。我们最常见的排序基本上都是将数字按递减或者递增关系进行排序。比较主流的排序方法有8种,分别是冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序、二叉排序和计数排序。

排序算法的不同,导致了各种方法的性能指标不同。排序的性能指标主要有算法的复杂性和稳定性。

排序算法的稳定性是指在待排序的序列中,存在多个相同的元素,若经过排序后这些元素的相对词序保持不变,即Xm=Xn,排序前m在n前,排序后m依然在n前,则称此时的排序算法是稳定的。

算法的复杂度包括辅助空间(空间复杂度)、时间复杂度两种,有时候时间复杂度还可以分为最好时间、最坏时间和平均时间这三个指标。算法的复杂性体现在运行该算法时的计算机所需资源的多少上,计算机资源最重要的是时间和空间(即寄存器)资源,因此复杂度分为时间和空间复杂度。辅助空间是评价排序算法的一个重要指标,辅助空间是指除了存放待排序资源之外,执行算法所需要的其他存储空间。通俗的讲就是你使用该算法除了源数据所占用的内存外所需的额外内存。时间复杂度,简单的说就是程序循环执行的总的次数。算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。时间复杂度常用大O符号表述,即O(f(n))。最好时间和最坏时间表示使用该算法进行排序的两种极端情况,而平均时间代表一种算法进行排序的一般情况。

在这里主要说明堆排序的各项指标与实现,以及和其他排序相比的优劣。故先给出各项排序算法的指标。(表1)

表1 排序算法比较

排序方法

最好时间

平均时间

最坏时间

辅助存储

选择排序

O(n^2)

O(n^2)

 O(n^2)

   O(1)

插入排序

O(n)

O(n^2)

 O(n^2)

   O(1)

冒泡排序

O(n)

O(n^2)

 O(n^2)

   O(1)

希尔排序

O(n^1.3)

O(nlogn)

 O(n^2)

   O(1)

快速排序

O(nlogn)

O(nlogn)

 O(n^2)

   O(logn)

堆排序

O(nlogn)

O(nlogn)

O(nlogn)

   O(1)

归并排序

O(nlogn)

O(nlogn)

O(nlogn)

   O(n)

基数排序

O(kn)

O(kn)

O(kn)

   O(n)

堆排序是一种特殊的树形数据结构,其每个节点都有一个值,通常提到的堆都是指一棵完全二叉树,根节点的值小于(或大于)两个子节点的值,同时根节点的两个子树也分别是一个堆。堆排序主要包括两个过程:一是构建堆,二是交换堆顶元素与最后一个元素的位置。堆排序的本质其实是选择排序,是利用完全二叉树进行选择排序。所谓选择排序是从待排序数组中选择最大或最小的一个元素,然后存放到队列的起始位置或终止位置,这取决于具体实现。选择最大的元素称为大堆,选择最小的元素称为小堆。

堆排序的实现步骤为:

1.   将序列构造成一棵完全二叉树 ;

2.   把这棵普通的完全二叉树改造成堆,便可获取最小值 ;

3.   输出最小值 ;

4.   删除根结点,继续改造剩余树成堆,便可获取次小值 ;

5.   输出次小值 ;

6.   重复改造,输出次次小值、次次次小值,直至所有结点均输出,便得到一个排序。

上述步骤是小堆的实现,大堆的实现只要选取最大值即可。

下面我以一个例子来说明排序的实现。

序列:33, 11, 55,88, 99, 66, 77, 44, 22

1.先将这些元素通过数组构造成完全二叉树。如图1所示

图1 完全二叉树

    2.我们采用小堆排序,将各个子节点和父节点进行比较,若子结点小于父结点,则父结点的元素与子结点的元素交换。以此循环。

3.一轮循环后,根结点的元素即是该序列中的最小值(11)。如图2

图2 一轮循环后的完全二叉树

4.将根结点和最后一个结点交换,然后之后的计算对最后一个结点不做处理,相当于将这个结点排除在二叉树外(如图3)。用数组实现的二叉树可以认为是将最小值和数组最后一个元素的值交换,然后数组长度减1。但是数组最后一个元素依然存在数组中,只是暂时不对它进行运算处理。

图3 排除最后一个结点的二叉树

5.剩下的元素再进行一次循环交换以及排除元素,结果如图4

图4 第二次循环交换后的结果

6.进行第三次循环,结果如图5

图5 第三次循环后结果

7.依次循环,直到只剩下一个根结点,结果如图6

图6 所有循环结束后的结果

8.排序结束,在数组中的结果就是数组的第一个元素到最后一个元素分别是:

99 88 77 66 55 44 33 2211

    排序结束后,我们也大值能够总结出堆排序的各项优缺点:

①  堆排序适用于数据量较大的序列排序。对于数据较少的序列来说,堆排序的主要运行时间耗费在创建堆和调整堆上。

②  堆排序的时间复杂度为O(nlogn),他的平均时间、最好时间、最坏时间均为O(nlogn)。

虽然最好时间不算快,但整体来说堆排序的运算效率还是较高的。

③  不稳定,堆排序会根据相同元素子结点的不同改变各个相同元素的位置。所以不稳定。

④  堆排序的辅助空间只有交换的temp变量,有时甚至temp变量都不需要。

以下是堆排序的C语言代码实现:

/*****************************************************
File name:Heapsort    
Auther name:Kin     Version:1.0    Date: 2018.5.13
*****************************************************/
#include <stdio.h>
#include <stdlib.h>

void Swap(int *a, int *b)//交换函数
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

void Print(int *a, int length)//打印函数
{
    int i;

    for (i = 0; i < length; i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");
}

void Minsort(int *a, int parents, int len)
{
    int temp;
    int child;
    for (; parents * 2 + 1 <= len; parents = child)
    {
        temp = a[parents];
        child = 2 * parents + 1;
        if (child + 1 <= len && a[child] > a[child + 1])//比较一个父结点的两个子结点的元素大小
{                                               
            child = child + 1;
        }

        if (a[parents] > a[child])                  //比较父结点的元素和两个子结点中较小的子结点元素
        {                                           
            a[parents] = a[child];
            a[child] = temp;
        }
    }
}

void Heapsort(int *a, int length)//排序函数
{
    int i = 0;

    for (i = length / 2 -1; i >= 0; i--)//第一轮循环排序
    {
        Minsort(a, i, length - 1);
    }

    for (i = length - 1; i > 0; i--)//剩余的循环排序
    {
        Swap(&a[0], &a[i]);//交换根结点元素和数组最后一个元素
        Minsort(a, 0, i - 1);
    }
}

int main(int argc,char **argv)
{
    int a[] = {11, 33, 55, 88, 99, 66, 77, 44, 22};//定义为数组
    int length = sizeof(a) / sizeof(a[0]);//计算序列长度

    Heapsort(a, length);
    Print(a, length);
   
return 0;
}


展开阅读全文

没有更多推荐了,返回首页