堆(一)——删除堆顶元素、插入元素、堆排序

堆的结构特点

堆排序是一个可以高效实现排序的算法,复杂度是O(nlogn)和之前的快速排序与归并排序的复杂度相同,而它高效的排序依赖于它的结构特点

  • 堆是一个完全二叉树
  • 堆中每一个节点的值都必须大于等于(或者小于等于)其子树中每个结点的值

第二点是说明每个节点都必须大于等于(或者小于等于)其左右子结点的值。

其中大于等于其左右节点的堆叫大顶堆,小于等于其左右节点的堆叫小顶堆

堆的存储结构分析

由于堆是一个完全二叉树,所以它可以用非常简便的数组来进行存储。

数组存储的元素就是节点的值,而数组下标位置就代表着该结点在堆中的位置

img

由此我们可以得出一个与下面算法实现息息相关的结论

如果某一个结点存储时在数组中下标为i

  • 它的父节点位置在i/2
  • 它的左子结点位置在2i
  • 它的右子结点位置在2i+1

堆的相关算法

插入元素
过程思路

在数组末尾插入元素,再判断是否符合堆的特点,即是否小于父节点,如果不小于,跟父节点交换,交换直到小于父节点。这个过程称为堆化,且是过程是自下而上

img
代码实现
void insert_node(int a[],int val,int cnt)
{
    int i=(cnt+1)/2;	//也可以只用一个变量i
    int j = cnt+1;
    a[j] = val;			//a[cnt+1]=val
    while(a[i]<a[j] && i>0)	//    while(a[i/2]<a[i] && i/2>0)
    {
        swap(a,j,i);			//swap(a,i,i/2)
        i = i / 2;
        j = j / 2;
    }
}
void swap(int a[], int m, int i)
{
    int temp = a[i];
    a[i] = a[m];
    a[m] = temp;

}
删除堆顶元素
过程思路

删除一个元素,由于堆的完全二叉树的特点限制,要提防出现最后一层断层,即不连续。

img

所以采用的方法是把最末的元素替换堆顶再进行堆化,这样得到的新堆肯定是符合其自身特点的。很明显,这里是从上往下的堆化过程。

img
代码实现
void heaping_(int a[],int cnt,int i)    //一次该函数调用,可以把堆从i结点开始到堆底的全部堆化
{
    while (true)
    {
        int maxpos = i;              
        if(a[2 * i] > a[maxpos]&& 2 * i <= cnt)maxpos = 2 * i;  //是否左节点会大于该节点
        if (a[2 * i + 1] > a[maxpos] && 2 * i <= cnt)maxpos = 2 * i + 1;    //是否右节点会大于该节点/左节点,注意这里是maxpos不是i因为右节点可能会大于左节点,
        if(i==maxpos)break;         //如果该节点大于左节点又大于右节点,说明不需要交换堆化跳出循环,
        swap(a,maxpos,i);          //把三个结点中最大的那个,换到结点位置
        i = maxpos;                
    }
}
void delete_node(int a[],int cnt)
{
    if(cnt==0)
    {
        return;
    }
    a[1] = a[cnt + 1];
    cnt--;
    heaping_(a, cnt, 1);

}
堆排序
过程思路

堆排序分为两个步骤

  • 建堆
  • 排序

为什么会需要建堆呢,因为排序的时候的算法利用了堆的特点,建堆是通过对已经有的数组进行堆化,使数组满足堆的特点。这里采用的是:从最后一个非叶子结点——>也就是数组有效元素/2位置 开始堆化(叶子节点不需要堆化,)

排序的思想是每次都将大顶堆堆顶元素放在数组末尾,再对数组里n-2个元素进行堆化,多次进行直到把倒数第二个元素进行交换,堆化。

代码实现
void build_heap(int a[],int cnt)    //采用的方法是从下往上依次堆化
{
    int i;
    int maxpos;
    for (i = cnt / 2; i>0;i--)
    {
        heaping_(a,cnt,i);
    }
}
void sort_heap(int a[], int cnt)
{
    int i = cnt;
    while (i > 1)
    {
        swap(a, 1, i);
        i--;
        heaping_(a, i, 1);
    }
}
完整代码
#include <stdio.h>
#include <stdbool.h>
#include <windows.h>
#include <malloc.h>
int cnt;
void heaping_(int a[], int cnt, int i);
void build_heap(int a[],int cnt);       //大顶堆
void sort_heap(int a[],int );             //堆排序
void insert_node(int a[],int val,int cnt);  //插入一个元素
void delete_node(int a[],int cnt); //删除堆顶元素
void swap(int a[], int m, int n);
void print_node(int a[],int cnt)
{
    int i;
    for (i = 1; i <=cnt; i++)
    {
        printf("%d  ", a[i]);
    }
}

int main()
{
    int cnt = 8;
    int heap[10] ={0,7,5,19,8,4,1,20,13};
    build_heap(heap,cnt);
    print_node(heap,cnt);
    printf("\n");
    //插入
    printf("after inserting:");
    printf("\n");
    insert_node(heap, 27, cnt);
    print_node(heap, cnt);
    //删除
    printf("\n");
    printf("after deleting");
    delete_node(heap, cnt);
    cnt--;
    printf("\n");
    print_node(heap, cnt);
   //堆排序
    printf("\n");
    printf("after sorting");
    printf("\n");
    sort_heap(heap, cnt);
    print_node(heap, cnt);

}

void heaping_(int a[],int cnt,int i)    //一次该函数调用,可以把堆从i结点开始到堆底的全部堆化
{
    while (true)
    {
        int maxpos = i;
        if (2 * i <= cnt && a[2 * i] > a[i])
            maxpos = 2 * i;                                                 //是否左节点会大于该节点
        if (2 * i +1<= cnt && a[2 * i + 1] > a[maxpos])
            maxpos = 2 * i + 1;     //是否右节点会大于该节点/左节点,注意这里是maxpos不是i因为右节点可能会大于左节点,
        if(i==maxpos)break;         //如果该节X点大于左节点又大于右节点,说明不需要交换堆化跳出循环,
        swap(a,maxpos,i);          //把三个结点中最大的那个,换到结点位置
        i = maxpos;                
    }
}
void build_heap(int a[],int cnt)    //采用的方法是从下往上依次堆化
{
    int i;
    int maxpos;
    for (i = cnt / 2; i>0;i--)
    {
        heaping_(a,cnt,i);
    }
}
void sort_heap(int a[], int cnt)
{
    int i = cnt;
    while (i > 1)
    {
        swap(a, 1, i);
        i--;
        heaping_(a, i, 1);
    }
}

void swap(int a[], int m, int i)
{
    int temp = a[i];
    a[i] = a[m];
    a[m] = temp;

}
void insert_node(int a[],int val,int cnt)
{
    int i=cnt+1;
    a[i] = val;
    while(a[i]>a[i/2] && i/2>0)
    {
        swap(a,i,i/2);
        i = i / 2;
    }
}
void delete_node(int a[],int cnt)
{
    if(cnt==0)
    {
        return;
    }
    a[1] = a[cnt];
    cnt--;
    heaping_(a, cnt, 1);
}

运行结果如下
在这里插入图片描述

总结

  • 堆的存储结构是利用数组,关系是通过下标来反映
  • 堆的算法跟堆的特性和下标有很大的关系,需要从这两个点去思考编程
  • 堆化的方法分为两种,删除和建堆的自上而下堆化方法 和 插入元素自下而上的堆化方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值