C语言数据结构--堆的实现和堆排序

        堆,完全二叉数的一种,分为大堆和小堆,如果父节点比子节点大,堆顶元素最大,则是大堆,反之为小堆,以小堆为例(后面演示所用皆为小堆),其结构如下图所示:

        

        抽象的是,堆的实现靠的是顺序表(也难怪可以用它来排序啦),也就是数组,那么,堆是如何通过数组实现的呢?

        首先记住一个公式,在堆的数组中,父下标和子下标的关系为:左子下标 = 父下表*2+1,右子下表在左子下表的基础上加一。一个堆的基本结构如下:

typedef int HPDataType;

typedef struct Heap
{
    HPDataType* _a;
     int _size;
     int _capacity;
}Heap;
void HeapInit(Heap* php)
{
     assert(php);
    php->_a = NULL;
    php->_size = 0;
    php->_capacity = 0;

    
}

        现在有了一个堆的基本结构,那么如何向其中插入数据呢?

        这里介绍一个向上调整算法,以上图树的结构为例,若想插入数据,先将数据置于数组末尾,如插入一个4,就像这样:

        

        然后让它与父节点5比较,如果它比父节点小,就和父节点交换位置,继续向上比较,直到父节点比它小或到堆顶为止

        这样,就成功地向堆中插入了一个节点,具体代码如下:

void AdjustUp(HPDataType* a, int n)
{
    //n代表要向上调整的数据下表
    int child = n;
    int parent = (n - 1) / 2;
    while (parent >= 0)
    {
        if (a[child] < a[parent])
        {
            //交换父子
            HPDataType temp = a[child];
            a[child] = a[parent];
            a[parent] = temp;
            child = parent;
            parent = (child - 1) / 2;

        }
        else
        {
            break;
        }

    }


}

void HeapPush(Heap* hp, HPDataType x)
{    
    //空间不足时记得扩容
    if (hp->_capacity <= hp->_size)
    {
        hp->_a = realloc(hp->_a, (hp->_capacity == 0 ? 4 : 2 * hp->_capacity) * sizeof(HPDataType));
        hp->_capacity = (hp->_capacity == 0 ? 4 : 2 * hp->_capacity);

    }

    hp->_a[hp->_size] = x;

    //向上调整算法
    AdjustUp(hp->_a, hp->_size);
    hp->_size++;

}

        

        然后就是在堆中删除数据,删除数据用的是向下调整算法,是向上调整算法的逆过程,

        为了删除堆顶数据,我们先将堆顶元素和堆尾元素交换位置,然后堆的size,也就是堆的元素个数减一,忽略原堆顶元素,就相当于删了它,现在要用向下调整算法把原堆尾元素拉下来。

        从堆顶开始,堆顶元素和其子元素比较,若子元素比它小,则和子元素交换位置,继续向下比较和交换,直到子元素比它大或到堆底为止,其具体代码如下:

void AdjustDown(HPDataType* a, int n, int size)
{
    //n代表要向下调整的数据下标
    //size代表整个数组元素长度
    HPDataType parent = n;
    HPDataType child = parent * 2 + 1;//得到父亲的左孩子,这里的child是最小的那个child,先假设是左孩子

    while (child < size)
    {
        if (child + 1 < size && a[child] > a[child + 1])
        {
            child++;
        }

        if (a[child] < a[parent])
        {
            //交换
            HPDataType temp = a[child];
            a[child] = a[parent];
            a[parent] = temp;

            parent = child;
            child = parent * 2 + 1;
        }

        else
        {
            break;
        }

    }


}


void HeapPop(Heap* hp)
{
    //交换堆顶和堆底元素
    HPDataType temp = hp->_a[0];
    hp->_a[0] = hp->_a[hp->_size - 1];
    hp->_a[hp->_size - 1] = temp;
    hp->_size--;
    AdjustDown(hp->_a,0,hp->_size);
}

        那么两个堆的核心方法,堆的插入和删除,就成功实现了,还有一点其它的方法,比如堆的销毁,获取堆顶元素等,具体代码如下:

// 堆的销毁
void HeapDestory(Heap* hp)
{
    assert(hp);
    free(hp->_a);
    hp->_a = NULL;
    hp->_size = 0;
    hp->_capacity = 0;
}


// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
    assert(hp);
    assert(hp->_size > 0);
    return hp->_a[0];

}
// 堆的数据个数
int HeapSize(Heap* hp)
{
    assert(hp);
    return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
    return hp->_size == 0 ? 1: -1;

}

        现在,堆的结构已经实现,下面就来研究堆排序吧!

        现在给定一个数组,若要对它进行堆排序,先用向下或向上调整算法将其构建成堆,接下来的步骤和堆顶元素的删除类似,先把堆顶元素与堆底元素互换,再用向下调整算法维持堆结构,直到仅剩堆顶元素为止,具体代码如下:

//堆排序,时间复杂度为nlogn
void HeapSort(int* a, int n)
{

    
    //n代表数组长度
    //建堆,用向上调整算法,时间复杂度为nlogn
    //降序,用小堆,升序,用大堆,这里演示降序
    //for (int i = 0; i < n; i++)
    //{
        //AdjustUp(a, i);

    //}

    //建堆,用向下调整算法,时间复杂度为n
    for (int i = (n - 1 - 1) / 2; i >= 0; i--)
    {
        AdjustDown(a, i ,n);
    }




    for (int i = n - 1; i > 0; i--)
    {
        //交换
        int temp = a[0];
        a[0] = a[i];
        a[i] = temp;
        AdjustDown(a, 0, i);

    }
    



}

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值