【问题标题】:How do I explain what a "naive implementation" is? [closed]我如何解释什么是“幼稚的实现”? [关闭]
【发布时间】:2008-11-02 20:43:40
【问题描述】:

对于计算机科学家所说的“幼稚实现”的含义,最清楚的解释是什么?我需要一个清晰的好例子来说明——理想情况下,即使对非技术人员也是如此——天真的实现可能技术上是解决问题的有效解决方案,但实际上是完全无法使用。

【问题讨论】:

  • 一个例子是如果你想打破一个两位数的锁。非计算机科学家可能会尝试天真的随机组合,因为很多组合将被使用两次或三次。一个非天真的实现将从 00 开始到 99。
  • 提名重新开放,因为它要求定义一个公认的技术术语,这在 SO“T&Cs”的主持下完全没问题。完全可以客观地回答。一个很好的例子:出于同样的原因,我在谷歌上搜索了同样的问题,然后来到了这里。

标签: language-agnostic complexity-theory


【解决方案1】:

我会尽量让它远离计算机。询问您的听众他们如何在字典中找到条目。 (一个普通的词定义词典。)

天真的实现是从头开始,看第一个单词。哦,这不是我们要找的词 - 看看下一个,等等。值得向观众指出,他们甚至可能没有考虑这种做事方式 - 我们'足够聪明,立即打折!然而,这是你能想到的最简单的方法。 (询问他们是否能想到更简单的方法可能会很有趣,并检查他们是否真的理解为什么它比我们实际做的方式更简单。)

下一个实现(也是一个非常好的实现)是从字典的中间开始。我们要找的词是在那之前还是之后?如果是之前,则转到开始和现在之间的中间页面 - 否则,转到现在和结束之间的中间页面,等等 - 二进制印章。

实际的人类实现是利用我们对字母的了解非常迅速地到达“几乎正确的位置”——如果我们看到“大象”,那么我们就会知道它会“接近起点”,可能大约是 1 /第 5 次通过。一旦我们到达 E(我们可以通过非常非常简单的比较来做到这一点),我们会找到 EL 等。

【讨论】:

  • 这里也有机会解释非朴素算法的局限性。如果列表未排序,“智能”方法将不起作用,但幼稚的方法会......很好的答案,顺便说一句。
  • 嗯。一个简单的实现通常是直观的或首先想到的。正如您自己所说,您的类比在这里失败了。
  • 请注意,简单的实现更健壮,因为它使用的信息更少:字典不需要按字母顺序排列。
【解决方案2】:

StackOverflow 的 Jeff Atwood 有一个 great example 一个与洗牌数组相关的简单算法。

【讨论】:

  • 公平地说,我的链接示例并不是一个特别非技术性的示例。但这仍然是我最喜欢的演示。
【解决方案3】:

以最直接、最简单的方式进行操作。一个例子是选择排序。

在这种情况下,naive not 是否意味着不好或无法使用。就是说不是特别好。


记住Jon Skeet's advice,您可以将选择排序描述为:

  1. 找出列表中的最高值并放在首位
  2. 查找下一个最高值并将其添加到列表中
  3. 重复第 2 步,直到用完列表

它很容易做也很容易理解,但不一定是最好的。

【讨论】:

    【解决方案4】:

    另一个简单的实现是在命令式语言中使用递归计算整数的阶乘。在这种情况下,更有效的解决方案是只使用循环。

    【讨论】:

    • 可能会更糟:尝试使用递归实现 Ackermann 的函数且不使用记忆。然后,尝试计算 A(6,6) - 啊,这不仅是天真,而且是最糟糕的受虐狂:-)
    【解决方案5】:

    你能想到的最明显、最简单的求幂算法是什么?

    base ** expbase * base * ... * baseexp 次:

    double pow(double base, int exp) {
        double result = 1;
        for (int i = 0; i < exp; i++)
            result *= base;
        return result;
    }
    

    不过,它不处理负指数。记住base ** exp == 1 / base ** (-exp) == (1 / base) ** (-exp)

    double pow(double base, int exp) {
        double result = 1;
        if (exp < 0) {
            base = 1 / base;
            exp = -exp;
        }
        for (int i = 0; i < exp; i++)
            result *= base;
        return result;
    }
    

    不过,实际上可以用小于 exp 的乘法计算 base ** exp

    double pow(double base, int exp) {
        double result = 1;
        if (exp < 0) {
            base = 1 / base;
            exp = -exp;
        }
        while (exp) {
            if (exp % 2) {
                result *= base;
                exp--;
            }
            else {
                base *= base;
                exp /= 2;
            }
        }
        return result * base;
    }
    

    这利用了base ** exp == (base * base) ** (exp / 2) 如果exp 是偶数这一事实,并且只需要大约log2(exp) 乘法。

    【讨论】:

      【解决方案6】:

      我花时间仔细阅读了你的问题,我有一个完美的例子。

      一个很好的清晰示例将说明 - 理想情况下,即使对非技术人员也是如此 - 天真的实现可能技术上是解决问题的有效解决方案,但 实际上 em> 完全无法使用。

      试试Bogosort

      如果使用 bogosort 对一副纸牌进行排序,它将包括检查纸牌是否有序,如果不是,则将纸牌扔到空中,随机捡起纸牌,然后重复这个过程,直到牌组被分类。

      【讨论】:

        【解决方案7】:

        “朴素实施”几乎总是与“蛮力实施”同义。幼稚的实现通常很直观,最先浮现在脑海中,但也往往是 O(n^2) 或更糟,因此对于大型输入来说花费的时间太长也不实用。

        编程竞赛充满了问题,其中天真的实现无法在可接受的时间内运行,问题的核心是提出一种改进的算法,该算法通常不那么明显,但运行速度更快。

        【讨论】:

          【解决方案8】:

          天真的实现是:

          • 直观;
          • 首先想到的;
          • 通常是有感染力和/或有问题的角落案例;

          【讨论】:

            【解决方案9】:

            假设有人弄清楚如何从数据库中提取单个字段,然后继续用 PHP 或任何语言编写网页,该网页对数据库中的每个字段进行单独查询。它可以工作,但会非常缓慢、低效且难以维护。

            【讨论】:

              【解决方案10】:

              天真并不意味着不好或无法使用 - 它意味着具有某些品质,在特定环境中出于特定目的会造成问题。

              经典的例子当然是排序。在对包含十个数字的列表进行排序的情况下,任何旧算法(除了 pogo 排序)都可以很好地工作。但是,当我们达到数千个或更多数字的规模时,通常我们会说选择排序是一种朴素算法,因为它具有 O(n^2) 时间的质量,这对于我们的目的来说太慢了,而且非朴素算法是快速排序,因为它具有 O(n lg n) 时间的质量,对于我们的目的来说足够快。

              事实上,在对包含十个数字的列表进行排序的情况下,快速排序是一种简单的算法,因为它比选择排序花费的时间更长。

              【讨论】:

              • 我会反对天真与性能有关的想法。当然,恕我直言,这只是关于您必须思考的难度。
              • 表现不一定是判断幼稚的标准。通常,在更特殊的环境中取得好的结果需要更加努力的思考。以 PEG 为例。一种更复杂(“非天真”)的缓存算法让我们可以定义左递归语法。
              • 我完全不同意最后一段:天真并不意味着糟糕或缓慢,它意味着直截了当。 可能一个简单的解决方案在性能方面实际上非常出色。无论您怎么看,快速排序都不是简单的。
              • 快速排序非常简单。在函数式语言中,快速排序是一种单线算法,而选择排序是一种更复杂的算法。然而,就地快速排序通常远不如就地选择排序那么简单。
              【解决方案11】:

              确定一个数是否为素数(素数测试)就是一个很好的例子。

              简单的方法只是检查 n mod x where x = 2..square root(n) 对于至少一个 x 是否为零。对于非常大的素数,这种方法可能会变得非常慢,并且在密码学中不可行。

              另一方面,有几个概率或快速确定性测试。这些太复杂了,无法在此处解释,但您可能需要查看有关该主题的相关 Wikipedia 文章以获取更多信息:http://en.wikipedia.org/wiki/Primality_test

              【讨论】:

                【解决方案12】:

                对超过 100,000,000 个条目进行冒泡排序。

                【讨论】:

                • 我不同意。当输入已经半排序时,冒泡排序是最好的。当列表已经排序后,冒泡将在一步之后完成,而这正是快速排序的最坏情况。真正的天真是相信“一种排序算法适合所有情况”。
                • 不是我所知道的冒泡排序。它的作用类似于 for(int i = 0; i x[j]) { t = x[i ]; x[i] = x[j]; x[j] = t; }
                • 你的是冒泡排序最天真的实现!!看看en.wikipedia.org/wiki/Bubble_sort - 如果在一个步骤之后没有交换两个元素,它们会停止(ergo,列表已经排序)。然后上下交替的变体(沙子和气泡)是 O(n^2 / 2)。
                【解决方案13】:

                通常用于对一副纸牌进行排序的直观算法(insertion sortselection sort,均为 O(n^2))可以被认为是幼稚的,因为它们易于学习和实现,但无法扩展好到一副牌,比如说,100000张牌:D。在一般情况下,有更快 (O(n log n)) 的方法来对列表进行排序。

                但请注意,天真并不一定意味着糟糕。在某些情况下,插入排序是一个不错的选择(例如,当您有一个已经排序的大牌堆和几张未排序的卡片要添加时)。

                【讨论】:

                  【解决方案14】:

                  (还没有看到一个真正幼稚的实现,所以......)

                  以下实现是“幼稚的”,因为它没有覆盖边缘情况,并且在其他情况下会中断。非常简单易懂,可以传达编程信息。

                     def naive_inverse(x):
                          return 1/x
                  

                  它会:

                  • 在 x=0 时中断
                  • 传递整数时表现不佳

                  您可以通过添加这些功能使其更加“成熟”。

                  【讨论】:

                  • 我认为这只是一个“破碎”的实现,而不是一个“幼稚”的实现......不太一样
                  【解决方案15】:

                  一个 O(n!) 算法。

                  foreach(object o in set1)
                  {
                      foreach(object p in set1)
                      {
                          // codez
                      }
                  }
                  

                  这将在小集合中表现良好,然后在大集合中表现得更差。

                  另一个可能是不考虑线程的幼稚单例。

                  public static SomeObject Instance
                  {
                       get
                       {
                            if(obj == null)
                            {
                                 obj = new SomeObject();
                            }
                  
                            return obj;
                       }
                  }
                  

                  如果两个线程同时访问它,它们就有可能获得两个不同的版本。导致严重奇怪的错误。

                  【讨论】:

                  • 单例示例当然不适合非技术人员,并且不清楚您的第一个示例是关于什么的(或者为什么它是 n! 而不是 n^2)。
                  • 我也看不到n!那里。两者都适用于 set1.Count 次 => O(n^2)
                  • :(。我失败了....老实说,我以为 n! 是 n*2,该死的我缺乏教育!但现在我很想创建一个 n!alg 只是看看一个是什么样子。我想我会尝试一个蛮力的旅行推销员。感谢您的指正!:)
                  • 或者简单地说,naieve Fibonacci 算法非常接近 n!
                  猜你喜欢
                  • 2016-04-10
                  • 2021-09-18
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-04-15
                  • 2020-02-09
                  • 2013-01-05
                  • 1970-01-01
                  相关资源
                  最近更新 更多