堆及其应用(优先级队列,TopK问题,堆排序)

完整代码:https://github.com/zzaiyuyu/Heap

堆创建

首先建立一个无序完全二叉树序列,然后自底向上调整每一个非叶结点。调整规则是将调整结点值变为比左右孩子结点都小(小根堆),若不满足,循环调整到树下层,称为向下调整。

为什么从最后的非叶结点向前调整?因为每次调整起码要保证当前结点比左右子树结点都小,而如果从跟结点开始调整只能保证跟结点比左右孩子值小。

void Adjust(Heap *heap, int parent)
{
    assert(heap);
    int child = parent * 2 + 1;
    while (child < heap->size) {
        int right = child +1;
        //得到左右孩子较小的
        if (right < heap->size) {
            if (heap->compare(heap->array[right] , heap->array[child])) {
                child = right;
            }
        }
        //与父交换,或者调整完毕
        if (heap->compare(heap->array[child], heap->array[parent] )) {
            swap(&heap->array[parent], &heap->array[child]);
            parent = child;
            child = child * 2 + 1;
        }
        else {
            return;
        }
    }
}

插入元素

将待插元素放到堆尾,逐步与父节点比较,直到放入合适位置,称为向上调整。

为何只需要和父节点比较?因为原本堆已经满足父节点是左右子树中最小的。

void InsertHeap(Heap *heap, DataType data)
{
    assert(heap);
    if (heap->size == heap->capacity) {
        //扩容,没使用realloc
        int newCap = 2 * heap->capacity;
        DataType *pNew = (DataType*)malloc(sizeof(DataType)*newCap);
        if (NULL == pNew) {
            exit(EXIT_FAILURE);
        }
        int i = 0;
        for (i = 0; i < heap->size; i++) {
            pNew[i] = heap->array[i];
        }
        heap->array = pNew;
        heap->capacity = newCap;
    }
    heap->array[heap->size++] = data;
    //向上调整
    int parent = (heap->size - 2) >> 1;
    int cur = heap->size - 1;
    while (parent >= 0) {
        if (heap->compare(heap->array[cur], heap->array[parent])) {
            swap(&heap->array[parent], &heap->array[cur]);
            cur = parent;
            parent = (parent - 1) >> 1;
        }
        else {
            return;
        }
    }
    return;
}

删除元素

堆顶元素与堆尾元素交换,删除堆尾,调整堆顶。

void DeleteHeap(Heap *heap)
{
    assert(heap);
    if (heap->size > 1) {
        swap(&heap->array[0], &heap->array[heap->size - 1]);
        heap->size--;
        int i = 0;
        int end = (heap->size - 2) >> 1;
        Adjust(heap, 0);
    }
    else if(heap->size ==1){
        heap->size--;
    }
}

优先级队列

优先级队列和堆的性质完全吻合。

每次出队的是值最小的(堆顶)。封装一个堆就可以了。

TopK问题

海量数据一次装不到内存里,需要分批处理。此时建立堆,或者外部排序解决。

堆的思想是,建立一个固定大小的小根堆,将数据剩余元素逐个与堆顶比较,如果大于堆顶,说明比堆里最小的元素大,那么将堆顶元素删除,插入这个较大元素。

void TopK(int data[], int len, int top[])
{
    const int MaxLen = 5;
    Heap heap;
    InitHeap(&heap, Less);
    CreateHeap(&heap, data, MaxLen);
    int res = len - MaxLen; //剩余需要处理的元素个数
                            //维护一个只有五个数值的堆
    while (res) {
        if (data[len - res] > TopHeap(&heap)) {
            DeleteHeap(&heap);
            InsertHeap(&heap, data[len - res]);
        }
        res--;
    }
    //此时堆元素就是最大的5个元素
    int i = 0;
    for (i = 0; i < 5; i++) {
        top[i] = TopHeap(&heap);
        DeleteHeap(&heap);
    }
}

堆排序

void HeapSort(int array[], int len)
{
    if (NULL == array) {
        return;
    }
    //建立大根堆
    Heap heap;
    InitHeap(&heap, Less);
    CreateHeap(&heap, array, len);
    //堆顶元素放数组末尾,移除堆顶元素,调整堆顶元素
    while (len--) {
        array[len] = TopHeap(&heap);
        DeleteHeap(&heap);
    }
    DestroyHeap(&heap);
}

猜你喜欢

转载自blog.csdn.net/hanzheng6602/article/details/80319617