处理海量数据的高级排序之——堆排序(C++)

在面对大数据量的排序时(100W以上量级数据),通常用以下三种的排序方法效率最高O(nlogn):快速排序、归并排序,堆排序。在这个量级上,其他冒泡,选择,插入等简单排序已经无法胜任,效率极低,跟前面三种排序差了千百倍,因此不作比较。

这三种排序的平均时间复杂度均为O(nlogn),快速排序,归并排序在面对基本有序序列排序时,效率反会降低。且归并排序需要用到O(n)的临时存储空间。而堆排序没有明显缺点,特别在面对经常会插入新元素的排序需求,堆排序效果最好。

下面是三种排序对100W个无序数组进行排序的时间对比,可以看出在平均情况下,时间效率:快排>归并>堆排序

基础概念                                                                                                                                                                                                       

什么是堆?

:一种数据结构,全称为:二叉堆数据结构,是一种数组对象。

当所有节点都大于各自左右子节点时,叫大顶堆;

当所有节点都小于各自左右子节点时,叫小顶堆。

在堆排序中,使用大顶堆结构。

排序原理                                                                                                                                                                                                       

若输出堆顶的最大值之后,使得剩余n-1个元素的序列重新又建成一个堆,则得到n个元素中个次大值。如此反复执行,便能得到一个有序序列,这个过程就称之为堆排序。

因此堆排序的实现思路,可以细分为两部分:

1、如何将一个无序数组排列成大顶堆(建堆过程)

2、拿走最大值后如何从剩下的堆中找出次大值,重新建立大顶堆(筛选过程)

时间复杂度                                                                                                                                                                                                    

堆排序可分细分为两部分:建堆过程+排序过程。

建堆过程时间复杂度为O(n),即将一个无序数组建立成堆只需要线性时间。

排序过程需要对n个数据进行筛选时,每次筛选需要O(logn)时间,所以整个排序过程的时间为O(nlogn)

因此堆排序总的运行时间为: O(nlogn) = O(n) + O(nlogn)

算法实现                                                                                                                                                                                                        

#include "stdafx.h"
#include <iostream>
#include <ctime>
using namespace std;

int a[1000000];

#define BEGIN_RECORD            
{                                
clock_t ____temp_begin_time___;    
____temp_begin_time___=clock();

#define END_RECORD(dtime)        
dtime=float(clock()-____temp_begin_time___)/CLOCKS_PER_SEC;
}

/*
    目标:筛选区域为以索引i为树根的子树,找出该子树最大值,将其存放到索引i
    过程:从索引为i的结点开始往下,与较大的子节点交换值,向下搜索直到子树底部
    a - 待排序数组
    i - 筛选起始结点索引
    len - 排序元素数量
*/
void sift(int a[], int i, int len)    
{
    int temp = a[i];
    int j = 2 * i;

    while(j <= len)
    {
        if(j < len && a[j] < a[j+1])    //如果右结点比左结点大,则拿右结点跟父节点比较
            j++;
        if(a[i] < a[j])                 //如果子节点比父节点大,则两者交换值,子节点成为新的父节点,继续向下筛选
        {
            a[i] = a[j];
            a[j] = temp;
            i = j;
            j = 2 * i;
        }
        else                            //如果父节点比子节点大,则说明找到了该子树的最大值,结束筛选
        {
            break;
        }
    }
    a[i] = temp;
}

/*
堆排序(大顶堆)
a - 待排序的数组
len - 数组长度
*/
void heapSort(int a[], int len)
{
    int temp;
    int i;

    for (i = len-1; i > 0; i--)      //堆排序只能从下标为1开始排序,因此要把数组所有数据后一移位。下标0的数据不处理
    {
        a[i] =  a[i - 1];
    }
    

    for (i = len/2; i >= 1; i--)     //建堆过程(使得全树的父节点都比子节点大)
    {
        sift(a, i, len);
    }
    for (i = len - 1; i >= 2; i--)   //排序过程:每次从树根取值(该值必为最大值),放到树的最后一个结点n,并把该结点从树中移除。重复排序过程,直到将所有结点从树移除,排序结束
    {
        temp = a[1];
        a[1] = a[i];
        a[i] = temp;
        sift(a, 1, i - 1);        //从树根取出最大值,取最尾树结点放到树根,此时树根不再为最大值,需要再对树根进行一次筛选过程,以确保树根仍然为最大值
    }
}


void printArray(int a[], int length)
{
    cout << "数组内容:";
    for(int i = 0; i < length; i++)
    {
        if(i == 0)
            cout << a[i];
        else
            cout << "," << a[i];

    }
    cout << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
    float tim;
    BEGIN_RECORD

    //int a[1000000];
    for (int i = 0; i < 1000000; i++)
    {
        a[i] = int(rand() % 100000);
    }

    //printArray(a, sizeof(a)/sizeof(int));
    heapSort(a, sizeof(a)/sizeof(int));
    //printArray(a, sizeof(a)/sizeof(int));

    END_RECORD(tim)
    
    cout << "运行时间:" << tim << "s" <<  endl;

    system("pause");
    return 0;
}
原文地址:https://www.cnblogs.com/leoin2012/p/3908311.html