堆及堆排序

一 定义

堆是一棵完全二叉树,树中每个节点的值都不小于(不大于)其左右孩子节点的值。

如果父亲节点的值大于或等于孩子节点的值,这样的堆称为大顶堆。这时每个节点的值都是以它为根节点的子树的最大值;反之则称为小顶堆。

堆一般用于优先队列的实现,优先队列默认情况下是大顶堆。

二 基本操作

针对一组无序数组,建堆的过程如下:

(1)首先把数组元素按照树的层序从上往下,从左往右依次排放。

(2)然后开始把节点从上往下的调整。调整节点的时候总是将当前节点V与它的左右孩子比较(如果有的话),假如孩子中存在比节点V的值还大的,就将其中权值最大的那个孩子与节点V交换;交换完毕后继续让节点V(新的节点V)与孩子节点比较,直到节点V的孩子权值都比V小或是V不存在孩子节点。

向下调整

二叉树的序号都是从1开始的,而数组中的元素起始下标为0,为了对应,可以让数组的heap[0]存放一个无意义的数字例如-1,真正的堆元素是a[1]-a[n]

//对heap数组在[low,high]范围进行向下调整
//low是欲调整节点的数组下标,high为堆中最后一个元素的数组下标
//时间复杂度为O(logn)
void downadjust(int low,int high)
{
    int i=low;    //欲调整节点
    int j=2*i;    //左孩子
    while(j<=hign)     //存在孩子节点
    {
        //如果右孩子存在,且右孩子的值大于左孩子
        if(j+1<=high&&heap[j+1]>heap[j])
            j=j+1;     //j存储右孩子下标
        if(heap[j]>heap[i])
        {
            swap_element(heap[j],heap[i]);    //交换值
            i=j;   //i变为欲调整节点
            j=i*2;
        }
        else
            break;
    }
}

建堆

知道了向下调整的流程,那么建堆的过程就很容易了。

假设元素个数为n。有完全二叉树的性质可知,其中叶子节点个数为n/2(向上取整),因此数组下标为【1,n/2(向下取整)】范围内的节点都是非叶子节点,于是从n/2号开始倒着枚举节点,对每个节点i进行范围[i,n]的调整。

//时间复杂度为O(n)
void createheap()
{
    for(int i=n/2;i>=1;i--)
    {
        downadjust(i,n);
    }
}

删除堆顶元素

如果要删除堆顶元素,即堆中最大的元素,并让其保持堆的结构,可以把最后一个元素覆盖堆顶元素,然后对根节点进行调整即可。时间复杂度为O(logn)

void deleteheaptop()
{
    heap[1]=heap[n--];
    downadjust(1,n);
}

向上调整

若往堆中添加一个元素(记住前提是已经有了一个堆了),可以把要添加的元素放到数组后面,然后进行向上调整操作。向上调整总是把欲调整节点与其父节点比较,如果权值比父节点大,就交换其与父节点。知道到达堆顶或是父亲节点的权值较大为止。时间复杂度为O(logn)

//对heap数组在[low,hign]范围进行向上调整
//low一般为1,hign表示欲调整节点的下标
void upadjust(int low,int high)
{
    int i=high;   //i为欲调整节点
    int j=i/2;    //j为父亲节点
    while(j>=low)
    {
        if(heap[j]<heap[i])
        {
            swap_element(heap[j],heap[i]);
            i=j;   //i为欲调整节点
            j=i/2;  //j为父亲
        }
        else
            break;
    }
}

向堆中添加元素

void insert(int x)
{
    heap[++n]=x;
    upadjust(1,n);
}

三 堆排序

堆排序指使用堆结构对一个序列进行排序。这里讨论递增排序。

考虑一个堆来说,堆顶元素是最大的,因此在建堆完成之后,取出堆顶元素,让堆的最后一个元素替换至堆顶,再进行一次针对堆顶元素的向下调整,如此重复,直到堆中只有1个元素为止。

具体实现时,为了节省空间,可以倒着遍历数组,假设,当前访问到i位,将堆顶元素与该位元素交换,接着再[1,i-1]范围内对堆顶元素进行一次向下调整即可。

void heapsort()
{
    createheap();
    for(int i=n;i>1;i--)
    {
        swap_element(heap[i],heap[1]);
        downadjust(1,i-1);
    }
}

四 实例

对数组a[9]={-1,6,8,3,1,5,4,7,2}中的后8个元素排序

#include <iostream>

using namespace std;

void swap_element(int *a,int *b)
{
    int tmp=*a;
    *a=*b;
    *b=tmp;
}

//对heap数组在[low,high]范围进行向下调整
//low是欲调整节点的数组下标,high为堆中最后一个元素的数组下标
void downadjust(int heap[],int low,int high)
{
    int i=low;    //欲调整节点
    int j=2*i;    //左孩子
    while(j<=high)     //存在孩子节点
    {
        //如果右孩子存在,且右孩子的值大于左孩子
        if(j+1<=high&&heap[j+1]>heap[j])
            j=j+1;     //j存储右孩子下标
        if(heap[j]>heap[i])
        {
            swap_element(&heap[j],&heap[i]);    //交换值
            i=j;   //i变为欲调整节点
            j=i*2;
        }
        else
            break;
    }
}

void createheap(int heap[],int n)
{
    for(int i=n/2;i>=1;i--)
        downadjust(heap,i,n);
}

//对heap数组在[low,hign]范围进行向上调整
//low一般为1,hign表示欲调整节点的下标
void upadjust(int heap[],int low,int high)
{
    int i=high;   //i为欲调整节点
    int j=i/2;    //j为父亲节点
    while(j>=low)
    {
        if(heap[j]<heap[i])
        {
            swap_element(&heap[j],&heap[i]);
            i=j;   //i为欲调整节点
            j=i/2;  //j为父亲
        }
        else
            break;
    }
}

void tranverse_heap(int heap[],int n)
{
    for(int i=1;i<=n;i++)
        cout<<heap[i]<<" ";
}

int main()
{
    int a[9]={-1,6,8,3,1,5,4,7,2};
    createheap(a,8);
    for(int i=8;i>1;i--)
    {
        swap_element(&a[i],&a[1]);
        downadjust(a,1,i-1);
    }
    tranverse_heap(a,8);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40123329/article/details/86679813