29

所以,这不是我的家庭作业问题,而是取自 coursera 算法和数据结构课程的未评分作业(现已完成)。

你得到一个由不同数字组成的 n × n 网格。如果一个数字小于其所有邻居,则该数字是局部最小值。(数字的邻居是紧邻上方、下方、左侧或右侧的一个。大多数数字有四个邻居;侧面的数字有三个;四个角有两个。)使用分而治之的算法设计范式来计算局部最小值,仅在数字对之间进行 O( n ) 比较。(注意:由于输入中有n 2 个数字,因此您不能全部查看。提示:考虑哪种类型的重复可以为您提供所需的上限。)

由于这些数字没有任何顺序,除了 O( n 2 ) 比较之外,我看不出我们如何能够摆脱任何事情。

4

5 回答 5

35

我们可以通过查看它如何出错来调整像 Jared 的回答这样的词。

该答案中的想法 - 这是一个很好的答案 - 是“下坡”。这只是意味着,如果您在一个元素上,请检查它是否是局部最小值。如果是这样,你就完成了;否则,步到其最近邻居中的最小者。最终这必须终止,因为每一步都指向一个较小的元素,并且不能在有限数组中永远持续下去。

这种方法的问题在于“滚动”可能会到处乱窜:

20 100 12  11 10 100  2
19 100 13 100  9 100  3
18 100 14 100  8 100  4
17  16 15 100  7   6  5

如果您从左上角开始“下坡”,您将访问数组中大约一半的元素。那太多了,所以我们必须稍微限制一下。

首先检查中间列和中间行。在所有这些中找到最小的元素并从那里开始。

从那里滚动一步“下坡”进入四个象限之一。您将进入其中一个象限,因为中间列和/或行中的相邻元素较大,因此两个相邻象限中只有一个可能是“下坡”。

现在考虑如果你从那里“滚下山”会发生什么。显然,您最终会达到局部最小值。(我们实际上不会这样做,因为它会花费太长时间。)但是,在滚动的过程中,你永远不会离开那个象限......因为要这样做,你必须穿过中间的列或中间的行,并且这些元素都不小于您开始的位置。因此,该象限在某处包含局部最小值。

因此,在线性时间内,我们已经确定了一个必须包含局部最小值的象限,并且我们将 n 减半。现在只是递归。

该算法需要时间 2n + 2n/2 + 2n/4 + ...,等于 4n,即 O(n),所以我们完成了。

有趣的是,我们根本没有使用“rolling downhill”,除了关键部分:证明算法有效。

[更新]

正如Incassator 指出的那样,这个答案并不完全正确,因为在你“只是递归”之后,你可能会再次退出象限......

最简单的解决方法是在“下坡”之前找到中间行、中间列和边界中最小的元素。

于 2013-08-30T05:24:49.617 回答
17

Nemo 接受的答案很好,但并不完全正确:

因此,在线性时间内,我们已经确定了一个必须包含局部最小值的象限,并且我们将 n 减半。现在只是递归。

我指的是“只是递归”位。问题是我们不能直接这样做,因为在下一次迭代中我们可能会找到一个局部最小值,它不是原始网格的局部最小值(下面的 x 表示一些任意大的数字):

 x  x 39  x  x 50  x  x  x  x  x
 x  x 38  x  x 49  x  x  x  x  x
37 36 33 34 35 48  x  x  x  x  x
 x  x 32  x  1 10  x  x  x  x  x
 x  x 31  x  x 47  x  x  x  x  x
46 45 30 44 43 60 51 52 53 54 55
 x  x  2  x  x 56  x  x  x  x  x
 x  x  x  x  x 57  x  x  x  x  x
 x  x  x  x  x 58  x  x  x  x  x
 x  x  x  x  x 59  x  x  x  x  x

在第一次迭代中,我们发现 10 是中间行和中间列的最小值。我们向左走(因为 1 小于 10)。所以我们的下一次迭代是在左上象限。但是现在中间行和列的最小值将是 31(如果象限的边界被认为是其中的一部分,则为 30)。然后,您将得出结论,这是一个局部最小值。但它不适用于全网格。

我们可以通过多种方式纠正这个不幸的缺陷。我是这样解决的:

在每次迭代中,除了网格本身之外,我们还会跟踪当前的最小候选(即第一次迭代后上例中的 1;在初始状态下,我们可以说最小候选是正无穷大)。我们计算中间行和列的最小值并将其与最小候选者进行比较。如果后者更小,我们将递归到包含最小候选者的象限。否则我们会忘记之前的候选者,然后才检查新的中间行/列最小值是否实际上是局部最小值。如果不是,那么像往常一样递归到我们从它向下倾斜的任何象限(并跟踪新的最小候选)。

或者,您可以修改这个大概是 MIT 讲座中描述的过程:在每次迭代时,您可以查看中间行/列网格边界,而不是查看中间行/列。然后算法再次正确。

你选择你喜欢的方式。

于 2014-06-27T21:35:25.153 回答
7

我认为这实际上很容易。

将问题转化为 3-D 问题,以了解该算法为何有效。将矩阵放在桌子上。假设每个单元都有柱子伸出,柱子的高度与其值成正比。将球放在任何柱子上。让球始终落在最低高度的相邻柱子上,直到达到局部最小值。

于 2013-08-30T05:03:24.957 回答
1

好吧,这就是你如何分而治之的。

1)将nxn矩阵划分为4个n/2 xn/2子矩阵。

2)继续递归划分子矩阵,直到你最终得到一个 2 x 2 矩阵

3) 检查 2 x 2 矩阵的任何元素是否是局部最小值。

递归方程为:T(n) = 4*T(n/2) + O(1)

4*T(n/2) 用于 4 n/2 xn/2 子矩阵和 O(1) 用于检查 2 x 2 子矩阵是否具有局部最小值

主定理说这是一个O(n^2)最坏情况界限。

但我认为我们可以得到一个最佳情况O(n)界限,

(“红色警报!---最好的情况是假的,只是假的---红色警报!”)。

如果我们在步骤 3 中找到局部最小值后退出递归堆栈。

伪代码:

private void FindlocalMin(matrix,rowIndex,colIndex,width){
    if(width == 1){ checkForLocalMinimum(matrix,rowIndex,colIndex); return;} //2x2 matrix
    FindlocalMin(matrix,rowIndex,colIndex,width/2);  
    FindlocalMin(matrix, (rowIndex + (width/2) + 1) ,colIndex,width/2);
    FindlocalMin(matrix,rowIndex, (colIndex + (width/2) + 1) ,width/2);
    FindlocalMin(matrix,(rowIndex + (width/2) + 1), (colIndex + (width/2) + 1) ,width/2);
}

private void checkForLocalMinimum(.........){
    if(found Local Minimum in 2x2 matrix){ exit recursion stack;} 
}

这是一个java实现

于 2016-01-19T16:03:33.837 回答
1

代码适用于 3*3 或更大的矩阵,根据算法第 4 版书籍练习的练习开发。

它的工作原理如下

  1. 寻找给定矩阵的中间行并从中找到最小值
  2. 对于该最小值,请查找上方和下方元素以确保它是局部最小值
  3. 如果不限制搜索矩阵
    1. Row :选择比中间行元素小的元素所在的行的一半。
    2. colmun :选择在该行中找到最小值的索引。
  4. 重复步骤 1-3

公共类MinimumOfMatrix {

private static int findLocalMinimum(int[][] matrix, int rowStart, int rowEnd, int colStart, int colEnd) {

    int midRow = (rowStart + rowEnd) / 2;
    int minPos = findMin(matrix, midRow, colStart, colEnd);

    if (minPos >= (colStart + colEnd) / 2)
        colStart = (colStart + colEnd) / 2;
    else
        colEnd = (colStart + colEnd) / 2;

    if (matrix[midRow][minPos] < matrix[midRow + 1][minPos]
            && matrix[midRow][minPos] < matrix[midRow - 1][minPos]) {
        return matrix[midRow][minPos];
    } else if (matrix[midRow][minPos] > matrix[midRow + 1][minPos]) {
        return findLocalMinimum(matrix, midRow, rowEnd, colStart, colEnd);
    } else {
        return findLocalMinimum(matrix, rowStart, midRow, colStart, colEnd);
    }

}

private static int findMin(int[][] matrix, int midRow, int colStart, int colEnd) {
    int min = Integer.MAX_VALUE;
    int pos = -1;

    for (int i = colStart; i < colEnd; i++) {
        if (matrix[midRow][i] < min) {
            min = matrix[midRow][i];
            pos = i;
        }

    }

    return pos;
}

public static void main(String[] args) {
    // Best Case
    /*
     * int[][] matrix= { {1,-2,4,-6,1,8}, {-3,-6,-8,8,1,3}, {1,2,6,-2,-8,-6},
     * {-2,9,6,3,0,9}, {9,-1,-7,1,2,-6}, {-9,0,8,7,-6,9} };
     */

    // Two Iteration Down Case
    /*
     * int[][] matrix= { { 1,-2, 4,-6, 1, 8}, {-3,-6,-8, 8, 1, 3}, { 1, 2, 6, 9, 0,
     * 6}, {-2, 9, 6,-1,-1, 9}, { 9,-1,-7, 1, 2,-6}, {-9, 0, 8, 7,-6, 9} };
     */

    /*
     * //Left Down Case int[][] matrix= { { 1,-2, 4,-6, 0, 8}, {-3,-6,-8, 8,-2, 3},
     * {-2, 9, 6,-1, 1, 9}, { 1, 0, 6, 9, 2, 6}, { 9,-1,-7, 1, 2,-6}, {-9, 0, 8,
     * 7,-6, 9} };
     */

    int[][] matrix = { { 1, -2, 4, }, { -3, -6, -8, }, { -2, 9, 6, }

    };

    System.out.println(findLocalMinimum(matrix, 0, matrix.length, 0, matrix.length));

}

}

于 2018-04-03T15:13:21.873 回答