堆排序(Heap Sort)

1、堆(Heap)是计算机科学中的一种特殊的树状数据结构:

        若满足以下特性,则可称为堆:“给定堆中任意结点P和C若P是C的父结点,则P的值小于等于(或大于等于)C的值。” 若父结点的值恒小于等于子结点的值,则该堆称为最小堆(min heap);反之,若父结点的值恒大于等于子结点的值,则该堆称为最大堆(max heap)。堆中最顶端的那个结点称为根结点(rootnode),根结点本身没有父结点(parentnode)。平时在工作中,我们将最小堆称为小根堆或小顶堆,把最大堆称为大根堆或大顶堆,

2、假设我们有3,87,2,93,78,56,61,38,12,40共10个元素我们将这10个元素建成一棵完全二叉树

        这里采用层次建树法,虽然只用一个数组存储元素,但是我们能将二叉树中任意一个位置的元素对应到数组下标上,我们将二叉树中每个元素对应到数组下标的这种数据结构称为堆,比如最后一个父元素的下标是N/2-1,也就是a[41,对应的值为78为什么是N/2-1?因为这是层次建立一棵完全二叉树的特性。 可以这样记忆:如果父结点的下标是dad,那么父结点对应的左子结点的下标值是2*dad+1。接着,依次将每棵子树都调整为父结点最大,最终将整棵树变为一个大根堆。

堆排序动画网址:https://www.cs.usfca.edu/~galles/visualization/HeapSort.html

 除了快排,接着就是堆排会在初试中出大题,也会出选择题,所以重要! 

3、代码实战步骤:

        首先我们通过随机数生成10个元素,通过随机数生成,我们可以多次测试排序算法是否正确,然后打印随机生成后的元素顺序,然后通过堆排序对元素进行排序,然后再次打印排序后的元素顺序。
    堆排序的步骤是首先把堆调整为大根堆,然后我们交换根部元素也就是 A[0]和最后一个元素,这样最大的元素就放到了数组最后,接着我们将剩余9个元素继续调整为大根堆,然后交换 A[0]和9个元素的最后一个,循环往复,直到有序。

核心代码:

void AdjustDown1(ElemType A[],int k,int len)
{
    int dad=k;//父亲的下标
    int son=2*dad+1;//左孩子的下标
    while(son<len)
    {
        if(son+1<len && A[son]<A[son+1])//如果左孩子小于右孩子
        {
            son++;//拿右孩子
        }
        if(A[son]>A[dad])//比较孩子和父亲,如果孩子大于父亲,那么就进行交换
        {
            swap(A[son],A[dad]);//孩子和父亲进行交换
            dad=son;//son重新作为dad去判断下面的字数是否符合大根堆
            son=2*dad+1;
        }else{
            break;
        }
    }
}

void HeapSort1(ElemType A[],int len)
{
    int i;
    //就是把堆调整为大根堆
    for(i=len/2-1;i>=0;i--)
    {
        AdjustDown1(A,i,len);
    }
    swap(A[0],A[len-1]);//交换根部元素和最后一个元素
    for(i=len-1;i>1;i--)//i代表的是剩余的无序数的数组的长度
    {
        AdjustDown1(A,0,i);//调整说呢过于元素变为大根堆
        swap(A[0],A[i-1]);//交换根部元素和无序树的数组的最后一个元素
    }
}

完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

typedef int ElemType;
typedef struct{
    ElemType *elem;//存储元素的起始地址
    int TableLen;//元素个数
}SSTable;

void ST_Init(SSTable &ST, int len)
{
    ST.TableLen = len;
    ST.elem = (ElemType *) malloc(sizeof(ElemType)*ST.TableLen);//申请一块堆空间,当数组来使用int i;
    srand(time(NULL));//随机数生成,每一次执行代码就会得到随机的 10个元素
    int i;
    for(i=0;i<ST.TableLen;i++)
    {
        ST.elem[i]=rand()%100;//生成的是 0-99之间
    }
}

//打印数组中的元素
void ST_print(SSTable ST)
{
    for (int i=0; i < ST.TableLen;i++)
    {
        printf("%3d", ST.elem[i]);
    }
    printf("\n");
}

//交换两个元素
void swap(int &a,int &b)
{
    ElemType tmp;
    tmp=a;
    a=b;
    b=tmp;
}

//王道书上调整某个父亲节点
void AdjustDown(ElemType A[],int k,int len)
{
    int i;
    A[0]=A[k];
    for(i=2*k;i<=len;i*=2) {
        if (i < len && A[i] < A[i + 1])//左子节点与右子节点比较大小
        i++;
        if (A[0] >= A[i])
            break;
        else {
            A[k] = A[i];
            k = i;
        }
    }
    A[k]=A[0];
}

void BuildMaxHeap(ElemType A[],int len)
{
    for(int i=len/2;i>0;i--)
    {
        AdjustDown(A,i,len);
    }
}

//王道树上的堆排序
void HeapSort(ElemType A[],int len)
{
    int i;
    BuildMaxHeap(A,len);
    for(i=len;i>1;i--)
    {
        swap(A[i],A[1]);
        AdjustDown(A,1,i-1);
    }
}

//将某个子树调整为大根堆
void AdjustDown1(ElemType A[],int k,int len)
{
    int dad=k;//父亲的下标
    int son=2*dad+1;//左孩子的下标
    while(son<len)
    {
        if(son+1<len && A[son]<A[son+1])//如果左孩子小于右孩子
        {
            son++;//拿右孩子
        }
        if(A[son]>A[dad])//比较孩子和父亲,如果孩子大于父亲,那么就进行交换
        {
            swap(A[son],A[dad]);//孩子和父亲进行交换
            dad=son;//son重新作为dad去判断下面的字数是否符合大根堆
            son=2*dad+1;
        }else{
            break;
        }
    }
}

void HeapSort1(ElemType A[],int len)
{
    int i;
    //就是把堆调整为大根堆
    for(i=len/2-1;i>=0;i--)
    {
        AdjustDown1(A,i,len);
    }
    swap(A[0],A[len-1]);//交换根部元素和最后一个元素
    for(i=len-1;i>1;i--)//i代表的是剩余的无序数的数组的长度
    {
        AdjustDown1(A,0,i);//调整说呢过于元素变为大根堆
        swap(A[0],A[i-1]);//交换根部元素和无序树的数组的最后一个元素
    }
}

int main() {
    SSTable ST;
    ST_Init(ST,10);//初始化
    //ElemType A[10]={3,87,2,93,78,56,61,38,12,40};
    //memcpy(ST.elem,A,sizeof(A));
    ST_print(ST);
    HeapSort1(ST.elem,10);
    ST_print(ST);

    HeapSort(ST.elem,9);
    ST_print(ST);
    return 0;
}

4、时间复杂度与空间复杂度:

        AdjustDown1 函数的循环次数是logzn,HeapSortl函数的第一个for循环了n2 次,第二个for 循环了n次,总计次数是 3/2nlogen 次,因此时间复杂度是O(nlogzn).堆排最好、最坏、平均时间复杂度都是 O(nlogzn);

        堆排的空间复杂度是 O(1),因为没有使用与n相关的额外空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值