堆的简单应用——TopK

1、海量数据top k问题

100亿个数中找出最大的前K个数,我们可以遍历K次找到,但是时间复杂度就很大为 O(KN);因此,我们可以用堆来实现,只需遍历一次,思路如下:

  • 如果要找前K个最大的数,我们用小堆,每次用堆顶元素和遍历的数比,如果堆顶元素小,则让堆顶元素的值等于它,然后向下调整
  • 如果要找前K个最小的数,我们用大堆,每次用堆顶元素和遍历的数比,如果堆顶元素大,则让堆顶元素的值等于它,然后向下调整
2、代码如下

这里我们暂时用有限的几个数模仿海量数据,来判断算法是否正确

TopK.h
#pragma once

//堆的操作见其他文章,这里只用到堆的结构体和,大堆小堆函数指针
#include "Heap.h"

//TopK
void TopK(Heap *hp, DataType * array, long size, int K);
//初始化堆
void HeapTopKInit(Heap *hp, Compare cmp, int K);
//交换元素
void SwopTopK(DataType *a, DataType *b);
//打印元素
void TopK_Print(Heap *hp, int K);
//向下调整算法
void AdjustDownTopK(Heap  *hp, DataType parent)
//测试
void TestTopK();
TopK.c
#include "TopK.h"

//求最小的3个元素
void TopK(Heap *hp, DataType * array, long size, int K)
{

    for (long i = 0; i < K; i++)
    {
        InsertHeap(hp, array[i]);
        AdjustDownTopK(hp, i);
    }
    for (long i = K; i < size; i++)
    {
        if (hp->cmp(array[0],hp->_array[i]))
        {
            SwopTopK(&hp->_array[0],&array[i]);
            AdjustDownTopK(hp, 0);
        }

    }
}

//向下调整
void AdjustDownTopK(Heap  *hp, DataType parent)
{
    int child = (parent<<1)+1;

    if (NULL == hp)
        return;
    while (child < hp->_size)
    {
        //找到孩子中较小的一个
        if ((child+1) < hp->_size && 
            hp->cmp(hp->_array[child+1], hp->_array[child]))
        {
            child += 1;
        }
        //如果双亲大于孩子,则交换
        if (hp->cmp(hp->_array[child], hp->_array[parent]))
        {
            Swop(&hp->_array[parent], &hp->_array[child]);
            parent = child;
            child = (parent << 1) + 1;
        }
        else
        {
            break;
        }
    }
}

void SwopTopK(DataType *a, DataType *b)
{
    DataType temp = *a;
    *a = *b;
    *b = temp;
}

void TopK_Print(Heap *hp, int K)
{
    int i = 0;
    for (; i < K; i++)
    {
        printf("%d ", hp->_array[i]);
    }
    printf("\n");
}

void HeapTopKInit(Heap *hp, Compare cmp, int K)
{
    if (NULL == hp)
        return;
    hp->_array = (DataType*)malloc(sizeof(DataType)* K);
    hp->_capacity = K;
    hp->_size = 0;
    hp->cmp = cmp;
}


void TestTopK()
{
    Heap hp;
    int array[] = { 53, 17, 78, 9, 45, 65, 87, 23, 31 };
    int K = 3;

    HeapTopKInit(&hp, Greater, K);
    TopK(&hp, array, sizeof(array)/sizeof(array[0]),K);
    TopK_Print(&hp, K);

}
### 数据结构的概念 是一种特殊的完全二叉树,满足特定性质:父节点的关键字值总是大于等于或者小于等于其子节点的关键字值。这种特性使得可以分为两种形式——最大和最小[^1]。 #### 最大与最小 - **最大**:每个节点的值都大于或等于它的孩子节点的值。 - **最小**:每个节点的值都小于或等于它的孩子节点的值。 ### 的数据结构实现方式 通常情况下,可以通过数组来高效地表示。假设根节点位于索引0的位置,则对于任意给定位置`i`上的元素: - 左孩子的索引为 `2*i + 1` - 右孩子的索引为 `2*i + 2` - 父节点的索引为 `(i-1)//2` 以下是基于Python语言的一个简单的最小实现示例: ```python class MinHeap: def __init__(self): self.heap = [] def parent(self, i): return (i - 1) // 2 def insertKey(self, k): self.heap.append(k) i = len(self.heap) - 1 while i != 0 and self.heap[self.parent(i)] > self.heap[i]: # Swap with the parent node if current is smaller than its parent. self.heap[i], self.heap[self.parent(i)] = self.heap[self.parent(i)], self.heap[i] i = self.parent(i) def decreaseKey(self, i, new_val): self.heap[i] = new_val while i != 0 and self.heap[self.parent(i)] > self.heap[i]: self.heap[i], self.heap[self.parent(i)] = self.heap[self.parent(i)], self.heap[i] def extractMin(self): if len(self.heap) <= 0: return float('inf') if len(self.heap) == 1: return self.heap.pop() root = self.heap[0] self.heap[0] = self.heap[-1] self.heap.pop() self.minHeapify(0) return root def minHeapify(self, i): smallest = i l = 2 * i + 1 r = 2 * i + 2 if l < len(self.heap) and self.heap[l] < self.heap[smallest]: smallest = l if r < len(self.heap) and self.heap[r] < self.heap[smallest]: smallest = r if smallest != i: self.heap[i], self.heap[smallest] = self.heap[smallest], self.heap[i] self.minHeapify(smallest) ``` 上述代码展示了如何通过列表创建并维护一个最小,并提供了插入新键、减少指定键以及提取最小值的功能[^3]。 ### 应用场景 作为一种重要的数据结构,在实际应用中有广泛用途,具体包括但不限于以下几个方面: - 构建优先级队列; - 解决Top-K问题,即从大量数据中找出前K大的元素[^2]; - 实现排序算法; - 合并多个已排序文件; - 定时器管理中的事件调度; - 计算动态数据流中的中位数或其他统计指标;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值