堆、堆的操作、优先级队列

时间:2022-11-28 10:13:56

●将完全二叉树的结构特点套用在一个一维数组上,堆的实质是一个一维数组。
什么样的一维数组才称为堆?
●将数组中的元素按照完全二叉树的方式排列起来(如图):
堆、堆的操作、优先级队列
使其满足任一结点的数值均小于(大于)等于它的左右孩子结点的数值,位于堆顶结点的数值最小(最大),从根结点到每个结点的路径上数组元素组成的序列都是递增(递减)的
就称这个一维数组为堆。
若位于堆顶结点的数值最大,则为大堆
若位于堆顶结点的数值最小,则为小堆
堆存储与下标为0开始的一维数组中,因此在堆中给定下标为i的结点时:
如果i=0,则结点i是根结点,没有双亲结点;否则结点i的双亲结点为结点(i-1)/2
如果2*i+1<=n-1,则结点i的左孩子为结点2*i+1,否则结点i无左孩子
如果2*i+2<=n-1,则结点i的右孩子为结点2*i+2,否则结点i无右孩子

堆的创建

给出堆的结构体:

#define MAXSIZE 50
typedef int(*Compare)(HDataType, HDataType);//函数指针,用来确定要创建大堆还是小堆
typedef int HDataType;
typedef struct Heap
{
    HDataType* _array;//存放数据的一维数组
    int _capacity;//容量
    int _size;//已经使用的大小
    Compare _com;//创建大堆还是小堆
}Heap;

●首先要以完全二叉树的结构套用所给定的一维数组,0号位置为根结点,1为根结点的左孩子,2为根结点的右孩子……
●将二叉树调整为最小(大)堆的原理:

从最后一个非叶子结点开始调整,一直到根结点为止,将每个结点及其子树调整到满足最小堆的性质即可

int Less(HDataType Left, HDataType Right)//若要创建小堆,则就将此函数的地址(函数名)作为参数传递给创建堆的函数
{
    return Left < Right;
}
int Greater(HDataType Left, HDataType Right)//若要创建大堆,则就将此函数的地址(函数名)作为参数传递给创建堆的函数
{
    return Left>Right;
}


//创建堆
void CreateHeap(Heap* hp, HDataType* array, int size, Compare com)
{
    int i = 0;
    int Root = 0;
    if (NULL == hp)
    {
        assert(0);
        return;
    }
    //指明堆的调整方式(大堆/小堆)
    hp->_com = com;

    //给堆开辟空间
    hp->_capacity = MAXSIZE;
    hp->_array = (HDataType*)malloc(sizeof(HDataType)*hp->_capacity);
    if (NULL == hp->_array)
    {
        assert(0);
        return;
    }
    //将数组赋给堆
    hp->_size = size;
    for (i = size - 1; i >= 0; i--)
    {
        hp->_array[i] = array[i];
    }
    //com指明要创建一个小堆
    //整理堆
    Root = (size - 2) / 2;
    for (; Root >= 0; --Root)
    {
        _AdjustDown(hp, Root);
    }
}
int Less(HDataType Left, HDataType Right)
{
    return Left < Right;
}
int Greater(HDataType Left, HDataType Right)
{
    return Left>Right;
}


//小/大堆的向下调整函数(向下调整法)
void _AdjustDown(Heap* hp, int Root)
{
    int Parent = 0;
    int Child = 0;
    Parent = Root;
    Child = Parent * 2 + 1;

    while (Child < hp->_size)
    {
        //判断他的右孩子是否存在
        if (Child + 1 <= hp->_size - 1)
        {
            //找出两个孩子中较小的一个
            if (Child+1<hp->_size && hp->_com(hp->_array[Child + 1] , hp->_array[Child]))
                Child += 1;
        }

        //较小的孩子与双亲节点比较,如果双亲节点较大就交换
        if (hp->_com(hp->_array[Child], hp->_array[Parent]))
        {
            Swap(&hp->_array[Parent], &hp->_array[Child]);
        }
        Parent = Child;
        Child = Parent * 2 + 1;
    }
}

给堆中插入元素

●检测堆中是否还有可用空间,若无则需给堆增容
●将要插入的数据作为一个叶子节点,插在堆的末尾
●从插入的结点的双亲结点开始向上调整堆,直至根结点

//在堆中插入一个元素
void InsertHeap(Heap* hp, HDataType data)
{
    int Parent = 0;
    int Child = 0;
    assert(hp);
    if (hp->_size >= hp->_capacity)
        return;

    //插入时,首先要检测堆中是否还有可用空间
    _CheckCapacity(hp);

    //将要插入的数据连接在堆的末尾
    hp->_array[hp->_size] = data;
    hp->_size++;

    //使用向上调整法调整堆
    Child = hp->_size - 1;
    while (Child > 0)
    {
        _AdjustUp(hp, Child);
        Child = ((Child - 1) >> 1);
    }
}


//向上调整法
void _AdjustUp(Heap* hp, int Root)
{
    int Parent = 0;
    int Child = 0;
    //让child指向新插入的指针
    Child = Root;
    Parent = ((Child - 1) >> 1);

    //调整堆
    while (0 <= Parent)
    {
        if (hp->_com(hp->_array[Child], hp->_array[Parent]))
        {
            Swap(&hp->_array[Child], &hp->_array[Parent]);
        }
        Child = Parent;
        Parent = ((Parent - 2) >> 1);
    }
}


//检测堆中是否还有可用空间
void _CheckCapacity(Heap* hp)
{
    assert(hp);
    if (hp->_capacity == hp->_size)
    {
        realloc(hp->_array, sizeof(HDataType)*(hp->_capacity * 2));//增容时,默认将容量增加到原容量的两倍
        if (NULL == hp->_array)
        {
            assert(0);
            return;
        }
    }
}

删除堆顶元素

●用堆的最后一个叶子结点覆盖堆顶结点
●释放堆的最后一个叶子结点
●重新进行堆调整

//删除堆顶元素 
//直接删除堆顶的元素,然后把堆中最末尾的元素放在堆顶,再进行堆调整
void DeleteHeapTop(Heap* hp)
{
    assert(hp);
    if (EmptyHeap(hp))
    {
        assert(0);
        return;
    }
    //用堆末尾的元素覆盖堆顶的元素
    hp->_array[0] = hp->_array[hp->_size - 1];
    hp->_size--;

    //进行堆调整
    _AdjustDown(hp, 0);//因为其他位置原先已经是调整好的堆,改动了根结点,所以只需从根结点开始向下调整
}

销毁堆

●释放所开辟的空间
●销毁所有数值

//销毁堆
void DestroyHeap(Heap* hp)
{
    assert(hp);
    free(hp->_array);
    hp->_array = NULL;
    hp->_capacity = 0;
    hp->_size = 0;
    hp->_com = NULL;
}

堆的应用

堆排序

●先将数据调整为一个小(大)堆
●交换堆顶元素与堆末尾的元素(将最小(大)的数据放置在堆尾)
●将堆的size-1,即将堆尾的元素不在视为堆的一份子
●循环此过程,直至堆中只剩下一个元素

//堆排序
//1.先交换堆顶元素与末尾元素(即已经把最大的元素放在末尾)
//2.下次调整就不包括堆中的最后一个元素
void SortHeap(Heap* hp)
{
    int Parent = 0;
    int Child = 0;
    int size = 0;
    assert(hp);
    size = hp->_size;

    while (hp->_size > 0)
    {
        //调整堆
        Parent = (hp->_size - 2) / 2;
        Child = Parent * 2 + 1;
        while (0 <= Parent)
        {
        _AdjustDown(hp, Parent);
        Parent = ((Parent - 1) >> 1);
        }
        //PrintHeap(*hp);
        //交换堆顶元素和末尾元素
        Swap(&hp->_array[0], &hp->_array[hp->_size - 1]); //吐出最后一个元素 --(hp->_size); } hp->_size = size; } //交换节点所存储的值 void Swap(HDataType* first, HDataType* second) { HDataType tmp = 0; tmp = *first; *first = *second; *second = tmp; }

用堆解决Top-K问题

eg:100亿个数据中找出最大的前K个数
●用数据的前10个元素创建一个小堆
●遍历剩下的元素,若大于堆顶元素,则用它覆盖原本的堆顶元素,再调整堆,若小于,则遍历下一个元素,当遍历完成时,堆中的所有元素即就是所要求的元素

//用堆解决Top_k问题
//先用数据的前K个元素建立一个小堆
//剩余的数据与堆顶元素比较 
//如果大于堆顶元素就覆盖堆顶元素然后重新调整堆
//如果小于就继续查看下一个元素
//循环此过程直至数据遍历完成
void TopKByHeap(HDataType* array, int K, int size)
{
    int i = 0;
    //初始化堆
    Heap hp;
    InitHeap(&hp, Less);

    //建立有K个节点的小堆
    while (hp._size < K)
    {
        InsertHeap(&hp, array[i]);
        i++;
    }

    //调整后续元素
    while (i < size)
    {
        //如果后续元素大于堆顶元素
        if (array[i]>hp._array[0])
        {
            //覆盖堆顶元素
            hp._array[0] = array[i];

            //调整堆
            _AdjustDown(&hp, 0);
        }
        i++;
    }
    printf("Top-K:");
    PrintHeap(hp);//打印堆
}

优先级队列

优先级队列是不同于先进先出队列的另一种队列。每次从队列中取出的是具有最高优先权的元素。

//堆的初始化
void InitHeap(Heap* hp, Compare com)
{
    assert(hp);
    hp->_array = NULL;
    hp->_size = 0;
    hp->_capacity = MAXSIZE;
    hp->_array = (HDataType*)malloc(sizeof(HDataType)*hp->_capacity);
    hp->_com = com;
}


//优先级队列的初始化
void PriorityQueueInit(PriorityQueue* q, Compare com)
{
    assert(q);

    //初始化优先级队列中的堆
    InitHeap(&q->_hp, com);
}

//在堆中插入一个元素
void InsertHeap(Heap* hp, HDataType data)
{
    int Parent = 0;
    int Child = 0;
    assert(hp);
    if (hp->_size >= hp->_capacity)
        return;

    //插入时,首先要检测堆中是否还有可用空间
    _CheckCapacity(hp);

    //将要插入的数据连接在堆的末尾
    hp->_array[hp->_size] = data;
    hp->_size++;

    //使用向上调整法调整堆
    Child = hp->_size - 1;
    while (Child > 0)
    {
        _AdjustUp(hp, Child);
        Child = ((Child - 1) >> 1);
    }
}


//向队列中插入元素
void PriorityQueuePush(PriorityQueue* q, HDataType data)
{
    assert(q);
    InsertHeap(&q->_hp, data);
}


//直接删除堆顶的元素,然后把堆中最末尾的元素放在堆顶,再进行堆调整
void DeleteHeapTop(Heap* hp)
{
    assert(hp);
    if (EmptyHeap(hp))
    {
        assert(0);
        return;
    }
    //用堆末尾的元素覆盖堆顶的元素
    hp->_array[0] = hp->_array[hp->_size - 1];
    hp->_size--;

    //进行堆调整
    _AdjustDown(hp, 0);
}


//删除优先级最高的元素
void PriorityQueuePop(PriorityQueue* q)
{
    assert(q);
    DeleteHeapTop(&q->_hp);
}

//获取堆中元素个数
int SizeHeap(Heap* hp)
{
    assert(hp);
    return hp->_size;
}


//获取优先级队列元素个数
int PriorityQueueSize(PriorityQueue* q)
{
    assert(q);
    return SizeHeap(&q->_hp);
}

//检测一个堆是否为空堆
int EmptyHeap(Heap* hp)
{
    assert(hp);
    return 0 == hp->_size;
}


//检测优先级队列是否为空
int PriorityEmpty(PriorityQueue* q)
{
    assert(q);
    return EmptyHeap(&q->_hp);
}


//获取优先级队列的堆顶元素
HDataType PriorityQueueTop(PriorityQueue* q)
{
    assert(q);
    return (q->_hp)._array[0];
}

//销毁堆
void DestroyHeap(Heap* hp)
{
    assert(hp);
    free(hp->_array);
    hp->_array = NULL;
    hp->_capacity = 0;
    hp->_size = 0;
    hp->_com = NULL;
}


//销毁优先级队列
void PriorityQueueDestroy(PriorityQueue* q)
{
    assert(q);
    DestroyHeap(&q->_hp);
}