【问题标题】:Your favourite algorithm and the lesson it taught you [closed]您最喜欢的算法及其教给您的课程[关闭]
【发布时间】:2008-08-25 15:55:42
【问题描述】:

在编程或特定语言功能方面,哪种算法教会您最多?

我们都有过这样的时刻,我们突然知道,只是知道,我们已经为未来吸取了重要的一课,基于最终理解程序员编写的算法,在进化阶梯上走了几步。谁的想法和代码对你产生了魔力?

【问题讨论】:

    标签: algorithm language-agnostic


    【解决方案1】:

    通用算法:

    • 快速排序(它是平均复杂度分析)表明随机化您的输入可能是一件好事!;
    • 平衡树(例如AVL trees),一种平衡搜索/插入成本的巧妙方法;
    • DijkstraFord-Fulkerson 图算法(我喜欢第二个有很多应用的事实);
    • LZ* 系列压缩算法(例如LZW),数据压缩对我来说听起来有点神奇,直到我发现它(很久以前:));
    • FFT,无处不在(在许多其他算法中重复使用);
    • simplex 算法,也无处不在。

    数字相关:

    • 计算两个整数的 gcd 的 Euclid 算法:最早的算法之一,简单而优雅,功能强大,有很多泛化;
    • 整数的快速乘法(例如Cooley-Tukey);
    • 牛顿迭代求逆/求根,一种非常强大的元算法。

    数论相关:

    • AGM 相关算法 (examples):导致计算 pi 的非常简单和优雅的算法(以及更多!),尽管理论非常深刻(高斯从中引入了椭圆函数和模形式,所以你可以说它催生了代数几何……);
    • number field sieve(用于整数分解):非常复杂,但理论上的结果相当不错(AKS 算法也是如此,它证明了 PRIMES 在 P 中)。

    我还喜欢研究量子计算(例如ShorDeutsch-Josza 算法):这教你跳出框框思考。

    如你所见,我有点偏向于数学算法:)

    【讨论】:

      【解决方案2】:

      “迭代是人类,递归是神圣的”- 1989 年在大学引用。

      附:由 Woodgnome 在等待加入邀请时发布

      【解决方案3】:

      Floyd-Warshall all-pairs shortest paths algorithm

      procedure FloydWarshall ()
         for k := 1 to n
           for i := 1 to n
              for j := 1 to n
                path[i][j] = min ( path[i][j], path[i][k]+path[k][j] );
      

      这就是它很酷的原因:当您第一次在图论课程中了解最短路径问题时,您可能会从解决单源最短路径的 Dijkstra 算法开始。一开始很复杂,但后来你克服了它,你就完全理解了。

      然后老师说“现在我们要解决相同的问题,但要针对所有来源”。你对自己说:“天啊,这将是一个更难的问题!它至少比 Dijkstra 的算法复杂 N 倍!!!”。

      然后老师给你弗洛伊德-沃歇尔。你的头脑会爆炸。然后你开始为这个算法是多么的简单而哭泣。这只是一个三重嵌套循环。它的数据结构只使用了一个简单的数组。


      对我来说最令人大开眼界的部分是以下认识:假设您有问题 A 的解决方案。那么您有一个更大的“超级问题”B,其中包含问题 A。问题 B 的解决方案实际上可能比问题更简单问题A的解决方案。

      【讨论】:

      • 好吧,我 +1 了,因为我认为 F-W 是一个很棒的算法,但老实说,我不能说它好到让我热泪盈眶……我知道铁石心肠: -P
      【解决方案4】:

      这可能听起来微不足道,但对当时的我来说是一个启示。 我在上我的第一堂编程课(VB6),教授刚刚教我们随机数,他给出了以下说明:“创建一个虚拟彩票机。想象一个装满 100 个乒乓球的玻璃球,标记为 0 到 99。随机挑选它们并显示它们的编号,直到它们都被选中,没有重复。”

      其他人都这样写他们的程序:挑选一个球,将其编号放入“已选择列表”,然后再挑选另一个球。检查它是否已经被选中,如果是则选择另一个球,如果没有将其编号放在“已选中列表”等......

      当然,到最后,他们进行了数百次比较,以找到少数尚未被选中的球。这就像选择它们后将球扔回罐子中一样。我的启示是采摘后扔掉球。

      我知道这听起来很明显,但这是“编程开关”在我脑海中翻转的那一刻。这是编程从试图学习一门陌生的外语到试图找出一个有趣的谜题的那一刻。一旦我在编程和乐趣之间建立了这种心理联系,就真的没有人能阻止我。

      【讨论】:

      • 我在学校也做过这样的练习。我的解决方案是通过将数组的每个索引中的值与随机索引处的值交换来对球进行洗牌,然后打印出数组值。
      • @Dour:我也这样做了,但我们错了 - codinghorror.com/blog/archives/001015.html :)
      【解决方案5】:

      霍夫曼编码将是我的,我最初通过将编码文本的比特数从 8 降至更少来制作自己的哑版本,但没有考虑根据频率可变的比特数。然后我发现了杂志上一篇文章中描述的霍夫曼​​编码,它开辟了许多新的可能性。

      【讨论】:

        【解决方案6】:

        Quicksort。它向我展示了递归是强大而有用的。

        【讨论】:

          【解决方案7】:

          Bresenham's line drawing algorithm 让我对实时图形渲染产生了兴趣。这可用于渲染填充的多边形,如三角形,用于 3D 模型渲染等。

          【讨论】:

            【解决方案8】:

            Recursive Descent Parsing - 我记得如此简单的代码可以完成如此​​看似复杂的事情给我留下了深刻的印象。

            【讨论】:

              【解决方案9】:

              Haskell 中的快速排序:

              qsort []     = []
              qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)
              

              虽然当时我不会编写 Haskell,但我确实理解了这段代码以及递归和快速排序算法。它只是点击了一下,它就在那里......

              【讨论】:

              • 是的,我还记得那个——很酷;它不是就地算法,但无论如何......
              【解决方案10】:

              斐波那契的迭代算法,因为对我来说它确定了一个事实,即最优雅的代码(在本例中为递归版本)不一定是最有效的。

              详细说明-“fib(10) = fib(9) + fib(8)”方法意味着 fib(9) 将被评估为 fib(8) + fib(7)。因此对 fib(8)(以及 fib7、fib6)的评估都会被评估两次。

              迭代方法(curr = prev1 + prev2 in a forloop)不会以这种方式生成树,也不会占用太多内存,因为它只有 3 个瞬态变量,而不是递归堆栈中的 n 帧。

              我在编程时倾向于追求简单、优雅的代码,但正是这种算法帮助我意识到,这并不是编写好软件的全部目标,而最终是终点用户并不关心您的代码的外观。

              【讨论】:

                【解决方案11】:

                出于某种原因,我喜欢Schwartzian transform

                @sorted = map  { $_->[0] }
                          sort { $a->[1] cmp $b->[1] }
                          map  { [$_, foo($_)] }
                                @unsorted;
                

                其中 foo($) 表示计算密集型表达式,该表达式采用 $(依次为列表中的每个项目)并生成相应的值,以便对其进行比较。

                【讨论】:

                • 好的,我认为这可能很有趣。不过,您使用的语言有点晦涩:/。你能提供一些评论吗?
                • 顺便说一句,这种模式最初被老派的 lispers 称为 decorate sort undecorate。 Randall 在 IRC 上向一些人展示了这个,从那时起它就以他的名字命名。这是一个更清晰的python版本: sorted = [ v for k,v in sorted([ foo(k), k for k in unsorted ], key=lambda x: [0]) ] 诀窍基本上是“缓存”在元组列表中进行比较的关键函数,而不是为每次比较计算它。这将 foo 的平均评估从 nlogn 减少到 n。
                【解决方案12】:

                Minimax 告诉我国际象棋程序并不聪明,它们只会比你想出更多的动作。

                【讨论】:

                  【解决方案13】:

                  我不知道这是否属于一种算法,或者只是一种经典的 hack。无论哪种情况,它都帮助我开始跳出框框思考。

                  在不使用中间变量的情况下交换 2 个整数(在 C++ 中)

                  void InPlaceSwap (int& a, int &b) {
                       a ^= b;
                       b ^= a;
                       a ^= b;
                  }
                  

                  【讨论】:

                  • 纯粹出于好奇,有没有理由这样做而不是使用 std::swap?
                  • 在内存非常低的环境中(例如 - 嵌入式系统),这可以节省额外内存的分配。有点弱,我知道。就像我说的,重点是跳出框框思考。乍一看,您会认为没有中间变量就不可能进行交换。
                  • 如果在两个参数中传递相同的变量,此代码将失败。不像听起来那么不可能!
                  【解决方案14】:

                  快速排序:在我上大学之前,我从未质疑过暴力冒泡排序是否是最有效的排序方式。它看起来很直观。但接触 Quicksort 等非显而易见的解决方案后,我学会了忽略显而易见的解决方案,看看是否有更好的解决方案。

                  【讨论】:

                    【解决方案15】:

                    对我来说,这是弱堆排序算法,因为它展示了 (1) 明智选择的数据结构(以及处理它的算法)可以在多大程度上影响性能,以及 (2) 即使在旧的情况下也可以发现令人着迷的东西,众所周知的事情。 (弱堆排序是所有堆排序中最好的变种,八年后是proven。)

                    【讨论】:

                      【解决方案16】:

                      这是一个缓慢的:)

                      通过了解Duffs DeviceXOR swaps,我总体上学到了很多关于 C 和计算机的知识

                      编辑:

                      @Jason Z,这是我的异或交换 :) 很酷,不是吗。

                      【讨论】:

                        【解决方案17】:

                        出于某种原因,冒泡排序对我来说一直很突出。不是因为它优雅或好,只是因为我想它有/有一个愚蠢的名字。

                        【讨论】:

                          【解决方案18】:

                          斐波那契的迭代算法,因为对我来说它确定了一个事实,即最优雅的代码(在本例中为递归版本)不一定是最有效的。

                          迭代方法(curr = prev1 + prev2 in a forloop)不会以这种方式生成树,也不会占用太多内存,因为它只有 3 个瞬态变量,而不是递归堆栈中的 n 帧。

                          您知道斐波那契有一个封闭形式的解决方案,它允许以固定的步数直接计算结果,对吧?即,(phin - (1 - phi)n) / sqrt(5)。我总是觉得这应该产生一个整数有点不可思议,但确实如此。

                          当然,phi 是黄金比例; (1 + sqrt(5)) / 2.

                          【讨论】:

                            【解决方案19】:

                            我没有最喜欢的——有很多漂亮的可供选择——但我一直觉得很有趣的是Bailey–Borwein–Plouffe (BBP) formula,它可以让你计算出一个不知道前面的数字的任意数字。

                            【讨论】:

                              【解决方案20】:

                              RSA向我介绍了模运算的世界,可以用来solveasurprisingnumberofinterestingproblems

                              【讨论】:

                                【解决方案21】:

                                并没有教会我太多,但Johnson–Trotter Algorithm 总是让我大吃一惊。

                                【讨论】:

                                  【解决方案22】:

                                  Binary decision diagrams,虽然形式上不是算法,而是数据结构,但可以为各种(布尔)逻辑问题提供优雅且最小的解决方案。它们的发明和开发是为了最大限度地减少芯片设计中的门数,并且可以被视为硅革命的基础之一。生成的算法非常简单。

                                  他们教给我的:

                                  • 任何问题的简洁表示很重要;小就是美
                                  • 可以使用一小组以递归方式应用的约束/减少来完成此操作
                                  • 对于对称性问题,首先要考虑转换为规范形式
                                  • 并非所有文献都被阅读。 Knuth 在 BDD 发明/推出几年后发现了他们。 (并花了将近一年的时间调查它们)

                                  【讨论】:

                                    【解决方案23】:

                                    对我来说,当我第一次看到它时,在 Kelly 和 Pohl 的 A Book on C 中演示引用调用的简单交换让我大吃一惊。我看了看,指针突然就位。逐字。 . .

                                    void swap(int *p, int *q)
                                    {
                                       int temp;
                                    
                                       temp = *p;
                                       *p = *q;
                                       *q = temp;
                                    }
                                    

                                    【讨论】:

                                      【解决方案24】:

                                      Towers of Hanoi algorithm 是最漂亮的算法之一。它展示了如何使用递归以比迭代方法更优雅的方式解决问题。

                                      另外,斐波那契数列的递归算法和一个数字的计算幂证明了递归算法的相反情况,它被用于递归而不是提供良好的价值。

                                      【讨论】:

                                        【解决方案25】:

                                        一种通过将每个数字与当前素数列表进行比较来生成素数列表的算法,如果没有找到则添加它,并在最后返回素数列表。在几个方面令人费解,其中最重要的是使用部分完成的输出作为主要搜索条件的想法。

                                        【讨论】:

                                          【解决方案26】:

                                          在一个单词中为双向链表存储两个指针让我学到了在 C 中确实可以做非常糟糕的事情(保守的 GC 会遇到很多麻烦)。

                                          【讨论】:

                                            【解决方案27】:

                                            我最自豪的解决方案是编写与 DisplayTag 包非常相似的东西。它教会了我很多关于代码设计、可维护性和重用的知识。我在 DisplayTag 之前就写好了它,它被纳入 NDA 协议,所以我不能开源它,但我仍然可以在工作面试中滔滔不绝地谈论它。

                                            【讨论】:

                                              【解决方案28】:

                                              映射/减少。两个简单的概念结合在一起,使大量数据处理任务更容易并行化。

                                              哦...这只是大规模并行索引的基础:

                                              http://labs.google.com/papers/mapreduce.html

                                              【讨论】:

                                                【解决方案29】:

                                                不是我最喜欢的,但用于测试素性的Miller Rabin Algorithm 向我表明,几乎所有时间都是正确的,几乎所有时间都足够好。 (即不要仅仅因为概率算法有错误的概率而怀疑它。)

                                                【讨论】:

                                                  【解决方案30】:

                                                  @克里希纳库马尔

                                                  按位解决方案比递归解决方案更有趣。

                                                  【讨论】:

                                                    猜你喜欢
                                                    • 2011-01-03
                                                    • 1970-01-01
                                                    • 2010-09-27
                                                    • 1970-01-01
                                                    • 1970-01-01
                                                    • 1970-01-01
                                                    • 1970-01-01
                                                    • 1970-01-01
                                                    • 2010-11-05
                                                    相关资源
                                                    最近更新 更多