【问题标题】:Is it possible to reduce a quadratic (or higher) complexity to linear?是否可以将二次(或更高)复杂度降低为线性?
【发布时间】:2020-11-06 19:52:13
【问题描述】:

我正在处理视频文件以查找 2 个帧块之间的相似性,假设 F 和 G,它们表示为元素的一维数组,两者的大小 n 相同,可以解释为正方形宽度为 W 和高度为 H 的像素,其中 W 和 H 不同。

为了找到帧块的相似性,我应该使用绝对差之和 SAD,它是给定大小为 Bw x Bh 的块 B 的每个帧的相同位置的所有像素差的总和。另外,我还需要所有 SAD 值的平均值。

考虑到给定 W、H、Bw 和 Bh 的值,可以通过将这两个值相乘来找到行中的所有块和列中的所有块,从而找到大小为 Bw x Bh 的所有块,我想到的最快的实现是这样的:

使用sad_total 变量,循环遍历列中的所有块,然后遍历每一行中的所有块,其中获取单个块的curr_sad 的变量通过遍历所有Bh 列和所有Bw 行来获取它一个块,其中curr_sad是通过在指定索引处的两个帧的元素减法的绝对值获得的,然后添加到sad_total,然后重复直到它全部,然后除以块的总数得到平均值。

假设 Bw 和 Bh 相同,就 Bw 而言,我这样得到 O(n^4) 的复杂度。考虑到我无法排序,因为我需要按顺序减去每个元素,有没有办法减少循环的嵌套?

为了清楚起见,请查看所附图片,并感谢任何可能提供帮助的人

【问题讨论】:

  • 由于行列的多次求和重复,缓存求和结果可能会节省一些时间。例如,可以先处理 F-G,因此您可以在最内部的循环中查找表以查找 Add。如果应该将时间复杂度从 n^4 降低到 n^3。如果我以错误的方式理解您的问题,请纠正我。如果缓存了行总和或列总和,您的第二个内部循环可能会减少。注意:这可能会占用空间。

标签: c++ algorithm time-complexity


【解决方案1】:

考虑使用累积和 - 计算从左上角 0,0i,j 结束的矩形中所有差异的总和矩阵。它可以在O(n^2) 中相对于矩阵大小n(与元素计数成线性关系)计算(Python example

那么矩形(x1,y1)-(x2,y2)中的值之和等于sum(x2,y2) + sum(x1,y1)-sum(x2,y1)-sum(x1,y2),所以O(1)代表每个窗口,O(n^2)代表整个矩阵

例如,查看integral 函数的 OpenCV 描述(也许您甚至可以将 OpenCV 库用于您的目的)。

【讨论】:

    【解决方案2】:

    首先计算从0, 0W-1, H-1 的所有i, j 的矩阵和。这可以使用动态编程在O(W*H) 中完成。其中的递归关系类似于F(i, j) = F(i, j-1) + F(i-1, j) - F(i-1, j-1) + M[i][j]。然后我们可以创建一个 2D Fenwick 树并将这些值插入其中。那么对于这棵树中的i, j,我们可以得到O(log(W)*log(H))中的累积和。因此对于所有i, j,总时间将是O(W*H*log(W)*log(H))

    这两个基本步骤的代码:

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    int sum(const vector<vector<int>>& BIT2D, int x, int y)
    {
        int res = 0;
    
        for (int i = x; i > 0; i -= i & -i)
            for (int j = y; j > 0; j -= j & -j)
                res += BIT2D[i][j];
        return res;
    }
    
    void add(vector<vector<int>>& BIT2D, int x, int y, int value)
    {
        for (int i = x; i < BIT2D.size(); i += i & -i)
            for (int j = y; j < BIT2D[i].size(); j += j & -j)
                BIT2D[i][j] += value;
    }
    
    int foobar(const vector<vector<int>>& matrix, vector<vector<int>>& dp, int i, int j)
    {
        if (dp[i][j] != -1)
            return dp[i][j];
    
        if (i == 0 || j == 0)
            return dp[i][j] = 0;
        return dp[i][j] = foobar(matrix, dp, i, j - 1) + foobar(matrix, dp, i - 1, j) - foobar(matrix, dp, i - 1, j - 1) + matrix[i - 1][j - 1];
    }
    
    int main()
    {
        int N, M;
        cin >> N >> M;
        vector<vector<int>> matrix(N, vector<int>(M)), dp(N + 1, vector<int>(M + 1, -1));
        for (int i = 0; i < matrix.size(); ++i)
            for (int j = 0; j < matrix[i].size(); ++j)
                cin >> matrix[i][j];
        foobar(matrix, dp, N, M);
    
        vector<vector<int>> BIT2D(N + 1, vector<int>(M + 1));
        for (int i = 1; i < dp.size(); ++i)
            for (int j = 1; j < dp[i].size(); ++j)
                add(BIT2D, i, j, dp[i][j]);
        for (int i = 1; i < BIT2D.size(); ++i)
        {
            for (int j = 1; j < BIT2D[i].size(); ++j)
                cout << sum(BIT2D, i, j) << ' ';
            cout << endl;
        }
    }
    

    对第二个矩阵做同样的事情,然后我们可以计算SAD。事实上,如果修改正确,您可以在一次调用foobar 内对第二个矩阵进行相同的计算(以及计算SAD)。时间复杂度将保持O(W*H*log(W)*log(H))

    编辑:可以在O(W*H) 中计算累积和。无需创建 Fenwick 树。在传递给foobar 的第一个调用的dp 上再次调用foobar,并将结果存储在dp2

    【讨论】:

    • 等等...我们可以在第一次调用foobar 形成的dp 上调用foobar,这将给出相同的答案。哈哈~
    • 我们不需要查询 Fenwick 树来获取矩形窗口中的总和,因为有更简单的 O(1) 方法 - 请查看我的答案。
    • @MBo 是的,我后来注意到了。这就是我在评论中所说的。
    【解决方案3】:

    看来我误解了我自己对这个操作的实现。即使我们假设 Bh 和 Bw 相同,复杂度实际上也不是 4。从伪代码:

      // assuming the 1D array width is w, the height is h, and both Bh and Bw is b:
    
      for (int i = 0; i < h; i += b) {
        for (int j = 0; j < w; j += b) {
          for (int y = 0; y < b; y++) {
            for (int x = 0; x < b; x++) {
              // code
            }
          }
        }
      }
    

    对于b,我们可以看出最里面的两个循环都是O(b)。但是最外面的两个循环是不同的。给定b 的步长/更新值,最外层的复杂度为O(h / b),另一个复杂度为O(w / b)。因此,实际的复杂性是:

    O((h / b) * (w / b) * b * b ) = O(h * w)

    而且,由于h * w 是用于此的一维元素数组的实际大小,因此实现在数组大小方面具有这种线性复杂性是有道理的,因为要获得所有块的平均值,我需要访问所有索引。

    【讨论】:

    • 所以...你需要非交叉块。我只是没有注意到伪代码中的step B。是的。你的实现是线性的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-29
    • 2011-01-10
    • 1970-01-01
    • 2020-10-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多