【问题标题】:Efficient way to find all zeros in a matrix?在矩阵中找到所有零的有效方法?
【发布时间】:2011-08-24 23:23:46
【问题描述】:

我正在考虑一种有效的算法来查找一行矩阵中的零个数,但只能考虑 O(n2) 解决方案(即通过迭代每一行和每一列)。有没有更有效的方法来计算零?

例如,给定矩阵

3、4、5、6 7、8、0、9 10、11、12、3 4、0、9、10

我会报告有两个零。

【问题讨论】:

  • 嗯,你必须至少阅读每个元素......所以它充其量是 O(n^2)。
  • 只是为了确定,你的情况是什么?通常这是输入大小,这意味着元素的数量。如果您通过行数/列数定义大小,那么您将 n 和 m 作为变量,因为矩阵可能不是正方形。复杂性取决于您对问题规模的定义。

标签: algorithm matrix


【解决方案1】:

不存储任何外部信息,不,你不能做得比 Θ(N2) 更好。理由很简单 - 如果您没有查看矩阵中的所有 N2 个位置,那么您不能保证您找到了所有的零并且最终可能会给出错误的答案背部。例如,如果我知道您查看的位置少于 N2 个,那么我可以在矩阵上运行您的算法并查看您报告了多少个零。然后我可以查看您未访问的位置,将它们全部替换为零,然后再次运行您的算法。由于您的算法不查看这些位置,因此它无法知道它们中有零,因此算法的两次运行中至少有一次会返回错误的答案。

更一般地说,在设计处理数据的算法时,查看是否可以比某些运行时做得更好的一个好方法是使用这种“对抗性分析”。问自己一个问题:如果我的运行速度超过某个时间 O(f(n)),对手是否会以改变答案但我无法检测到的方式操纵数据?这种分析,连同一些更聪明的数学,证明基于比较的排序算法在平均情况下不能比 Ω(n log n) 做得更好。

如果矩阵具有其他一些属性(例如,如果它已排序),那么您可能会比在 O(N2) 中运行做得更好。例如,假设您知道矩阵的所有行都已排序。然后您可以轻松地对每一行进行二进制搜索以确定它包含多少个零,这需要 O(N log N) 时间并且速度更快。

根据您的设置参数,如果您假设您被允许并行扫描,您可能能够让算法运行得更快。例如,如果您的机器上有 K 个处理器可以专用于扫描矩阵的任务,那么您可以将矩阵分成 K 个大小大致均匀的组,让每个处理器计算组中零的数量,然后将这些计算的结果相加。这最终会为您提供 Θ(N2 / K) 的运行时间,因为运行时间是跨多个内核拆分的。

【讨论】:

    【解决方案2】:

    总是 O(n^2) - 或者更确切地说 O(n x m)。你不能跳过它。

    但是如果您知道矩阵是sparse(只有少数元素具有非零值),则您只能存储非零值和矩阵大小。然后考虑使用散列而不是存储整个矩阵 - 通常创建将行号映射到嵌套散列的散列。

    例子:

    m = 
    [ 
      0 0 0 0
      0 2 0 0
      0 0 1 0
      0 0 1 0
    ]
    

    将表示为:

    row_numbers = 4
    column_numbers = 4
    hash = { 1 => { 1 => 2}, 2 => {2 => 1, 3 => 2}}
    

    然后:

    number_of_zeros = row_numbers * column_numbers - number_of_cells_in_hash(hash)
    

    【讨论】:

      【解决方案3】:

      对于任何未排序的矩阵,它应该是 O(n)。因为通常我们用“n”表示总元素。

      如果矩阵包含 X 行和 Y 列,则 X x Y = n。

      例如,在 4 X 4 未排序的矩阵中,它总共有 16 个元素。所以当我们用 2 个循环进行线性迭代时,4 X 4 = 16 次。它将是 O(n),因为数组中的总元素是 16。

      很多人投票支持 O(n^2),因为他们认为 n X n 是矩阵。

      如果我的理解有误,请纠正我。

      【讨论】:

        【解决方案4】:

        假设当你说“在矩阵的一行中”时,你的意思是你有行索引i,并且你想计算i-th 行中零的数量,你可以做得更好比 O(N^2)。

        假设N 是行数,M 是列数,然后存储你的 矩阵作为单个数组[3,4,5,6,7,8,0,9,10,11,12,34,0,9,10],然后要访问行i,您可以访问索引N*i 处的数组。

        由于数组具有恒定的时间访问,这部分不依赖于矩阵的大小。然后,您可以通过从 0N-1 访问 j 的元素 N*i + j 来遍历整行,这是 O(N),前提是您知道要访问哪一行并且您正在使用数组.

        【讨论】:

        • 没错,但是线性化矩阵并不会改变算法的整体复杂性。如果您想查看零的总数,您仍然需要查看所有内容。如果你只想看一行,即使矩阵存储为二维数组,它仍然需要 O(N) 时间。
        • 好点,虽然 2D 矩阵需要一个额外的解引用和指针算术运算。但是,这与 M 和 N 无关,因此您的观点仍然成立。
        【解决方案5】:

        由于我将解释的原因,这不是一个完美的答案,但它提供了一种可能比您描述的更快的替代解决方案:

        1. 由于您不需要知道矩阵中零点的位置,您可以将其展平为一维数组

        2. 之后,对元素执行快速排序,这可能会提供 O(n log n) 的性能,具体取决于您输入的矩阵的随机性。

        3. 最后,计算数组开头的零个元素,直到找到一个非零数。

        在某些情况下,这将比检查每个元素更快,尽管在最坏的情况下,快速排序将花费 O(n2),除了最后的零计数之外,这可能比遍历每一行更糟糕和列。

        【讨论】:

        • 您将 OP 使用的编号与正常编号混淆了。为了使问题有意义,n 应该是矩阵的行数/列数。然后快速排序将以 O( n^2 log n ) 平均和 O( n^4 ) 运行,您的解决方案会更慢。由于快速排序必须在大小为 n^2 的输入上运行,而不是通常的 n。
        • 我明白你的意思,重新阅读他询问连续零个数的问题。这要么意味着任何给定的行(我的解决方案只需要跳过步骤 (1) 即可解决),要么意味着所有行 - 在这种情况下,如您所述,此解决方案不适合。
        【解决方案6】:

        假设给定的矩阵是M 执行M+(-M) 操作请使用默认的+ 代替my_add(int a, int b) 这样

        int my_add(int a, int b){
          return (a == b == 0) ? 1 : (a+b);      
        }
        

        这会给你一个类似的矩阵

        0  0  0  0
        0  0  1  0
        0  0  0  0
        0  1  0  0
        

        现在您创建一个s := 0 并继续将所有元素添加到 s。 s += a[i][j]

        你甚至可以在一个周期内完成这两项工作。 s += my_add(a[i][j], (-1)*a[i][j])

        但仍然是O(m*n)

        注意

        要计算 1 的数量,您通常检查矩阵中的所有项目。如果不对所有元素进行操作,我认为您无法分辨出 1 的数量。并循环其(m*n) 的所有元素。它可以比(m*n) 更快当且仅当您可以不选中某些元素并说出 1 的数量

        编辑

        但是,如果您将 2x2 内核移动到矩阵上并跳跃,您将获得 (m*n)/k 迭代,例如如果您对相邻元素进行操作 a[i][j], a[i+1][j], a[i][j+1], a[i+1][j+1] 直到 i < m & i< n

        【讨论】:

        • 这只是一个数学技巧。除非您未选中某些元素,否则您永远无法逃脱 m*n
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-09-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-20
        • 1970-01-01
        相关资源
        最近更新 更多