algorithm - 使用LogLogN distinct元素对数组进行排序

时间:2012-04-01 12:06:46

标签: algorithm sorting data-structures

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

Algorithm Design Manual中,有这样的消费税

  

4-25假设数组A [1..n]只有{1,...的数字。 。 。 ,n ^ 2}但是这些数字的最多日志log n出现了。设计一种算法,将A排序在一个基本上小于O(n log n)的范围内。

我有两种方法:


第一种方法:

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

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

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


第二种方法:

同样,我必须扫描整个数组以获得具有loglogN大小的不同数字数组。

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

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


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

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

  

4-22显示1到k范围内的n个正整数可以在O(n log k)时间内排序。有趣的情况是当k <&lt; ñ。

     

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

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

由于

4 个答案:

答案 0 :(得分:5)

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

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

现在扫描哈希映射并将元素添加到新阵列频率次数。像这样:

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

答案 1 :(得分:0)

计算排序是可能的方法之一:

  1. 我将在示例2,8,1,5,7,1,6上演示此解决方案,所有数字都是&lt; = 3 ^ 2 = 9.我使用更多元素来使我的想法更清晰。
  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。

  10. 复杂性: 您只使用元素&lt; = N计数排序。 每个元素都参与了2个“排序”。所以整体复杂度是O(N)。

答案 2 :(得分:0)

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

原始答案(不正确)

让我提供一个非传统的答案:虽然O(n * n)和O(n)之间确实存在差异,但O(n)和O(n * log(n))之间没有区别。 / p>

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

除了它们没有区别。

如此激进 - 看似一个立场自然要求辩护,所以请考虑以下因素,然后自己决定。

从数学上讲,本质上,函数 f(z) m 的顺序是 f(z)/(z ^(m + epsilon) )收敛,而 f(z)/(z ^(m-epsilon))对于大幅度的 z 和实数 epsilon < / em>任意小幅度。 z 可以是真实的或复杂的,但正如我们所说 epsilon 必须是真实的。根据这种理解,将L'Hospital的规则应用于O(n * log(n))的函数,看它与O(n)函数的顺序没有区别。

我认为目前公认的计算机科学文献在这一点上有些误解。这篇文献最终会改进其在这个问题上的立场,但尚未完成。

现在,我不希望你今天同意我的看法。毕竟,这仅仅是Stackoverflow的一个答案 - 与经过编辑的,正式的同行评审,出版的计算机科学书籍相比,还有什么呢?更不用说这些书籍的搁置了?你今天不应该同意我的观点,只考虑我在建议中所写的内容,在未来几周内考虑一下你的想法,参考一两个上述计算机科学书籍,这些书籍占据另一个位置,并构成你自己的想法

顺便提一下,这个答案的立场具有违反直觉的含义,即可以在O(1)时间内访问平衡二叉树。我们都知道这是假的,对吧?它应该是O(log(n))。但请记住:O()符号从未打算精确测量计算需求。除非 n 非常大,否则其他因素可能比函数的顺序更重要。但是,即使对于 n = 1百万,log(n)也只有20,比如说,与sqrt(n)相比,即1000.我可以继续这样做。

无论如何,请考虑一下。即使你最终决定不同意我,你也可能会觉得这个位置很有趣。就我而言,我不确定当O(记录某些东西)时O()符号真的有用。

@Blueshift提出了一些有趣的问题,并在下面的评论中提出了一些有效的观点。我建议你读他的话。除了观察之外,我并没有太多要添加到他所说的内容,因为很少有程序员在复杂变量的数学理论中具有(或需要)坚实的基础,O(log(n))可能会误导符号,确实有成千上万的程序员认为他们在计算效率上实现了大多数虚幻的收获。在实践中很少将O(n * log(n))减少到O(n)真的会给你带来你认为它会给你买的东西,除非你有一个明确的心理形象,即对数的功能是多么令人难以置信的慢 - 而将O(n)减少到O(sqrt(n))可以为你买很多东西。数十年前数学家会告诉计算机科学家,但计算机科学家并没有倾听,匆忙,或者不明白这一点。那没关系。我不介意。关于其他我不理解的主题有很多很多要点,即使这些要点都经过仔细解释。但这是我相信我碰巧理解的一点。从根本上说,这是一个数学点,而不是计算机点,而且我恰好与Lebedev和数学家站在一起,而不是与Knuth和计算机科学家站在一起。这就是全部。

答案 3 :(得分:0)

我将在这里背叛我对算法复杂性的有限知识,但是:

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

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