代码改变世界

堆的实现、堆排序及其应用

2013-10-23 02:10  Ross Wang  阅读(4072)  评论(0编辑  收藏  举报

堆排序和其他排序算法的比较

 

上图引自:http://hi.baidu.com/ycdoit/item/6b5f5b9571a843becc80e560,这篇文章也解释了可能的优化途径。

(sort 是STL的排序方法,qsort是库函数,quicksort是没有经过优化的快速排序实现,Heapsort是一般的堆排序实现)

 

堆的定义

谈堆排序首先要有堆,堆类似于一种特殊的完全二叉树,分为最大堆和最小堆。

最大堆:所有结点的值都不大于其父结点。

最小堆:所有结点的值都不小于其父结点。

关于堆的定义和特性,请参考http://blog.csdn.net/v_JULY_v/article/details/6198644,非常好的一篇关于堆排序的文章。

 

堆的实现

谈这个话题是写这篇文章的主要原因。

我们常说程序就是数据结构和算法的组合,具体到堆的实现上,算法体现出了非常强大的抽象作用,表现在:

1. 存储最小堆或者最大堆的数据结构就是一个简单的数组。

2. 算法实现的时候,把这个数组看作一种完全二叉树:

    (1) 从根结点开始(编号为1),分层遍历这颗二叉树,每个结点的编号是上一个遍历到的结点编号+1,就是说:

                                             1

                                         /       \                                      

                                      2           3

                                    /   \                        

                                 4       5

 

    (2) 二叉树各个结点按照编号顺序存在数组中。

    (3) 对于完全二叉树来说,下面的条件成立:

  • 第i个结点的父结点为第|_i/2_|个结点(|_i/2_|表示对i/2的结果向下取整)。
  • 第i个结点的左孩子结点为第2i个结点。
  • 第i个结点的右孩子结点为第2i+1个结点。

    带着上面的逻辑来实现最小堆和最大堆,就是把父结点和左右子结点的大小关系映射为数组中第i个数据和第2i以及第2i+1个数据的大小关系。

这里我们没有定义二叉树的数据结构,而是在处理数组的时候将其看作分层遍历的完全二叉树结点,具体表现在算法的实现上面。

很多人在想到堆的时候就想到二叉树的数据结构,进而认为堆的实现就基于这种数据结构,堆排序也是对二叉树的排序,这是不准确的,其实我们的操作,包括创建一个最小堆或最大堆,以及进行堆排序都是在上面映射关系的基础上,用处理数组元素的方式进行的。

当然,可以用二叉树的数据结构,但是考虑到这种数据结构相比数组而言,每个结点的大小增加了,而堆排序的算法却没有优化,还是平均N*log2(N)(N为结点数量),必要性在哪里?

所以,有时候一种不同的对待事物的方式可以极大提高我们的编程效率,这就是算法的威力所在。

 

寻找M个数中的前K个最小的数并保持有序

这个算法题在这篇文章中得到了精彩详尽的讨论,强烈推荐感兴趣的同学学习一下。

这里我们使用最大堆来解决:

1. 用M个数里面的前K个数创建一个K个元素的最大堆。

2. 将第M-k个数到第M个数依次和上面最大堆的第一个元素(存储这个最大堆的第1个数据,也即是抽象的完全二叉树的根结点数据)比较,如果大于等于堆顶元素,继续下一个数进行比较(堆顶元素在最大堆中最大),如果小于堆顶元素,就把堆顶元素值设置为这个数,然后重新对这个堆进行重新最大堆化,使之符合最大堆的要求。

3. 结束之后最大堆里面放的就是最小的K个数了,这时对最大堆进行堆排序,就解决这个问题了。

时间复杂度为 :

O(K)                                           +                         (M - K)*log2(K)

|                                                                              |

创建K个元素最大堆的时间复杂度                       对剩余M-K个数据进行比较并每次对最大堆进行重新最大堆化

 

在K远小于M的时候,时间复杂度相当于M*log2(K)。

 

完整代码实现:

P.S.如果需要在M非常大的情况下测试,可以考虑将M个数据保存在文件中,然后依次读取进行处理。

#include <iostream>
using namespace std;

/******************
根据输入的int数组创建一个最大堆
input:      输入的int数组
maxHeap:     最大堆数组
maxHeapCount 最大堆中元素的个数
******************/
void createMaxHeap(const int * input, int * maxHeap, const int maxHeapCount);

/******************
对输入int数组进行操作,使之符合最大堆的条件:
所有结点的值不大于其父结点

maxHeap:     最大堆数组
pos:         最大堆中的元素标志位,由1开始
maxHeapCount 最大堆中元素的个数
******************/
void heapifyMaxHeap(int * maxHeap, const int pos, const int maxHeapCount);

/******************
对最大堆排序,使之由小到大有序

maxHeap:     最大堆数组
maxHeapCount 最大堆中元素的个数
******************/
void maxHeapSort(int * maxHeap, const int maxHeapCount);


/******************
根据指定的长度初始化输入的int数组
bigData:    输入的int数组
arrayLength: 数组长度
******************/
void initBigDataArray(int * bigData, const int arrayLength);

void main()
{
    const int M = 1000;
    const int K = 5;
    int bigDataM[M];
    int maxHeap[K];

    initBigDataArray(bigDataM, M);
    createMaxHeap(bigDataM, maxHeap, K);

    for(int step = K; step < M; ++step)
    {
        if(bigDataM[step] < maxHeap[0])
        {
            maxHeap[0] = bigDataM[step];
            heapifyMaxHeap(maxHeap, 1, K);
        }
    }

    maxHeapSort(maxHeap, K);

    cout<<"bigData array is: ";
    for(int step = 0; step < M; ++step)
    {
        cout<<bigDataM[step]<<" ";
    }
    cout<<endl;

    cout<<"Output maxHeap from less to larger: ";
    for(int step = 0; step < K; ++step)
    {
        cout<<maxHeap[step]<<" ";
    }
    cout<<endl;

    cout<<"Output maxHeap from larger to less: ";
    for(int step = 0; step < K; ++step)
    {
        cout<<maxHeap[K - 1 - step]<<" ";
    }
    cout<<endl;

    return;
}

void heapifyMaxHeap(int * maxHeap, const int pos, const int maxHeapCount)
{
    int left = 2 * pos;
    int right = 2 * pos + 1;
    int largestElemPos = 0;
    if(left <= maxHeapCount && maxHeap[left - 1] > maxHeap[pos - 1])
        largestElemPos = left;
    else
        largestElemPos = pos;

    if(right <= maxHeapCount && maxHeap[right - 1] > maxHeap[largestElemPos - 1])
        largestElemPos = right;
    
    if(largestElemPos != pos)
    {
        swap(maxHeap[pos - 1], maxHeap[largestElemPos - 1]);
        heapifyMaxHeap(maxHeap, largestElemPos, maxHeapCount);
    }


}

void createMaxHeap(const int * input, int * maxHeap, const int maxHeapCount)
{
    for(int step = 0; step < maxHeapCount; ++step)
    {
        maxHeap[step] = input[step];
    }

    for(int startHeapifyPos = maxHeapCount/2; startHeapifyPos >= 1; --startHeapifyPos)
    {
        heapifyMaxHeap(maxHeap, startHeapifyPos, maxHeapCount);
    }
}

void initBigDataArray(int * bigData, const int arrayLength)
{
    for(int i = 0; i< arrayLength; ++i)
    {
        bigData[i] = rand();
    }
}

void maxHeapSort(int * maxHeap, const int maxHeapCount)
{
    int maxHeapSize = maxHeapCount;
    for(int step = maxHeapSize; step >=2; --step)
    {
        swap(maxHeap[step - 1], maxHeap[0]);
        maxHeapSize -=1;
        heapifyMaxHeap(maxHeap, 1 , maxHeapSize);
    }
}

 

如有任何错误或者不足,欢迎不吝指出。