【问题标题】:What is the optimal way to search for value density in a given matrix?在给定矩阵中搜索值密度的最佳方法是什么?
【发布时间】:2013-07-14 23:37:31
【问题描述】:

假设我有一个带有short 的n:n 矩阵——只有正数——其中的值如下:

0 1 0 3
1 0 5 6
7 1 0 4
6 2 7 9

我正在这个矩阵中搜索一个 m:m 矩阵,其中包含大于 0 的大多数值。我的问题是我目前的解决方案不能很好地扩展 n(也不是 m )。

事实上,n:n 矩阵表示产品的价格,轴表示从给定(任意)日期开始的天数。因此,您可以在给定的时间间隔内搜索价格。 m:m 矩阵实际上是一个 7 x 7 矩阵,其中包含价格的子集(如视图)。我正在寻找n:n 矩阵中我填写的价格最多的部分。

在上面的例子中,m:m 矩阵是

7 1
6 2

m 是 2。

以下是我目前编写的原型的相关部分:

private static class ResultMatrixData {
    private byte fillCount;
    private short distanceFromToday;

    public ResultMatrixData() {
        fillCount = 0;
        distanceFromToday = Short.MAX_VALUE;
    }

    public ResultMatrixData(short[][] pricesMatrix, short iArg, short jArg) {
        byte fillCount = 0;
        for (int i = iArg; i < iArg + 7; i++) {
            for (int j = jArg; j < jArg + 7; j++) {
                if (pricesMatrix[i][j] > 0) {
                    fillCount++;
                }
            }
        }
        this.fillCount = fillCount;
        distanceFromToday = iArg > jArg ? iArg : jArg;
    }
}

private ResultMatrixData calculateSingleResult(short[][] pricesMatrix) {
    ResultMatrixData bestSoFar = new ResultMatrixData();
    ResultMatrixDataComparator comparator = new ResultMatrixDataComparator();
    for (short i = 0; i < NUMBER_OF_DAYS - 6; i++) {
        for (short j = 0; j < NUMBER_OF_DAYS - 6; j++) {
            ResultMatrixData current = new ResultMatrixData(pricesMatrix, i, j);
            if (comparator.compare(current, bestSoFar) >= ResultMatrixDataComparator.GREATER_THAN) {
                bestSoFar = current;
            }
        }
    }
    return bestSoFar;
}

private static class ResultMatrixDataComparator implements Comparator<ResultMatrixData> {
    private static final int LESS_THAN = -1;
    private static final int EQUAL = 0;
    private static final int GREATER_THAN = 1;

    @Override
    public int compare(ResultMatrixData first, ResultMatrixData second) {
        if (first.fillCount > second.fillCount) {
            return GREATER_THAN;
        } else if (first.fillCount < second.fillCount) {
            return LESS_THAN;
        } else {
            if (first.distanceFromToday < second.distanceFromToday) {
                return GREATER_THAN;
            } else if (first.distanceFromToday > second.distanceFromToday) {
                return LESS_THAN;
            }
        }
        return EQUAL;
    }
}

我的问题是运行时间似乎是二次或指数的(我没有进行准确的渐近分析):

n (days)  | running time in ms 
1 *  365  | 48
2 *  365  | 123
3 *  365  | 278
4 *  365  | 482
5 *  365  | 733
6 *  365  | 1069
7 *  365  | 1438
8 *  365  | 1890
9 *  365  | 2383
10 * 365  | 2926
11 * 365  | 3646
12 * 365  | 4208
13 * 365  | 5009

您有什么建议可以优化这个算法吗?

注意:这不是家庭作业。

编辑:正如其他人在回答中所说,这里的时间复杂度约为 O((n-m)^2)。我正在寻找亚二次型的东西,并且在n 收敛到无穷大时可以很好地扩展。

【问题讨论】:

    标签: java algorithm search matrix


    【解决方案1】:

    从理论上讲,如果在计算复杂度时谈到最坏的情况,你不能做得比 O((mn)^2) 更好,如果 n 不大于 C*m,实际上是 O(n^2) ,当 C 是某个正常数时。 原因是,即使您知道唯一可能的矩阵除了在一次单元格中之外都是零,在最坏的情况下,如果不遍历 m:m 矩阵的所有单元格,除了 n^2 个单元格,您也无法回答这个问题。

    我会建议以下算法,它甚至可以提供比要求更多的选项。

    1. 创建一个与原始矩阵 M 大小相同的矩阵 A,其中单元格 (i,j) 将在 (0,0) 之间的矩形中保存非零的数量。 您可以简单地通过从右到左的行来填充它,然后计算: A(i,j)=A(i-1,j)+ (A(i,j-1)-A(i-1,j-1)) + (M(i,j) != 0)。 或者代替 (A(i,j-1)-A(i-1,j-1)) 你现在可以有你在 i-the 行中遇到的数量的计数器。 在此伪代码中,A(0,j) 或 A(i,0) 表示 0,假设索引从 1 开始。

    2. 现在您可以在 O(1) 中查询 M 的每个三角形子矩阵 M((i,j)(l,k)) 中非零的数量,包括 n:n 矩阵 (如前所述,您有 (mn)^2 个: num_of_non-zeros in M((i,j)(l,k)) = A(l,k)-A(l,j)-A(i,k)+A(i,j)强>

    请注意,您可以将 1 和 2 组合到同一个双循环中,并在计算后立即检查 n:n 子矩阵 M((in,jn)(i,j)) 的非零值的数量A(i,j)。

    所以你得到了一个简单的二次算法,可以很容易地扩展到其他类似的应用程序。

    【讨论】:

    • 我最终实现了这样的东西。因为我没有费心回答我自己的问题,所以我会接受你的。所以简而言之,我只能使二次曲线变平,但它为我赢得了接下来 100 年的足够时间。 :D
    • 很好 :) 但我不确定我是否会称解是二次的,因为它与输入的大小成线性关系,即 am:m 矩阵。
    【解决方案2】:

    有 2 条数据你没有使用:

    1. 无论如何,您都在保持“最佳”结果。您可以突破您正在评估的特定“矩形”,如果它无法击败您当前的“最佳”(因此,如果您已经看到具有 3 个非零元素的 2x2 并且您只需点击第二个零评估某个矩形,您可以打破离开它

    2. 您还知道矩形的最大可能计数 = mxm(因此 4 由您负责)。当你找到一个有 4 个非零的矩形时,你就可以破坏整个东西——这是你可能得到的最好的东西。

    这些建议都不是算法改进。

    您可以尝试“扫描窗口”方法: 1.从0,0开始,像现在一样使用全扫描计算“左上”mxm窗口的分数。

    1. 向右扫描一列 - 获取您的分数,减去最左侧(最低索引)列的分数,然后将相邻列的分数相加到右侧。

    2. 继续执行第 2 步,直到到达行尾,然后向下扫描一行(减去第一行的分数,添加下一行的分数

    3. 向“左”前进(朝向较低的列索引)直到到达边缘,此时向下扫描一个索引并再次开始向右移动(第 2 步)。

    如果 m 很大,这将为您节省一些重新计算。只是为了演示该算法的迭代顺序,这是从 7x7“板”中扫描一个 3x3 矩形:

        going right -->        hitting the edge, moving     hit edge, go down     etc
                              down one row, heading left        head right
    xxx.... .xxx... ..xxx..     ....xxx ....... .......     ....... .......     .......
    xxx.... .xxx... ..xxx..     ....xxx ....xxx ...xxx.     xxx.... .......     .......
    xxx.... .xxx... ..xxx..     ....xxx ....xxx ...xxx.     xxx.... xxx....     ....xxx
    ....... ....... ....... ... ....... ....xxx ...xxx. ... xxx.... xxx.... ... ....xxx
    ....... ....... .......     ....... ....... .......     ....... xxx....     ....xxx
    ....... ....... .......     ....... ....... .......     ....... .......     .......
    ....... ....... .......     ....... ....... .......     ....... .......     .......
    

    这样,每次我移动到下一个矩形 - 边缘时,我只“计算” 6 个元素而不是 9 个元素。 m越大,收益越大。

    并行化

    您可以将每个“行”作为单独的任务进行扫描(跨多个内核甚至机器)。

          Task 1        |        Task 2       |        Task N
    xxx....     ....xxx | .......     ....... | .......     .......
    xxx....     ....xxx | xxx....     ....xxx | .......     .......
    xxx.... --> ....xxx | xxx.... --> ....xxx | ....... --> .......
    .......     ....... | xxx....     ....xxx | xxx....     ....xxx
    .......     ....... | .......     ....... | xxx....     ....xxx
    .......     ....... | .......     ....... | xxx....     ....xxx
    

    那么你只需要从每个任务返回的结果中挑选出最好的结果(每个任务返回它所在行的最好结果)

    理论界限: 因为 m 是“窗口”的大小,M 是棋盘的大小,所以有 (M-m)x(M-m) 个这样的窗口,最坏的情况是遍历所有窗口。 所以我不认为你可以在这里避免 O(n^2) 曲线。你可以玩弄系数

    【讨论】:

    • 我就是这么想的 :(
    • @AdamArold - 不要绝望 :-) 对于 7x7 窗口,我的扫描方法每个窗口执行 14 次计算而不是 49 次,这意味着它可能比原来的方法快 3 倍,快 6 倍对于 n=13
    • 到目前为止,我们似乎不打算搜索大于 365 的区间,但我可能需要将其扩展到十年 (10 * 365) 或更长。
    • ...多线程怎么样?你能用那个吗?并行切片?甚至可能在多台机器上?
    • 我正在考虑一个涉及位向量的解决方案,因为我并不真正关心价格,而是他们可以用位向量表示的存在。
    【解决方案3】:

    从问题中我假设如下:在 A 中找到最大 (nxn) 子矩阵 B_{i,j},其中包含最少数量的零

    如果这是正确的:

    1. 计算(或猜测)矩阵中出现的最大元素,取反,命名为:POISON
    2. 遍历 A 的所有元素并毒化不需要的数字 (
    3. 计算每一行的完整前缀 (http://en.wikipedia.org/wiki/Prefix_sum)

      y_0=x_0;
      for(int i=1;i<n;i++){
      y_i=y_{i-1}+x_i
      

      }

    4. 在所有行的前缀上应用以下内容:

      x_i = x_i - x_{i-m}

      删除前 'm-1' 个元素

    5. 行前缀形成一个矩阵,翻译它(你可以根据你的实现来解决这个问题,但如果你这样做,实现会更复杂)

    6. 在步骤 5 的矩阵上重复步骤 3

    7. 在第 6 步的输出上重复第 4 步
    8. 找到矩阵中的最大元素...如果它在 (i,j) 处,那么在原始矩阵中:从 (j,i) 开始会有最大和矩阵

    复杂度基于 A 的大小(n):O(n*n)

    【讨论】:

    • 这实际上比我已经拥有的 O((n-m)^2) 算法差。
    • 您的输入矩阵的大小为 nn,您不能低于 nn,因为您必须读取输入...
    【解决方案4】:

    我完全不确定你可以用这种方法走多远,但我们开始吧:
    我会尝试交换行和列,以便将零从左上角移开。
    因此生成的矩阵 m:m 将在左上角找到。

    我们需要评估交换行/列是否有趣。
    为此,我们基于这个权重矩阵构建了一个成本函数:

     7 6 5 4 3 2 1
     6 6 5 4 3 2 1
     5 5 5 4 3 2 1
     4 4 4 4 3 2 1
     3 3 3 3 3 2 1
     2 2 2 2 2 2 1
     1 1 1 1 1 1 1
    

    换句话说,第 i 行第 j 列的权重(从 0 开始)为 min(n-i,n-j)。

    找到的每个 0 都花费相应的权重,我们希望最小化总成本。
    如果交换可以降低总成本,那么它会很有趣。

    为了降低评估成本,我们可以使用一种稀疏矩阵的结构:

    • 每行映射零位置,即每个 rowIndex 的 (columnIndex) 集合
    • 每列映射零位置,即每个 columnIndex 的 (rowIndex) 集合

    我们现在遇到了对行和列进行排序的问题。
    次优方法包括分别解决子问题:

    • 交换行,
    • 交换列,
    • 迭代直到成本不变。

    如果满足以下条件,交换两行 i 和 k 有一个优势:

    weigth.atRow(i).sumOfIndices(zerosPerRow.at(i)) + weigth.atRow(k).sumOfIndices(zerosPerRow.at(k)) >
    weigth.atRow(i).sumOfIndices(zerosPerRow.at(k)) + weigth.atRow(k).sumOfIndices(zerosPerRow.at(i))
    

    请注意,这不是完整的顺序关系,因此并非所有排序算法都会成功。

    也许有兴趣通过额外的启发式来减少更多的组合:将具有最多零的行交换到底部,将具有最多零的列交换到右侧。

    显然,具有满秩的行/列在向上/向左移动后不需要排序。

    所以也许对相同等级的行/列的子集进行排序是一种合理的(次)最优算法。

    【讨论】:

    • 我对权重没有用处,值需要到位。我不明白你想用这种方法实现什么(除了次优算法)。
    • @AdamArold 的目的是通过使用适当的稀疏结构来降低评估成本,并通过使用可能是次优的启发式方法来降低组合成本。由于您创建了一个稀疏结构,因此您只需交换此人工制品,而不是原始矩阵。正确排序后,您将得到 m=1、m=2、... m=n-1 的答案。
    • @AdamArold 此处的权重只是为了正确排序行/列,不用于任何其他用途
    • @AdamArold 子矩阵必须是连续的吗?
    • “连续”是什么意思?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-21
    • 2020-10-21
    • 2016-01-16
    • 1970-01-01
    • 2015-10-20
    • 1970-01-01
    • 2013-09-22
    相关资源
    最近更新 更多