在矩阵上找到波中心的最佳算法是什么?

时间:2014-07-17 01:41:38

标签: java algorithm

考虑到我有这样的矩阵(mXn):

0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0
0 | 1 | 1 | 2 | 1 | 1 | 0 | 0 | 0
0 | 1 | 4 | 9 | 4 | 1 | 0 | 0 | 0
1 | 2 | 9 | # | 9 | 2 | 1 | 0 | 0
0 | 1 | 4 | 9 | 4 | 1 | 0 | 0 | 0
0 | 1 | 1 | 2 | 1 | 1 | 0 | 0 | 0
0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0

随机设置点#。靠近它的值以波的形式受到影响。离该点越近,值越接近10。

哪种算法能够很好地找到#,假设它会在更大的范围内使用?

编辑:我更感兴趣的是如何找到第一个非零数字,而不是查找#本身,这是目的,但不是真正的问题。 想象一个充满零的巨大矩阵和#隐藏的某个地方。该算法的详尽部分是找到第一个非零值。

12 个答案:

答案 0 :(得分:6)

查找第一个非零值仅在信号对称且不包含旋转时才有效。考虑从Internet借来的以下示例(零=蓝色,最大=红色),注意第一个非零值位于右上角:

http://www.mathworks.com/matlabcentral/fileexchange/screenshots/1134/original.jpg

您可能需要查看gradient descent。通用算法是为连续函数定义的(你的是离散函数),但你仍然可以使用它。

它基本上在矩阵的某处初始化,在该点寻找渐变并向该方向移动,然后重复直到它收敛。你可以通过随机抽样来初始化它(选择一个随机单元格直到你得到一个非零值,你可以期望这比遍历更快并且找到一个非零值的平均值,自然取决于您的矩阵和信号大小)

一些属性:

  • 通常比穷举搜索更快(迭代整个矩阵)
  • 搜索空间越大(矩阵),与穷举搜索相比就越快。
  • 即使信号不对称且居中(首先非零与最大值对齐),您仍然可以处理更复杂的信号!
  • 同样的方法可以用于1维信号或缩放到n维(如果你考虑它会很酷,而且非常有用:])

限制:

  • 它可以永远振荡而不会收敛到某个值,特别是在离散函数上,您需要在代码中处理这种情况。
  • 您不能保证找到全局最大值(可以在本地获取,有方法可以克服此问题)
  • 你需要插入你的函数(不是全部,只是几个单元格,不是一件困难的事情,我不会使用线性插值)或对算法做一些调整(计算离散的梯度)功能与连续功能,并不困难)

对你来说这可能是一种矫枉过正,这可能是适当的,我不知道,你不提供更多的细节,但看看它可能是值得的。请注意,这里有一整套算法,有许多变化和优化。首先看一下维基百科的文章;)

答案 1 :(得分:3)

在最坏的情况下,你可能无法避免扫描整个矩阵,但是你可以通过逐步增加分辨率进行扫描来削减一些运行时间。

例如,你可以从一些(任意选择的)大距离采样开始,给你两种可能性:

  • 您要么找到了一个非零值的点 - >那么你可以根据需要使用其他一些技术在峰值上本地“回家”(比如其他一些答案中提到的“渐变上升”)

  • 您的搜索结果为空 - >这意味着扫描分辨率太大,波浪“穿过裂缝”,就像它一样。然后你的算法会降低分辨率(比如减半)并运行另一次扫描(如果巧妙地完成,你甚至可以跳过你在前一次运行中已经采样的那些点),只是更细粒度

所以你要继续以逐渐变小的分辨率扫描,直到你找到你想要的东西 - 前几次“粗略”扫描会更快,但成功的机会较小,但(取决于某些因素,就像完整矩阵的大小与“小波”的大小相比,平均而言,你必须有很好的机会找到目标,然后你必须将分辨率降低到足以扫描整个矩阵元素 - - 元素。

举例说明:

首次扫描:

#-------#-------
----------------
----------------
----------------
----------------
----------------
----------------
----------------
#-------#-------
----------------
----------------
----------------
----------------
----------------
----------------
----------------

第二次扫描:

o---#---o---#---
----------------
----------------
----------------
#---#---#---#---
----------------
----------------
----------------
o---#---o---#---
----------------
----------------
----------------
#---#---#---#---
----------------
----------------
----------------

第三次扫描:

o-#-o-#-o-#-o-#-
----------------
#-#-#-#-#-#-#-#-
----------------
o-#-o-#-o-#-o-#-
----------------
#-#-#-#-#-#-#-#-
----------------
o-#-o-#-o-#-o-#-
----------------
#-#-#-#-#-#-#-#-
----------------
o-#-o-#-o-#-o-#-
----------------
#-#-#-#-#-#-#-#-
----------------

依此类推('#'是新采样的单元格,'o'是先前采样的单元格,可以跳过)...

答案 2 :(得分:1)

首先将整个矩阵分成7x7个小矩阵,因为矩阵之间的重叠最小化。

将矩阵分割成小矩阵后,遍历或随机选取每个小矩阵的某些点,检查是否存在非零值。

如果您从小矩阵中找到任何非零值,请从该非零值中找到#

答案 3 :(得分:1)

从0,0遍历矩阵,直到您达到非零值。

一旦你达到一个非零值,看看顶部,右边,底部,左边的邻居找到最大的一个(如果有多个,只选择其中一个)。

然后在你选择的最大的那个上做同样的事情,看看它的4个邻居并找到最大的一个,直到你点击#

答案 4 :(得分:1)

如果周围的模式总是相同的,那么根据相邻元素的值和我们遇到的第一个非零数字,我们总能在O(1)时间内预测#点的正确位置

 First non zero element-->| 1 | 1 | 2 |
                        0 | 1 | 4 | 9 |
                        1 | 2 | 9 | # |

例如,如果第一个非零数字为1且右边元素为1,则down为1,右下角为4,因此#为(i + 2,j + 2) with(i,j)是当前元素的位置。

答案 5 :(得分:1)

假设模式始终相同,您需要检查每个方向的每五个单元格,从[2][2]开始,直到找到非零值的方式。因此,您需要检查[2][2], [2][7], [2][12], ..., [7][2], [7][7], [7][12], ..., [12][2], ...,依此类推。

一旦您发现这些单元格中的一个具有非零值,您只需检查其邻居及其邻居的邻居即可找到#字符。

这是O(n^2),这是您可以做的最好的,因为您无法检查O(n^2)个单元格。

答案 6 :(得分:1)

另一种可能的方法是迭代单元格,直到找到第一个非零数字。因为保证非零数字与波的中心在波的属性中在同一列中,所以我们可以简单地遍历列,直到我们找到aba模式的点(找到示例中的9#9),其中b值将是wave的中心。

假设:

  • 矩阵中存在全波,因此波的最远区域存在,允许找到第一个非零数字。
  • 中心值是唯一的,因为它不等于同一列中的其他值,否则算法会找到中心的一个周围值作为中心。

代码:

// iterate through cells
for (int y = 0; y < matrix.length; y++) {
    for (int x = 0; x < matrix[0].length; x++) {
        if (matrix[y][x] > 0) { // first non-zero value, in this case at point 3,3
            int cellCurr = 0;
            int cellOnePrev = 0;
            int cellTwoPrev = 0;
            for (; y < matrix.length; y++) { // iterate down rest of column
                cellCurr = matrix[y][x];
                if (cellCurr == cellTwoPrev) { // aba pattern found, center is b
                    System.out.println("found " + cellOnePrev + " at " + x + "," + (y - 1));
                    return;
                }
                // update necessary values
                cellTwoPrev = cellOnePrev;
                cellOnePrev = cellCurr;
            }
        }
    }
}

示例案例的输出,其中10代替#:

found 10 at 3,6

答案 7 :(得分:1)

根据编辑,主要目的是找到第一个非零项。这是有道理的。一旦找到了这个,你就可以通过渐变 - 上升方法来找到最大值:只需从当前条目走到每个步骤中具有最高值的邻居,直到到达顶部。

但是,缺少一些可能重要的细节。例如,了解wave的形状可能很重要。在最初的问题中,它似乎有一些高斯形式。它还可以更多&#34;扁平&#34;?也就是说,相同的最大值是否会导致矩阵的更大区域被非零条目填充?

这里的关键点是 - 对于第一个,微不足道的优化 - 知道包含非零条目的区域的直径。如果您知道具有非零条目的区域的直径是n,那么您可以使用步长n-1遍历矩阵,并确保您将错过了这个浪潮。

如果确实存在关于波浪可能位置的没有信息,那么我怀疑是否会有很大的改进空间。如果它可以任何地方,则您必须搜索无处不在

但是,对于平凡搜索(无论步长是1还是n-1),可能会有影响整体性能的决策。最突出的是:缓存效果。这是一个放置&#34; wave&#34;进入各种尺寸的matices。 (注意,&#34; wave&#34;实际上是矩形的,基于具有最大值的条目的moore邻域,为简单起见)。

它使用三种方法搜索第一个非零条目:

  • findNonZeroSimpleA:只需遍历矩阵,专栏
  • findNonZeroSimpleB:只需遍历矩阵,行主要
  • findNonZeroSkipping:只需遍历矩阵,专栏,步长为n-1

这不是&#34;基准&#34;

它仅提供关于性能差异的粗略指示。我的PC的一些结果(没有详细说明设置,因为它不是基准):对于8000x8000矩阵,最大值为10,位于(6000,6000),三种方法的运行时间为< / p>

  • findNonZeroSimpleA: 28.783 ms
  • findNonZeroSimpleB 831.323 ms
  • findNonZeroSkipping 2.203 ms

正如您所看到的,遍历顺序隐含了最大的差异(只需交换两行 - 确保在这里使用正确的行!)。 &#34;跳过&#34;方法将运行时间大致减少一个与波浪大小相对应的因子。 (结果也可能在这里失真,同样由于缓存效应,当波浪大小为&#34;大&#34; - 但幸运的是,这些正是跳过方法特别有益的情况)。

import java.awt.Point;

public class WaveMatrixTest
{
    public static void main(String[] args)
    {
        //basicTest();
        speedTest();
    }

    private static void basicTest()
    {
        int size = 30;
        int maxValue = 10;
        int array[][] = new int[size][size];
        placeValue(array, maxValue, 15, 15);
        System.out.println(toString2D(array));
    }

    private static void speedTest()
    {
        int maxValue = 10;
        int runs = 10;
        for (int size=2000; size<=8000; size*=2)
        {
            for (int run=0; run<runs; run++)
            {
                int x = size/2+size/4;
                int y = size/2+size/4;
                runTestSimpleA(size, maxValue, x, y);
                runTestSimpleB(size, maxValue, x, y);
                runTestSkipping(size, maxValue, x, y);
            }
        }

    }

    private static void runTestSimpleA(int size, int maxValue, int x, int y)
    {
        int array[][] = new int[size][size];
        placeValue(array, maxValue, x, y);

        long before = System.nanoTime();
        Point p = findNonZeroSimpleA(array, maxValue);
        long after = System.nanoTime();

        System.out.printf("SimpleA,  size %5d max at %5d,%5d took %.3f ms, result %s\n",
            size, x, y, (after-before)/1e6, p);
    }

    private static void runTestSimpleB(int size, int maxValue, int x, int y)
    {
        int array[][] = new int[size][size];
        placeValue(array, maxValue, x, y);

        long before = System.nanoTime();
        Point p = findNonZeroSimpleB(array, maxValue);
        long after = System.nanoTime();

        System.out.printf("SimpleB,  size %5d max at %5d,%5d took %.3f ms, result %s\n",
            size, x, y, (after-before)/1e6, p);
    }

    private static void runTestSkipping(int size, int maxValue, int x, int y)
    {
        int array[][] = new int[size][size];
        placeValue(array, maxValue, x, y);

        long before = System.nanoTime();
        Point p = findNonZeroSkipping(array, maxValue);
        long after = System.nanoTime();

        System.out.printf("Skipping, size %5d max at %5d,%5d took %.3f ms, result %s\n",
            size, x, y, (after-before)/1e6, p);
    }

    private static void placeValue(int array[][], int maxValue, int x, int y)
    {
        int sizeX = array.length;
        int sizeY = array[0].length;
        int n = maxValue;
        for (int dx=-n; dx<=n; dx++)
        {
            for (int dy=-n; dy<=n; dy++)
            {
                int cx = x+dx;
                int cy = y+dy;
                if (cx >= 0 && cx < sizeX &&
                    cy >= 0 && cy < sizeY)
                {
                    int v = maxValue - Math.max(Math.abs(dx), Math.abs(dy));
                    array[cx][cy] = v;
                }
            }
        }
    }

    private static Point findNonZeroSimpleA(int array[][], int maxValue)
    {
        int sizeX = array.length;
        int sizeY = array[0].length;
        for (int x=0; x<sizeX; x++)
        {
            for (int y=0; y<sizeY; y++)
            {
                if (array[x][y] != 0)
                {
                    return new Point(x,y);
                }
            }
        }
        return null;
    }

    private static Point findNonZeroSimpleB(int array[][], int maxValue)
    {
        int sizeX = array.length;
        int sizeY = array[0].length;
        for (int y=0; y<sizeY; y++)
        {
            for (int x=0; x<sizeX; x++)
            {
                if (array[x][y] != 0)
                {
                    return new Point(x,y);
                }
            }
        }
        return null;
    }

    private static Point findNonZeroSkipping(int array[][], int maxValue)
    {
        int size = maxValue * 2 - 1;
        int sizeX = array.length;
        int sizeY = array[0].length;
        for (int x=0; x<sizeX; x+=size)
        {
            for (int y=0; y<sizeY; y+=size)
            {
                if (array[x][y] != 0)
                {
                    return new Point(x,y);
                }
            }
        }
        return null;
    }


    private static String toString2D(int array[][])
    {
        StringBuilder sb = new StringBuilder();
        int sizeX = array.length;
        int sizeY = array[0].length;
        for (int x=0; x<sizeX; x++)
        {
            for (int y=0; y<sizeY; y++)
            {
                sb.append(String.format("%3d", array[x][y]));
            }
            sb.append("\n");
        }
        return sb.toString();
    }

}

答案 8 :(得分:1)

在你的情况下,最重要的是达到第一个非零值,有几种方法可以做到这一点,不同的情况需要不同的步骤,所以我将发布两种类型的通用方式。 / p>

案例I.波浪影响区域与所有区域相比较大

我的建议是:对角旅行。

您应该从角落开始并沿对角线移动,直到达到第一个非零值。之后旅行系统发生变化。

CASE II波浪影响区域与所有区域相比较小

我的建议:下棋。

你应该从角落开始,像棋盘一样移动(参见示例) enter image description here

执行此操作直到达到第一个非零值。这里的旅行系统发生了变化。


达到非零值后执行此操作:检查全部8(实际上5个就足够了,您不需要从角落检查,但这个小事情可能难以实施)周围区域并转移到最大值。重复此步骤,直至找到#

答案 9 :(得分:0)

因为没有写入任务,我会先使用多线程查找非零数字,然后用单个线程查找#。如果矩阵足够大以抵消线程处理的较低性能,那么这将比单线程更好。

  1. 在多个区域中拆分矩阵
  2. 按顺序或随机查找非零数字
  3. 找到任何非零数字后,停止所有线程
  4. 只使用一个具有正确算法的线程查找#

答案 10 :(得分:0)

假设您知道该区域的极限,找到第一个非零点应该有点像玩战舰,除非您知道目标同时具有宽度和高度,这是一个优势。考虑到你不知道波的非零部分的大小,首先假设它很大并且朝着假设更小的尺寸前进。

如果长度为1x1,则先测试0.33x0.33,0.33x0.66,0.66x0.33和0.66x0.66。如果未找到,则将步长减半至0.33 / 2并再次测试,直到一组点击中船。当你找到第一个点时,棘手的部分开始,因为没有相邻的点必然具有更高的值,所以在这种情况下你将不得不重复第一步,寻找高于你当前水平的东西。上一组命中确定要查看的新区域。如果相邻点的水平较高,那么显然会移动到那里。

如果区域足够大,这应该比对列和行求和更快,对吗?

答案 11 :(得分:-1)

你可以这样:

首先找到最大的没有。在矩阵中,然后去找那个最大的位置。在矩阵中,最后检查邻居将解决查找#。

的问题
a = max(matrix)
[r,c] = find(matrix == a)

然后用r +/- 1和c +/- 1检查邻居,你会找到#。