笔记2. 堆(优先队列)

堆(优先队列)

堆的概念

  • 堆是满二叉树:从左到右依次变满(一般用数组下标存储)
    image

父节点和左右节点的位置

节点i位置对应的父子节点位置
父节点: (i - 1) / 2;
左子节点: 2 * i + 1
右子节点: 2 * i + 2

大根堆和小根堆

  • 大根堆:每棵子树的头节点为当前树的最大值
  • 小根堆:每棵子树的头节点为当前树的最小值

堆的行为:上浮(insert)、下沉(pop)

待刷的题

image

完全二叉树的高度

堆排序

需要两步:

  1. 数组array->大根堆。在这个过程中array也发生了变化,但是还未完成有序。但是大根堆的第1个元素就是array中最大的。
  2. 把大根堆第1个和最后一个做交换,然后heapSize--,直到heapSize为0,array中所有数据都处理完了,数组排序完成。

练习题

703. 数据流中的第 K 大元素

#define Parent(i) ((i - 1) / 2)
#define Left(i)   ((i * 2) + 1)
#define Right(i)  ((i * 2) + 2)

/* 小根堆 */
typedef struct {
    int *heap;
    int heapSize;
    int capacity;
} KthLargest;

int Top(KthLargest *obj)
{
    return obj->heap[0];
}

void Swap(int *a, int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void Swim(KthLargest *obj, int index)
{
    int cur = index;
    while (cur >= 0 && obj->heap[Parent(cur)] > obj->heap[cur]) {
        Swap(&obj->heap[Parent(cur)], &obj->heap[cur]);
        cur = Parent(cur);
    }
}

/* 下沉:从堆的指定位置(一般是顶部),开始下沉 */
void Sink(KthLargest *obj, int index)
{
    int cur = index;
    int reChild = cur;

    while (cur < obj->capacity) {
        if (Left(cur) >= obj->capacity) {
            break;
        }
        if (obj->heap[Left(cur)] < obj->heap[cur]) {
            reChild = Left(cur);
        }
        if (Right(cur) < obj->capacity && obj->heap[Right(cur)] < obj->heap[Left(cur)]) {
            reChild = Right(cur);
        }
        if (obj->heap[reChild] < obj->heap[cur]) {
            Swap(&obj->heap[reChild], &obj->heap[cur]);
            cur = reChild;
        } else {
            break;
        }
    }
}

void HeapAdd(KthLargest *obj, int num)
{
    // printf("add num = %d, Top(obj) = %d\n", num, Top(obj));
    if (obj->heapSize < obj->capacity) {
        obj->heap[obj->heapSize] = num;
        ++(obj->heapSize);
        Swim(obj, obj->heapSize - 1);
    } else if (Top(obj) < num) {
        obj->heap[0] = num;
        Sink(obj, 0);
    }
}

KthLargest* kthLargestCreate(int k, int* nums, int numsSize) 
{
    KthLargest *obj = malloc(sizeof(KthLargest));
    obj->heap = malloc(sizeof(int) * k);
    obj->heapSize = 0;
    obj->capacity = k;

    for (int i = 0; i < numsSize; i++) {
        HeapAdd(obj, nums[i]);
    }
    return obj;
}

int kthLargestAdd(KthLargest* obj, int val) 
{
    HeapAdd(obj, val);
    return Top(obj);
}

void kthLargestFree(KthLargest* obj) 
{
    free(obj->heap);
    free(obj);
}

/**
 * Your KthLargest struct will be instantiated and called as such:
 * KthLargest* obj = kthLargestCreate(k, nums, numsSize);
 * int param_1 = kthLargestAdd(obj, val);
 
 * kthLargestFree(obj);
*/

347. 前 K 个高频元素

#define GetParent(i)        ((i - 1) / 2)
#define GetLeftChid(i)      ((2 * i) + 1)
#define GetRightChild(i)    ((2 * i) + 2)
// #define TopCnt(Heap *heap)  ((heap)->data[0].cnt)

typedef struct HashTable {
    int key; // num
    int val; // 出现次数
    UT_hash_handle hh;
} HashTable;

HashTable *g_hash;

typedef struct Data {
    int num;
    int cnt;
} Data;

typedef struct Heap {
    Data *data;
    int heapSize;
    int capacity;
} Heap; // 小根堆

void Swap(Data *a, Data *b)
{
    Data tmp;
    tmp.num = a->num;
    tmp.cnt = a->cnt;
    (*a).num = (*b).num;
    (*a).cnt = (*b).cnt;
    (*b).num = tmp.num;
    (*b).cnt = tmp.cnt;
}

int TopCnt(Heap *heap)
{
    return heap->data[0].cnt;
}

/* 上浮:添加到堆的指定位置(一般是尾巴),然后按照小根堆依次上浮上来 */
void Swim(Heap *heap, int index)
{
    while (index >= 0 && heap->data[GetParent(index)].cnt < heap->data[index].cnt) {
        Swap(&heap->data[GetParent(index)], &heap->data[index]);
        index = GetParent(index);
    }
}

/* 下沉:从堆的指定位置(一般是顶部),开始下沉 */
void Sink(Heap *heap, int index)
{
    int left, right;
    int reChild;
    while (index < heap->capacity) {
        left = GetLeftChid(index);
        if (left >= heap->capacity) {
            break;
        }
        right = GetRightChild(index);
        if (right < heap->capacity) {
            reChild = heap->data[left].cnt < heap->data[right].cnt ? left : right;
        } else {
            reChild = left;
        }

        if (heap->data[reChild].cnt < heap->data[index].cnt) {
            Swap(&heap->data[reChild], &heap->data[index]);
        } else {
            break;
        }
        index = reChild;
    }
}

void HeapAdd(Heap *heap, int num, int cnt)
{
    if (heap->heapSize >= heap->capacity) { // 如果堆没有满则无脑添加
        heap->data[heap->heapSize].num = num;
        heap->data[heap->heapSize].cnt = cnt;
        (heap->heapSize)++;
        Swim(heap, heap->heapSize - 1);
    } else if (TopCnt(heap) < cnt) { // 如果堆满了,则要在堆顶元素频次小于待添加元素频次,才添加
        heap->data[0].num = num;
        heap->data[0].cnt = cnt;
        Sink(heap, 0);
    }
}

int* topKFrequent(int* nums, int numsSize, int k, int* returnSize)
{
    // 1. 遍历数组,将每个数出现的频次存到hash表中
    g_hash = NULL;
    HashTable *cur = NULL;
    HashTable *tmp = NULL;
    for (int i = 0; i < numsSize; i++) {
        HASH_FIND_INT(g_hash, &nums[i], cur);
        if (cur == NULL) {
            cur = malloc(sizeof(HashTable));
            cur->key = nums[i];
            cur->val = 1;
            HASH_ADD_INT(g_hash, key, cur);
        } else {
            ++(cur->val);
        }
    }

    // 2. 遍历hash表,开始往小根堆里push或pop节点
    Heap *heap = malloc(sizeof(Heap));
    heap->data = malloc(sizeof(Data) * k);
    heap->heapSize = 0;
    heap->capacity = k;
    HASH_ITER(hh, g_hash, cur, tmp) {
        HeapAdd(heap, cur->key, cur->val);
        HASH_DEL(g_hash, cur);
    }

    // 3. 取出小根堆的结果存到结果数组中
    *returnSize = k;
    int *res = malloc(sizeof(int) * k);
    for (int i = 0; i < k; i++) {
        res[i] = heap->data[i].num;
    }
    return res;
}
posted @ 2023-04-08 20:43  胖白白  阅读(42)  评论(0编辑  收藏  举报