【问题标题】:"Stable" k-largest elements algorithm“稳定”k-最大元素算法
【发布时间】:2020-10-06 15:55:05
【问题描述】:

相关:priority queue with limited space: looking for a good algorithm


我正在寻找一种算法,它从列表中返回 k 最大元素,但不改变 k 最大元素的顺序,例如对于k=45,9,1,3,7,2,8,4,6,算法应该返回9,7,8,6

更多背景信息,我的输入数据大约是 200 对 (distance,importance),它们按 distance 排序,我需要选择其中最重要的 32 个。性能在这里至关重要,因为我必须运行这个选择算法几千次。

到目前为止,我有以下两个想法,但似乎都不是最好的。

  1. 迭代删除最小元素,直到剩下 32 个元素(即进行选择排序)
  2. 使用快速选择或中位数来搜索第 32 个最大的元素。之后,再次对剩余的 31 个元素进行排序。距离。

我需要在 C++ 中实现这一点,所以如果有人想编写一些代码并且不知道使用哪种语言,C++ 将是一个选择。

【问题讨论】:

  • 为什么不使用标准的堆/优先队列解决方案,还要跟踪每个元素来自哪个索引,然后按索引对结果进行排序?
  • 如果可能,我想避免第二个排序步骤。
  • 值是否有固定范围?如果是,那么它的小计数排序是一个选项。
  • 请注意,对剩下的 32 个元素进行排序会比第一步选择这 32 个元素要快
  • @Cherubim,distances 是介于 1 和 300000 之间的整数(我可能必须使用浮点数来表示距离,但我认为没有必要)。 importances 是介于 -1 和 200 之间的浮点数。因此,计数排序不是一个选项(尽管如此,我还不知道排序算法,谢谢你指点我)。

标签: algorithm sorting selection


【解决方案1】:

@trincot 的解决方案的启发,我想出了一个与工作实施略有不同的变体。

算法

  1. 使用 Floyd 算法 来构建最大堆,或者相当于在 C++ 中使用构造函数来构建 priority_queue,我们在其中一次传递整个数组/向量,而不是单独添加元素。以 O(N) 时间复杂度构建的最大堆。

  2. 现在,从最大堆 K-1 中弹出项目,直到我们得到第 Kth Max Importance Item。将 Kth Max Importance Item 的值存储在变量 Kth_Max_Importance_Item 中。

  3. 从原始输入中扫描重要性值大于Kth_Max_Importance_Item的所有节点,并将它们推入输出向量。

  4. 通过从k 中减去当前输出向量的大小,计算重要性值等于Kth_Max_Importance_Item 的重要性值的所需项目的剩余计数。将其存储在变量left_Over_Count中。

  5. 从原始输入中扫描left_Over_Count的重要性值等于Kth_Max_Importance_Item的重要性值的项目的值,并将它们推入输出向量。

注意:如果importance 值不是唯一的,则此条件由步骤 34 处理 算法。

时间复杂度:O(N + K*log(N))。假设 K

实施:

#include <iostream>
#include <vector>
#include <queue>
#include <math.h>

typedef struct Item{

    int distance;
    double importance;

}Item;

struct itemsCompare{

    bool operator() (const Item& item1, const Item& item2){

        return ((item1.importance < item2.importance) ? true : false);
    }
};

bool compareDouble(const double& a, const double& b){

    return (fabs(a-b) < 0.000001) ? true : false;
}

int main(){

    //Original input
    std::vector<Item> items{{10, 2.1}, {9, 2.3}, {8, 2.2}, {7, 2.2}, {6, 1.5}};

    int k = 4;

    //Min Heap
    std::priority_queue<Item, std::vector<Item>, itemsCompare> maxHeap (items.begin(), items.end());

    //Checking if the order of original input is intact
    /*for(int i=0;i<items.size();i++){
        std::cout<<items[i].distance<<" "<<items[i].importance<<std::endl;
    }*/

    //Pulling the nodes until we get Kth Max Importance Node

    int count = 0;
    while(!maxHeap.empty()){
        
        if(count == k-1){
            break;
        }

        maxHeap.pop();
        count++;

    }

    Item Kth_Max_Importance_Item = maxHeap.top();

    //std::cout<<Kth_Max_Importance_Item.importance<<std::endl;


    //Scanning all the nodes from original input whose importance value is greater than the importance value of Kth_Max_Importance_Item.

    
    std::vector<Item> output;

    for(int i=0;i<items.size();i++){

        if(items[i].importance > Kth_Max_Importance_Item.importance){
            output.push_back(items[i]);
        }
    }
    
    int left_Over_Count = k - output.size();

    //std::cout<<left_Over_Count<<std::endl;

    //Adding left_Over_Count number of values of items whose importance value if equal to importance value of Kth_Max_Importance_Item

    for(int i=0;i<items.size();i++){

        if(compareDouble(items[i].importance, Kth_Max_Importance_Item.importance)){
            output.push_back(items[i]);
            left_Over_Count--;
        }

        if(!left_Over_Count){
            break;
        }
    }

    //Printing the output:

    for(int i=0;i<output.size();i++){

        std::cout<<output[i].distance<<" "<<output[i].importance<<std::endl;
    }

    return 0;
}

输出:

9 2.3
8 2.2
7 2.2
10 2.1

【讨论】:

  • 我不得不接受 trincots 的回答,因为他想出了解决方案 - 但我也很感激你。谢谢。
  • 不用担心。他的方法比我的好!
【解决方案2】:

使用基于堆的算法来查找 k 最大值,即使用永远不会超过 k 大小的 min 堆(不是最大堆)。一旦超过该大小,请继续从中拉出根以将其恢复到 k 的大小。

最后堆的根将是 k 最大值。我们就叫它m

然后您可以再次扫描原始输入以收集至少等于 m 的所有值。这样您就可以按照原来的顺序获得它们。

m 不是唯一的时,您可能收集了太多的值。所以检查结果的大小并确定它比 k 长多少。向后浏览该列表并将具有值 m 的列表标记为已删除,直到达到正确的大小。最后收集未删除的项目。

所有这些扫描都是O(n)。最昂贵的一步是第一步:O(nlogk)

【讨论】:

  • 不会利用 min-heap 改变原始输入的顺序吗?
  • 如果您使用单独的最小堆内存,则不会。请注意,一旦识别出第 k 个最大元素,此算法实际上并不依赖于堆的内容。在那一刻,堆可以被丢弃。
  • 如果空间复杂度无关紧要的话,这是一个很好的解决方案@trincot
  • 我理解这个想法,但不太了解最小堆的使用。如果我添加到最小堆的最后一个元素是我列表中的最小数字,那么这个元素不是在最小堆顶部,而不是 k 最大值吗?
  • @tommsch,关键是,如果堆的当前顶部元素大于您将要推送的元素,则不要推送堆内的元素。这样,顶部元素将是第 Kth Max Element。
猜你喜欢
  • 1970-01-01
  • 2015-01-15
  • 1970-01-01
  • 1970-01-01
  • 2015-10-06
  • 2021-06-04
  • 1970-01-01
  • 2012-06-27
  • 2012-06-22
相关资源
最近更新 更多