在 C# 中计算数组频率分布的最快方法是啥?

Posted

技术标签:

【中文标题】在 C# 中计算数组频率分布的最快方法是啥?【英文标题】:What is the fastest way to calculate frequency distribution for array in C#?在 C# 中计算数组频率分布的最快方法是什么? 【发布时间】:2011-11-07 17:21:11 【问题描述】:

我只是想知道该计算的最佳方法是什么。假设我有一个输入值数组和边界数组 - 我想计算/分桶边界数组中每个段的频率分布。

使用桶搜索是个好主意吗?

其实我发现了那个问题Calculating frequency distribution of a collection with .Net/C#

但我不明白如何为此目的使用存储桶,因为每个存储桶的大小在我的情况下可能不同。

编辑: 在所有讨论之后,我有内/外循环解决方案,但我仍然想用字典消除内循环以在这种情况下获得 O(n) 性能,如果我理解正确,我需要将输入值散列到存储桶索引中。所以我们需要某种复杂度为 O(1) 的散列函数?有什么想法吗?

【问题讨论】:

你能更好地描述边界数组吗?各种边界之间是否有任何关系(即它们是连续的)还是它们的大小和“位置”完全随机?我假设边界数组完全覆盖了可能值的范围 - 这是真的吗?另外,我假设没有重叠 - 对吧? 最快是大“O”的意思还是小代码的意思?一种简单的方法是自己编写一个函数 Func 并将其与 Linqs .GroupBy 一起使用以将其分组为“Buckets” - 但可能有更快的计算方法来执行此操作。 是的,你是对的。边界值的值单调递增。它们没有重叠,并且涵盖了可能值的范围。例如:0、10、50、100、120。 在大“O”的意思中最快,没有 Linqs 。 GroupBy,只是计算方式。 一个简单但不太快的解决方案是二分搜索。 【参考方案1】:

桶排序已经是 O(n^2) 最坏的情况,所以我会在这里做一个简单的内/外循环。由于您的存储桶数组必然比您的输入数组短,因此请将其保留在内部循环中。由于您使用的是自定义存储桶大小,因此实际上没有任何数学技巧可以消除该内部循环。

int[] freq = new int[buckets.length - 1];
foreach(int d in input)

    for(int i = 0; i < buckets.length - 1; i++)
    
         if(d >= buckets[i] && d < buckets[i+1])
         
             freq[i]++;
             break;
         
    

这也是 O(n^2) 最坏的情况,但您无法超越代码的简单性。在它成为一个真正的问题之前,我不会担心优化。如果你有一个更大的桶数组,你可以使用某种二进制搜索。但是,由于频率分布通常小于 100 个元素,我怀疑您会看到很多实际性能优势。

【讨论】:

您如何看待 BucketizedHashtable 实现,就像它在 Java 中呈现的那样?或者说在执行开始的时候数组排序,有意义吗? Dictionary&lt;sometype, int&gt; 消除内部循环以获得摊销的 O(n) 性能。 @Hans 你什么意思?我不太明白:( @Jevgenij - 据我了解,分桶哈希表通常适用于标准桶大小。这很有效,因为您使用一个输入值并输出存储桶编号的函数,而不是循环遍历存储桶数组。如果转换函数在 O(1) 中运行,那么您可以获得 O(n) 的性能,因为它完全消除了对任何内部循环的要求。这类似于@Hans 通过使用Dictionary&lt;type, int&gt; 所说的话——但它需要某种方法将输入值散列到存储桶索引中。至于数组排序,你只会增加算法的复杂性。 内循环可以用二分查找代替,得到一个整体的O(n*log(m)),其中n - 输入计数; m - 桶数。【参考方案2】:

如果您的输入数组代表真实世界的数据(及其模式),并且边界数组很大以在内部循环中一次又一次地迭代它,您可以考虑以下方法:

首先对输入数组进行排序。如果您使用真实世界的数据 我建议为此考虑 Timsort - Wiki。它 为可以看到的模式提供了非常好的性能保证 真实世界的数据。

遍历排序后的数组,并与边界数组中的第一个值进行比较:

如果输入数组中的值小于边界 - 增加此边界的频率计数器 如果输入数组中的值大于边界 - 转到边界数组中的下一个值并增加新边界的计数器。

在代码中它可能如下所示:

Timsort(myArray);
int boundPos; 
boundaries = GetBoundaries(); //assume the boundaries is a Dictionary<int,int>()

for (int i = 0; i<myArray.Lenght; i++) 
  if (myArray[i]<boundaries[boundPos])  
     boundaries[boubdPos]++;
  
  else 
    boundPos++;
    boundaries[boubdPos]++;
  

【讨论】:

边界用值数组表示。但是复杂性呢?正如我对 Timsort 的理解,在最坏的情况下 O(nlogn) + O(n) 进行循环。我认为二进制搜索的内/外循环应该更好? 不太对。如果中间有一个“空”桶,这将失败。也就是说,排序后的数组中有两个输入值彼此相邻,但进入彼此不相邻的存储桶。但这可以解决。总而言之,这是一个非常好的主意。根据数据,甚至可以使用基数排序,即 O(n),尽管它可能需要大量数据才能使其有价值。但总体运行时间将是一个干净的 O(n)。 P.S.很抱歉发布此文本作为答案。这是一个评论。 @Vilx-,同意并感谢您的纠正。我没有考虑这种情况。

以上是关于在 C# 中计算数组频率分布的最快方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在Python中计算数组元素[重复]

频数分布直方图的特点是啥

如何在 SwiftUI 中计算数组的总计、小计和平均值

c#中定时器的频率因子是啥

获得范围内频率平均值的最快方法[关闭]

频率分布直方图