(算法)堆与堆排序

主要内容:

1、什么是堆?

2、如何建堆

3、堆排序

4、参考代码

一、什么是堆?

“堆”是个很有趣的数据结构,是个完全二叉树。

“堆”的特性:每个节点的键值一定总是大于(或小于)它的父节点(大于:称为“最大堆”,小于:称为“最小堆”),或者说每个节点总是大于或小于它的子节点。

对于最大堆而言,根节点为最大值;对于最小堆而言,根节点为最小值。

通常“堆”是通过数组来实现的,这样可以利用数组的特点快速定位指定索引的元素。

因此,当我们得知某个节点的索引为 i 时,可以通过以下“堆”的定义很容易地定位到它的子节点和父节点。

在起始索引为 0 的“堆”中: 
1) 堆的根节点将存放在位置 0 
2) 节点 i 的左子节点在位置 2 * i + 1 
3) 节点 i 的右子节点在位置 2 * i + 2 
4) 节点 i 的父节点在位置 floor( (i - 1) / 2 )   : 注 floor 表示“取整”操作 

在起始索引为 1 的“堆”中: 
1) 堆的根节点将存放在位置 1 
2) 节点 i 的左子节点在位置 2 * i 
3) 节点 i 的右子节点在位置 2 * i + 1 
4) 节点 i 的父节点在位置 floor( i / 2 )           : 注 floor 表示“取整”操作 

二、如何建堆?

根据上述对于堆的定义,建立一个堆,首先需要构建一棵完全二叉树,树的节点需要满足:每个节点的值都不大于或不小于其父节点的值,即A[PARENT[i]] >=/<= A[i]。

给定一个无序数组,如何构建一个最大堆呢?

首先,将该无序数组构建为一棵完全二叉树,然后根据每个节点需要满足的条件,进行二叉树的调整。

建立一个最大堆,而建堆的核心内容是调整堆,使二叉树满足堆的定义(每个节点的值都不大于其父节点的值)。

调堆的过程应该从最后一个非叶子节点(最后一个节点的父节点)开始,假设有数组A = {1, 3, 4, 5, 7, 2, 6, 8, 0}。

那么调堆的过程如下图,数组下标从0开始,A[3] = 5开始,分别与左孩子和右孩子比较大小,如果A[3]最大,则不用调整,否则和孩子中的值最大的一个交换位置,在图1中是A[7] > A[3] > A[8],所以A[3]与A[7]对换,从图1.1转到图1.2。

image

所以建堆的过程就是

   for ( i = headLen/2-1; i >= 0; i++)
​      do AdjustHeap(A, heapLen, i)

调堆:

如果初始数组是非降序排序,那么就不需要调堆,直接就满足堆的定义,此为最好情况,运行时间为Θ(1);

如果初始数组是如图1.5,只有A[0] = 1不满足堆的定义,经过与子节点的比较调整到图1.6,但是图1.6仍然不满足堆的定义,所以要递归调整,一直到满足堆的定义或者到堆底为止。

如果递归调堆到堆底才结束,那么是最坏情况,运行时间为O(h) (h为需要调整的节点的高度,堆底高度为0,堆顶高度为floor(logn) )。

调堆的时间复杂度:O(logn)

建堆的时间复杂度:O(n)

关于时间复杂度的分析:参考http://www.cnblogs.com/zabery/archive/2011/07/26/2117103.html

三、如何进行堆排序?

根据“堆”的特性,我们可以从无序序列的最后一个节点的父节点开始,对其进行“下移”操作,直到序列的第一个节点为止。这样就可以保证每个节点都在合适的位置上,也就建立起了一个“堆”。 
但是“堆”并不是一个完全排序的序列,因为“堆”只保证了父节点与子节点的位置关系,但并不保证左右子节点的位置关系。那么我们如何进行“堆排序”呢? 

由于一个“堆”的根节点必然是整个序列中最大的元素,因此对于一个排序的序列而言,每个“堆”中我们只能得到一个有效的元素。如果我们拿掉根节点,再对剩下的序列重新排列组成一个“堆”,反复如此,我们就可以依次得到一个完整的排序序列了。 
当然,为了简化操作,每次我们只需要把根节点与最后一个位置的节点交换,然后把最后一个位置排除之外,对根节点进行“下移”操作即可。

堆排序的时间复杂度:O(nlogn)

关于时间复杂度的分析:参考http://www.cnblogs.com/zabery/archive/2011/07/26/2117103.html

建堆完成之后,堆如图1.7是个大根堆。

将A[0] = 8 与 A[heapLen-1]交换,然后heapLen减一,如图2.1,然后AdjustHeap(A, heapLen-1, 0),如图2.2。如此交换堆的第一个元素和堆的最后一个元素,然后堆的大小heapLen减一,对堆的大小为heapLen的堆进行调堆,如此循环,直到heapLen == 1时停止,最后得出结果如图3。

image

image

四、代码

#include <iostream>

using namespace std;

int leftChild(int i){
    return 2*i+1;
}

int rightChild(int i){
    return 2*i+2;
}

// adjust heap
void makeMaxheap(int *A,int len,int i){
    int left=leftChild(i);
    int right=rightChild(i);
    int largest=i;
    int tmp;

    while(left<len || right<len){
        if(left<len && A[largest]<A[left])
            largest=left;
        if(right<len && A[largest]<A[right])
            largest=right;
        if(largest!=i){
            tmp=A[largest];
            A[largest]=A[i];
            A[i]=tmp;

            i=largest;
            left=leftChild(i);
            right=rightChild(i);
        }
        else
            break;
    }
}

void makeMinheap(int *A,int len,int i){
    int left=leftChild(i);
    int right=rightChild(i);
    int smallest=i;
    int tmp;

    while(left<len || right<len){
        if(left<len && A[smallest]>A[left])
            smallest=left;
        if(right<len && A[smallest]>A[right])
            smallest=right;
        if(smallest!=i){
            tmp=A[smallest];
            A[smallest]=A[i];
            A[i]=tmp;

            i=smallest;
            left=leftChild(i);
            right=rightChild(i);
        }
        else
            break;
    }
}

// build heap
void buildMaxHeap(int* A,int len){
    int begin=len/2-1;
    for(int i=begin;i>=0;i--)
        makeMaxheap(A,len,i);
}

void buildMinHeap(int* A,int len){
    int begin=len/2-1;
    for(int i=begin;i>=0;i--)
        makeMinheap(A,len,i);
}

// heap sort
void heapMaxSort(int* A,int len){
    int hlen=len;
    int tmp;

    buildMaxHeap(A,hlen);

    while(hlen>0){
        tmp=A[hlen-1];
        A[hlen-1]=A[0];
        A[0]=tmp;
        hlen--;
        makeMaxheap(A,hlen,0);
    }
}

void heapMinSort(int* A,int len){
    int hlen=len;
    int tmp;

    buildMinHeap(A,hlen);

    while(hlen>0){
        tmp=A[hlen-1];
        A[hlen-1]=A[0];
        A[0]=tmp;
        hlen--;
        makeMinheap(A,hlen,0);
    }
}
int main()
{
    int A[]={1,3,4,5,7,2,6,8,0};
    int len=sizeof(A)/sizeof(A[0]);

    buildMaxHeap(A,len);
    for(int i=0;i<len;i++)
        cout<<A[i]<<" ";
    cout<<endl;

    int B[]={1,3,4,5,7,2,6,8,0};
    buildMinHeap(B,len);
    for(int i=0;i<len;i++)
        cout<<B[i]<<" ";
    cout<<endl;

    heapMaxSort(A,len);
    for(int i=0;i<len;i++)
        cout<<A[i]<<" ";
    cout<<endl;

    heapMinSort(B,len);
    for(int i=0;i<len;i++)
        cout<<B[i]<<" ";
    cout<<endl;
    return 0;
}

五、参考文章

http://www.cnblogs.com/zabery/archive/2011/07/26/2117103.html

http://wxg6203.iteye.com/blog/668968

原文地址:https://www.cnblogs.com/AndyJee/p/4612821.html