【问题标题】:Algorithm to compute mode计算模式的算法
【发布时间】:2013-08-13 05:06:48
【问题描述】:

我正在尝试以函数的形式设计一种算法,该算法接受两个参数,一个数组和数组的大小。我希望它返回数组的模式,如果有多种模式,则返回它们的平均值。我的策略是获取数组并首先对其进行排序。然后计算一个数字的所有出现次数。当该数字出现时,将计数器加一并将该计数存储在数组 m 中。所以 m 保存所有计数,另一个数组 q 保存我们比较的最后一个值。

例如:我的列表是{1, 1, 1, 1, 2, 2, 2} 然后我会m[0] = 4 q[0] = 1 and then m[1] = 3 and q[1] = 2.

所以模式是q[0] = 1;

不幸的是,到目前为止我还没有成功。希望有人能帮忙。

float mode(int x[],int n)
{
    //Copy array and sort it
    int y[n], temp, k = 0, counter = 0, m[n], q[n];

    for(int i = 0; i < n; i++)
        y[i] = x[i];

    for(int pass = 0; pass < n - 1; pass++)
        for(int pos = 0; pos < n; pos++)
            if(y[pass] > y[pos]) {
                temp = y[pass];
                y[pass] = y[pos];
                y[pos] = temp;
            }

    for(int i = 0; i < n;){
        for(int j = 0; j < n; j++){
            while(y[i] == y[j]) {
                counter++;
                i++;
            }
        }
        m[k] = counter;
        q[k] = y[i];
        i--; //i should be 1 less since it is referring to an array subscript
        k++;
        counter = 0;
    }

}

【问题讨论】:

  • 你没有从你的函数中返回任何东西。我很不清楚您对 mode 的含义和/或函数的结果应该是什么。如果它应该是所有值的平均值,可以只是return std::accumulate(x, x + n, 0.0) / n;。顺便说一句,C++ 没有可变大小的数组。但是,您可以改用 std::vector&lt;int&gt; y(n);
  • @DietmarKühl 该功能未完成。模式是指数组中最常出现的值。我没有使用可变大小的数组,因为数组的大小是参数 n。
  • 您可能想查看std::mapstd::unordered_map 来计算每个值出现的次数。显而易见的替代方法是改用 Boost bimap

标签: c++ algorithm mode


【解决方案1】:

即使您已经有了一些好的答案,我还是决定发布另一个。我不确定它是否真的增加了很多新东西,但我也不确定它是否也没有。如果不出意外,我很确定它使用的标准标题比其他任何答案都多。 :-)

#include <vector>
#include <algorithm>
#include <unordered_map>
#include <map>
#include <iostream>
#include <utility>
#include <functional>
#include <numeric>

int main() {
    std::vector<int> inputs{ 1, 1, 1, 1, 2, 2, 2 };

    std::unordered_map<int, size_t> counts;
    for (int i : inputs)
        ++counts[i];

    std::multimap<size_t, int, std::greater<size_t> > inv;
    for (auto p : counts)
        inv.insert(std::make_pair(p.second, p.first));

    auto e = inv.upper_bound(inv.begin()->first);

    double sum = std::accumulate(inv.begin(),
        e,
        0.0,
        [](double a, std::pair<size_t, int> const &b) {return a + b.second; });

    std::cout << sum / std::distance(inv.begin(), e);
}

与@Dietmar 的回答相比,如果数字中有很多重复,这应该会更快,但如果数字大部分是唯一的,他可能会更快。

【讨论】:

  • 不错。一个小的改进是将std::accumulate() 的第二个参数替换为您已经计算的e
  • @JerryCoffin 这令人印象深刻!你能推荐一些关于标准图书馆的书吗?如果了解您使用过的工具,我似乎可以解决很多问题。问题是我遇到的大多数书籍更像是参考手册而不是教程。我需要一些东西来让我练习这些工具,同时解释这些工具解决的问题类别以及何时使用它们。如果您有任何想法,请告诉我!
  • 我想到了三本书:Effective STL(Scott Meyers)、C++ 标准库:教程和参考(第 2 版)、( Nicolai Josuttis)和 STL 教程和参考指南:使用标准模板库进行 C++ 编程(平装本)(第 2 版)(Musser、Saini 和...一些我不记得名字的人)。其中,Josuttis 是最注重参考的,而 Meyers 可能是最少的。
【解决方案2】:

根据评论,您似乎需要找到最常出现的值,如果有多个值出现相同的次数,则需要生成这些值的平均值。看来,这可以通过std::sort() 轻松完成,然后遍历查找值变化的位置并保持一些运行计数:

template <int Size>
double mode(int const (&x)[Size]) {
    std::vector<int> tmp(x, x + Size);
    std::sort(tmp.begin(), tmp.end());
    int    size(0);  // size of the largest set so far
    int    count(0); // number of largest sets
    double sum(0);    // sum of largest sets
    for (auto it(tmp.begin()); it != tmp.end(); ) {
        auto end(std::upper_bound(it, tmp.end(), *it));
        if (size == std::distance(it, end)) {
            sum += *it;
            ++count;
        }
        else if (size < std::distance(it, end)) {
            size = std::distance(it, end);
            sum = *it;
            count = 1;
        }
        it = end;
    }
    return sum / count;
}

【讨论】:

  • 我知道现在想起来很糟糕,但你真的只使用上限,所以upper_bound 可能更合适。对不起,我第一次没有仔细阅读。
  • @JerryCoffin:你说得对,我自己也应该注意到这一点。也就是说,使用std::find_if() 会在std::sort() 之后产生一个线性算法,而使用std::equal_range()std::upper_bound() 会导致O(n log n) 最坏情况的行为。当然,std::sort() 已经是 O(n),即整体复杂性不会变得更糟。
  • 基本问题是您是否希望看到单个值的平均重复次数超过 log(N) 次。如果它的重复次数少于 log(N) 次,我们可以预期使用 find_if 进行更少的比较。如果它超过 log(N),我们可以预期 upper_bound 会更少。
  • 我认为你可以使upper_bound 整体线性(或按该顺序排列)。每次找到范围的结尾时,都提供超出该范围的下一个点作为下一次搜索的开始。对于每次搜索,N 都会减少,因此您在每次搜索后取较小数字的对数。
【解决方案3】:

如果您只是想计算出现次数,那么我建议您使用std::mapstd::unordered_map

如果您将计数器映射到每个不同的值,那么使用 std::map 计算出现次数是一项简单的任务,因为每个键只能插入一次。要列出列表中不同的数字,只需遍历地图即可。

下面是一个例子:

#include <cstddef>
#include <map>
#include <algorithm>
#include <iostream>

std::map<int, int> getOccurences(const int arr[], const std::size_t len) {
    std::map<int, int> m;
    for (std::size_t i = 0; i != len; ++i) {
        m[arr[i]]++;
    }
    return m;
}

int main() {
    int list[7]{1, 1, 1, 1, 2, 2, 2};
    auto occurences = getOccurences(list, 7);
    for (auto e : occurences) {
        std::cout << "Number " << e.first << " occurs ";
        std::cout << e.second << " times" << std::endl;
    }
    auto average = std::accumulate(std::begin(list), std::end(list), 0.0) / 7;
    std::cout << "Average is " << average << std::endl;
}

输出:

Number 1 occurs 4 times
Number 2 occurs 3 times
Average is 1.42857

【讨论】:

    【解决方案4】:

    这是您的代码的工作版本。 m 将值存储在数组中,q 存储它们的计数。最后,它遍历所有值以获得最大计数、模式的总和以及不同模式的数量。

    float mode(int x[],int n)
    {
        //Copy array and sort it
        int y[n], temp, j = 0, k = 0, m[n], q[n];
    
        for(int i = 0; i < n; i++)
            y[i] = x[i];
    
        for(int pass = 0; pass < n - 1; pass++)
            for(int pos = 0; pos < n; pos++)
                if(y[pass] > y[pos]) {
                    temp = y[pass];
                    y[pass] = y[pos];
                    y[pos] = temp;
                }   
    
        for(int i = 0; i < n;){
            j = i;
            while (y[j] == y[i]) {
              j++;
            }   
            m[k] = y[i];
            q[k] = j - i;
            k++;
            i = j;
        }   
    
        int max = 0;
        int modes_count = 0;
        int modes_sum = 0;
        for (int i=0; i < k; i++) {
            if (q[i] > max) {
                max = q[i];
                modes_count = 1;
                modes_sum = m[i];
            } else if (q[i] == max) {
                modes_count += 1;
                modes_sum += m[i];
            }   
        }   
    
        return modes_sum / modes_count;
    }
    

    【讨论】:

      猜你喜欢
      • 2020-07-23
      • 2010-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-28
      相关资源
      最近更新 更多