龙空技术网

二叉堆-优先队列应用-数据流中的第 K 大元素-LeetCode

软件设计师小华也很嗨 117

前言:

此时看官们对“求第k大元素”大致比较重视,朋友们都想要分析一些“求第k大元素”的相关文章。那么小编也在网络上汇集了一些对于“求第k大元素””的相关文章,希望你们能喜欢,同学们快快来学习一下吧!

北京

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。

int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

示例:

输入:

["KthLargest", "add", "add", "add", "add", "add"]

[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]

输出:

[null, 4, 5, 5, 8, 8]

解释:

KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);

kthLargest.add(3); // return 4

kthLargest.add(5); // return 5

kthLargest.add(10); // return 5

kthLargest.add(9); // return 8

kthLargest.add(4); // return 8

我们首先想到一种暴力的解法,就是首先把输入的数据流进行排序,然后从排好序的数据流中找到第K个元素,就行了,这也是一种解法,我们可以计算一下这种解法情况下的时间复杂度,假设有 N 个元素,要将 K 个元素进行快速排序,那么时间复杂度就为:O ( N ⋅ K ⋅ log ⁡ K ) 。

我们有没有更好的办法来解决这个问题呢?这里我们可以用到优先队列的数据结构(优先队列-二叉堆的运用 ),优先队列的作用是能保证每次取出的元素都是队列中数值最小的,其通过堆实现,通过完全二叉树(complete binary tree)(数据结构:二叉堆 )实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值)。我们可以将优先队列的大小定义成 K ,堆顶就存放的是 K 个元素中最小的那个,当有新的数据流来的时候,就和这个堆顶最小的元素进行比较,比这个堆顶元素小的话,不放进优先队列中;如果新的数据流比堆顶的元素大的话,那么把堆顶的元素剔除,把数据流中的元素加入优先队列。所以说用大小为 K 的优先队列的话,可以保证堆顶一定是第K个大小的元素。这个时候的时间复杂度为: O ( N ⋅ log ⁡ K ) ,所以说用优先队列的方法来解决这个问题是最优的方法。

class KthLargest:    #  小堆优先队列    def __init__(self, k, nums):        self.array = []        self.size = 0        self.kthLargestNum = k        for i in range(len(nums)):            self.add(nums[i])    def enqueue(self, data):        self.array.append(data)        self.size += 1        self.up_adjust()    def dequeue(self):        if self.size < 0:            raise Exception("队列为空!")        ret = self.array[0]        self.array[0] = self.array[self.size - 1]        self.array.pop()        self.size -= 1        self.down_adjust()        #  return ret    def up_adjust(self):        child_index = self.size - 1        parent_index = (child_index - 1) // 2        temp = self.array[child_index]        while child_index > 0 and temp < self.array[parent_index]:            self.array[child_index] = self.array[parent_index]            child_index = parent_index            parent_index = (parent_index - 1) // 2        self.array[child_index] = temp    def down_adjust(self):        parent_index = 0        temp = self.array[parent_index]        child_index = 2 * parent_index + 1        while child_index < self.size:            if child_index + 1 < self.size and self.array[child_index + 1] < self.array[child_index]:                child_index += 1            if temp <= self.array[child_index]:                break            self.array[parent_index] = self.array[child_index]            parent_index = child_index            child_index = 2 * parent_index + 1        self.array[parent_index] = temp    def add(self, val) -> int:        self.enqueue(val)        if(self.size > self.kthLargestNum):            self.dequeue()        return self.array[0]
typedef int ElemType;typedef struct {    ElemType* arr;    int size;    int capacity;    int kthLargestNum;} KthLargest;void Swap(int* a, int* b){    int temp = *a;    *a = *b;    *b = temp;}void checkCapcity(KthLargest* obj){    if(obj->capacity == obj->size)    {        int newCapacity = obj->capacity == 0 ? 1 : 2 * obj->capacity;        obj->arr = (ElemType*)realloc(obj->arr, sizeof(ElemType) * newCapacity);        obj->capacity = newCapacity;    }}void shiftDown(int* arr, int n, int cur){       int child = 2 * cur + 1;    while (child < n)    {        //比较左右子树,找到较小值        if(child + 1 < n && arr[child + 1] < arr[child])        {            ++child;            // child时刻保存较小值的下标        }        if(arr[child] < arr[cur])        {            Swap(&arr[child], &arr[cur]);            cur = child;            child = 2 * cur + 1;        }        else        {            break;        }      } }void shiftUp(int* arr, int n, int cur){    int parent = (cur - 1) / 2;    //假定倒数第一个叶子节点为当前节点cur,找出它的父节点    while (cur > 0)    {//比较父节点和当前节点。当前节点小的话,则不满足小堆规则,需要进行交换        if(arr[cur] < arr[parent])        {            Swap(&arr[cur], &arr[parent]);            cur = parent;            parent = (cur - 1) / 2;        }        else        {            break;        }    }}void heapPush(KthLargest* obj, ElemType val){    checkCapcity(obj);    obj->arr[obj->size++] = val;    shiftUp(obj->arr, obj->size, obj->size -1);}void heapPop(KthLargest* obj){    if(obj->size > 0)    {        Swap(&obj->arr[0], &obj->arr[obj->size - 1]);        obj->size--;        shiftDown(obj->arr, obj->size, 0);    }}ElemType heapTop(KthLargest* obj){    return obj->arr[0];}int kthLargestAdd(KthLargest* obj, int val) {    heapPush(obj, val);    if(obj->size > obj->kthLargestNum)    {        heapPop(obj);    }    return heapTop(obj);}KthLargest* kthLargestCreate(int k, int* nums, int numsSize){    KthLargest* obj = (KthLargest*)malloc(sizeof(KthLargest));    obj->arr = (ElemType*)malloc(sizeof(ElemType));    obj->capacity = obj->size = 0;    obj->kthLargestNum = k;    for (int i = 0; i < numsSize; i++)    {        kthLargestAdd(obj, nums[i]);    }        return obj;}void kthLargestFree(KthLargest* obj) {    free(obj->arr);    obj->arr = NULL;    obj->capacity = obj->size = 0;}

往期精彩:

数据结构:二叉堆

数据结构:二叉堆的自我调整

优先队列-二叉堆的运用

堆排序-二叉堆应用之二

标签: #求第k大元素