【问题标题】:Given O(n) sets, what is complexity of figuring out distinct ones amongst them?给定 O(n) 个集合,找出其中不同的集合的复杂性是多少?
【发布时间】:2019-07-16 05:05:02
【问题描述】:

我有一个应用程序,其中有一个O(n) 集合列表。

每组Set(i) 是一个n-vector。假设n=4,例如,

Set(1) 可以是[0|1|1|0]

Set(2) 可以是[1|1|1|0]

Set(3) 可以是[1|1|0|0]

Set(4) 可以是[1|1|1|0]

我想处理这些集合,以便作为输出,我只得到其中唯一的集合。所以,在上面的例子中,我会得到输出:

Set(1), Set(2), Set(3)。请注意,Set(4) 被丢弃,因为它与 Set(2) 相同。

一种相当蛮力的计算方式给了我O(n^3)的最坏情况界限:

Given: Input List of size O(n)
Output List L = Set(1)

for(j = 2 to Length of Input List){ // Loop Outer, check if Set(j) should be added to L
    for(i = 1 to Length of L currently){ // Loop Inner
       check if Set(i) is same as Set(j) //This step is O(n) since Set() has O(n) elements
       if(they are same) exit inner loop
       else
            if( i is length of L currently) //so, Set(j) is unique thus far
                  Append Set(j) to L               
    }
 }

n 没有先验界限:它可以任意大。这似乎排除了使用将二进制集映射为十进制的简单散列函数。我可能是错的。

除了O(n^3)之外,还有其他方法可以在最坏情况下运行时间更好吗?

【问题讨论】:

    标签: algorithm data-structures time-complexity complexity-theory


    【解决方案1】:

    O(n) 个长度为 n 的序列构成一个大小为 O(n^2) 的输入。您不会得到比这更好的复杂性,因为您至少可能需要阅读所有输入。例如,所有序列可能都相同,但您必须全部阅读才能知道这一点。

    长度为 n 的二进制序列可以插入到 trie 或基数树中,同时检查它是否已经存在,时间为 O(n)。所有序列加在一起的时间为 O(n^2),因此只需使用 trie 或基数树来查找重复项是最佳的。

    见:https://en.wikipedia.org/wiki/Trie 和:https://en.wikipedia.org/wiki/Radix_tree

    【讨论】:

    • 哇。这似乎是最优的。 @Tim Biegeleisen 的答案是否有任何空间权衡,具有更高的复杂性?此外,在我的应用程序中,每个不同集合中的 1 的索引对我来说相当重要。在检索不同的键时,在 Trie 中,1 的索引在完成后是否也很容易检索?
    • 使用二进制字母表,基数树节省空间——至少与 BST 一样好。我不完全确定您对 1 的索引是什么意思,但答案肯定是实现它并不难。
    • 我所说的索引的意思是,在 OP 中,当我处理输出列表时,我将处理 Set(1) (假设索引是从 L 到 R)然后 1;s 的索引是1 和 2。接下来,我处理输出的 Set(2),我需要能够获得索引 0、1、2,例如等等。
    • 我应该提到,如果您不从索引 0 开始每次比较,您也可以在 O(n) 时间内插入 BST - 您必须避免比较已经确定的前缀平等。
    • 很容易从基数树中读取每个字符串以确定所有1的索引。如果 1 比较稀疏,而您需要更快地使用它们,您可以记录它们在叶子中的位置。
    【解决方案2】:

    您可以考虑使用平衡二叉树来实现您的集合。在这样的树中插入一个新节点的成本是O(lgm),其中m 是树中元素的数量。重复将被隐式清除,因为如果我们检测到这样的节点已经存在,那么它就不会被添加。

    在您的示例中,查找/插入操作的总数为 n*n,因为有 n 集,并且每个集都有 n 值。因此,总时间可能会缩放为O(n^2*lg(n^2))。这在一定程度上优于O(n^3)

    【讨论】:

    • 因此,如果每个集合都是一个标量键,那么插入一个新键(在确定确实需要插入之后)是 O(log n)。但是在这种情况下,因为每个集合都是一个n向量,所以上面的操作变成了O(log n^2)。我对吗?然后,就外部和内部循环而言,我得到 O(n^2),因此网络为 O(n^2 log n^2)。我对你的论点的理解正确吗?
    • 是的,没错。我觉得复杂度大概是O(n*n*lgn),还是比你现在的好一点。
    • 这个不应该考虑!他已经有一个应用程序,您需要了解他不能创建另一个 ds 来实现结果!创建另一个 ds,我猜这里不好玩!
    • @ReturnZero 总的来说,对于阅读此问题的人来说,更改数据结构以实现更好的性能实际上是受到娱乐的。
    【解决方案3】:

    首先,这些不是集合,而是位串。

    接下来,对于每个位串,您可以将其转换为数字并将该数字放入哈希集中(或者简单地存储原始位串,大多数哈希集实现都可以这样做)。之后,您的哈希集包含所有唯一项。 O(N) 时间,O(N) 空间。如果您需要保持字符串的原始顺序,则在第一个循环中检查每个字符串是否已经在哈希集中,如果没有,则将其输出并插入哈希集中。

    【讨论】:

    • 我希望能够知道 1 的索引。如果我将它们存储在数组或向量中,那么 1s 特征的索引对我来说很重要。找出 1 是一个集合(具有 0/1 的数组或向量)中的 O(n) 操作。在位串中很容易做到这一点吗?
    • 您的项目是位串。如何存储它们完全取决于您。例如,在 C++ 的 STL 中,有两个专门用于此目的的类:std::bitstring 和专门化的 std::vector<bool>
    • 我不确定每个位串都可以转换成数字部分。如果 n 是 100000 会怎样?我不清楚任何散列函数可以将其转换为机器中可表示的数字。不会有碰撞需要担心吗?我猜这不会使这个 O(n) 成为可能。
    • 同样,STL 散列函数是为所有标准类型 IIRC 定义的,因此您可以直接保留字符串,而无需转换为数字。
    【解决方案4】:

    如果你可以使用 O(n) 额外的空间,你可以试试这个:

    首先,我们假设向量是二进制数,所以 0110 变成了 6。

    • 这是在向量中的数字为 [0,1] 的情况下,否则您可以乘以 10 而不是 2。

    将所有向量转换为小数需要 O(4n)。 对于每个转换后的数字,我们将用十进制数映射向量。为了实现这一点,我们将使用一个 n 大小的哈希映射。

    1. HM
    2. 对于每个向量 v: num
    3. 遍历 HM,每个索引只取一个

    运行时分步:

    1. O(n)
    2. O(n*(4+1)) ,当1为映射时间时,4为向量长度
    3. O(n)

    【讨论】:

    • 但是在 n 上不可能有先验的界限。那么,如果 n 为 1000 会怎样。2^1000 无法充分表示。
    • 您可以尝试使用基数排序对向量进行排序,这需要 O(kn),此时 k 是向量的数量,n 是每个向量的长度。向量排序后,您可以遍历排序后的向量数组并避免重复的向量(检查 prev == curr 或类似的东西)。
    猜你喜欢
    • 1970-01-01
    • 2012-10-08
    • 2019-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-27
    • 1970-01-01
    相关资源
    最近更新 更多