【问题标题】:algorithm - Sort an array with LogLogN distinct elementsalgorithm - 对具有 LogLogN 不同元素的数组进行排序
【发布时间】:2012-04-15 09:27:12
【问题描述】:

这不是我学校的家庭作业。这是我自己的家庭作业,我是自学算法。

Algorithm Design Manual,有这样的消费税

4-25 假设数组 A[1..n] 只有 {1, . . . , n^2} 但最多 log log n 这些数字曾经出现过。设计一种算法,以大大小于 O(n log n) 的方式对 A 进行排序。

我有两种方法:


第一种方法:

基本上我想对这个问题进行计数排序。我可以首先扫描整个数组 (O(N)) 并将所有不同的数字放入 loglogN 大小的数组 (int[] K) 中。

然后应用计数排序。但是,在设置计数数组(int[] C)时,我不需要将其大小设置为 N^2,而是将其大小也设置为 loglogN。

但是这样,当计算每个不同数字的频率时,我必须扫描数组 K 以获取该元素的索引 (O(NloglogN) ,然后更新数组 C。


第二种方法:

再次,我必须扫描整个数组以获得一个不同的数字数组 K,其大小为 loglogN。

然后我只是做一种快速排序,但分区是基于 K 数组的中位数(即,每次枢轴是 K 数组的一个元素),递归。

我认为这种方法最好,O(NlogloglogN)。


我说的对吗?还是有更好的解决方案?

算法设计手册中存在类似的删减,例如

4-22 证明可以在 O(n log k) 时间内对 1 到 k 范围内的 n 个正整数进行排序。有趣的情况是当 k

4-23 我们试图对具有许多重复的 n 个整数的序列 S 进行排序,使得 S 中不同整数的数量为 O(log n)。给出一个 O(n log log n) 最坏情况时间算法来对这些序列进行排序。

但基本上对于所有这些消费税,我的直觉总是考虑计数排序,因为我们可以知道元素的范围,并且与整个数组的长度相比,范围足够短。但是经过更深入的思考,我猜消费税要寻找的是第二种方法,对吧?

谢谢

【问题讨论】:

  • 我们可以使用大小为 log log n 个元素的 BST 结构为什么 - 我们想对较小的元素进行排序以获得更短的运行时间(我不考虑计数排序,因为它会占用太多空间比我的原始数组)我们可以在每个节点维护计数器来处理重复项 T(n) =O(元素数 * bst 的高度) = O(n * log log log n) 你是如何计算排序数组的大小 log log n 而不是 n^2

标签: algorithm sorting data-structures


【解决方案1】:

我们可以创建一个哈希映射,将每个元素存储为键,将其频率存储为值。

使用任何排序算法在 log(n)*log(log(n)) 时间即 (klogk) 中排序此地图。

现在扫描哈希图并将元素添加到新的数组频率次数。像这样:

total time = 2n+log(n)*log(log(n)) = O(n) 

【讨论】:

    【解决方案2】:

    计数排序是一种可能的方式:

    1. 我将在示例 2、8、1、5、7、1、6 上演示此解决方案,所有数字均
    2. 首先为每个数字 A[i] 计算 A[i] / N。让我们将此数字称为 first_part_of_number
    3. 使用按first_part_of_number 的计数排序对此数组进行排序。
    4. 结果以形式表示(例如 N = 3)

      (0, 2)
      (0, 1)
      (0, 1)
      (2, 8)
      (2, 6)
      (2, 7)
      (2, 6)

    5. first_part_of_number将它们分组。

    6. 在此示例中,您将拥有组
      (0, 2) (0, 1) (0, 1)

      (2, 8) (2, 6) (2, 7) (2, 6)

    7. 对于每个数字计算 X 模 N。我们称之为 second_part_of_number。将此数字添加到每个元素
      (0, 2, 2) (0, 1, 1) (0, 1, 1)

      (2, 8, 2) (2, 6, 0) (2, 7, 1) (2, 6, 0)

    8. 使用计数排序按second_part_of_number对每个组进行排序

      (0, 1, 1) (0, 1, 1) (0, 2, 2)

      (2, 6, 0) (2, 6, 0) (2, 7, 1) (2, 8, 2)

    9. 现在组合所有组,您得到结果 1、1、2、6、6、7、8。

    复杂性: 您只对元素

    【讨论】:

    【解决方案3】:

    我要在这里暴露我对算法复杂性的有限知识,但是:

    扫描一次数组并构建类似自平衡树的东西不是有意义吗?正如我们所知,树中的节点数只会增长到(log log n),每次找到一个数字相对便宜(?)。如果找到(可能)重复编号,则该节点中的计数器会增加。 然后构造排序数组,按顺序读取树。

    也许有人可以评论这件事的复杂性和任何缺陷。

    【讨论】:

    • 关于复杂性问题:这样做是O(nlogloglogn),这与我在解决方案中建议的想法相同[“使用地图而不是数组”] - 此解决方案使用基于树的地图实施。
    【解决方案4】:

    更新:在我写下下面的答案后,@Nabb 告诉我为什么它不正确。有关详细信息,请参阅 Õ 上的 Wikipedia's brief entry 及其链接。至少因为它仍然需要为@Nabb 和@Blueshift 的 cmets 提供上下文,并且因为整个讨论仍然很有趣,所以保留了我的原始答案,如下所示。

    原始答案(错误)

    让我提供一个非常规的答案:虽然 O(n*n) 和 O(n) 之间确实存在差异,但 O(n) 和 O(n*log(n)) 之间没有差异。

    现在,当然,我们都知道我刚才说的是错误的,不是吗?毕竟,许多作者都同意 O(n) 和 O(n*log(n)) 不同。

    除了它们没有区别。

    如此激进的立场自然需要证明,因此请考虑以下事项,然后自己决定。

    在数学上,本质上,函数 f(z) 的阶 m 是这样的 f(z)/(z^(m+epsilon) ) 收敛,而 f(z)/(z^(m-epsilon)) 发散,因为 z 幅度很大且为实数,正 epsilon 任意小幅度。 z 可以是真实的或复杂的,尽管正如我们所说的 epsilon 必须是真实的。有了这个理解,将 L'Hospital 规则应用于 O(n*log(n)) 的函数,看看它与 O(n) 的函数在顺序上没有区别。

    我认为目前公认的计算机科学文献在这一点上略有错误。这篇文献最终会完善它在这件事上的立场,但它还没有做到。

    现在,我不希望你今天同意我的看法。毕竟,这只是 Stackoverflow 上的一个答案——与经过编辑的、经过正式同行评审的、已出版的计算机科学书籍相比,这算什么——更不用说书架上的书了?你今天不应该同意我的观点,只接受我在建议下写的东西,在接下来的几周里在你的脑海中仔细考虑,参考前面提到的一两本采取其他立场的计算机科学书籍,然后下定决心.

    顺便说一句,这个答案的位置的一个违反直觉的含义是,人们可以在 O(1) 时间内访问平衡的二叉树。再说一次,我们都知道那是错误的,对吧?它应该是 O(log(n))。但请记住:O() 表示法从来都不是用来精确衡量计算需求的。除非 n 非常大,否则其他因素可能比函数的顺序更重要。但是,即使 n = 100 万,log(n) 也只有 20,与 sqrt(n) 相比,sqrt(n) 是 1000。我可以继续这样做。

    不管怎样,考虑一下。即使最终你决定不同意我的观点,你仍然会发现这个职位很有趣。就我而言,我不确定 O() 表示法在 O(log something) 方面到底有多大用处。

    @Blueshift 提出了一些有趣的问题,并在下面的 cmets 中提出了一些有效的观点。我建议你阅读他的话。除了观察这一点外,我真的没有太多要补充的内容,因为很少有程序员在复变量的数学理论 O(log(n)) 方面拥有(或需要)扎实的基础符号可能误导了数十万程序员,他们相信他们在计算效率方面取得了大部分虚幻的收益。在实践中很少能将 O(n*log(n)) 减少到 O(n) 真正给你带来你可能认为它给你带来的东西,除非你有一个清晰的脑海中的对数真正慢得令人难以置信的函数的形象——而将 O(n) 甚至减少到 O(sqrt(n)) 可以为您带来很多好处。几十年前,数学家会告诉计算机科学家,但计算机科学家没有听,很着急,或者不明白这一点。没关系。我不介意。我不明白的其他主题有很多很多点,即使这些点已经向我仔细解释过。但这是我相信我确实理解的一点。从根本上说,这是一个数学点而不是计算机点,在这一点上,我碰巧站在列别捷夫和数学家一边,而不是站在高德纳和计算机科学家一边。就这些了。

    【讨论】:

    • 在你发表这篇文章之前,我想我会坚持使用 Knuth。
    • @blueshift:没错。好吧,也许有一天我会尝试发布它,但要推动一个矛盾的立场超越对 Knuth 的既定立场进行了数十年投资的同行并不容易(也不应该如此)。而且,毕竟,高德纳的处境也不错。 Knuth 的立场很有趣。我只是认为这是错误的。
    • 我不明白声称 100 万 = 2000 万有什么意义或有用。
    • @blueshift:顺序是一个概念,这个概念在算法的初始设计中很有用。当然,100万和2000万不一样,就像你说的那样。
    • 大O符号是一个正式定义的概念,你的定义不是公认的定义(我想你可能对Õ感兴趣)。
    猜你喜欢
    • 2014-12-28
    • 2017-08-25
    • 2018-04-08
    • 2013-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多