【问题标题】:Could a nested loop be replaced with a counter variable to reduce the time complexity?可以用计数器变量替换嵌套循环以降低时间复杂度吗?
【发布时间】:2022-01-09 15:55:07
【问题描述】:

这可能是一个初学者的问题,但我有一个方法是使用嵌套循环在二维数组中查找值,假设主体看起来像这样:(假设有总是匹配二维数组中的 1)


        // example array ==> int arr = new int[5][5]
        int length = arr.length;
        int index1 = -1;
        int index2 = -1;

        loop:
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length; j++) {
                if (arr[i][j] == 1) {
                    index1 = i;
                    index2 = j;
                    break loop;
                }
            }
        }

我以这种方式用一个额外的变量 (counter) 替换了第二个循环:

        // example array ==> int arr = new int[5][5]
        int length = arr.length;
        int index1 = -1;
        int index2 = -1;
        int counter = 0;

        for (int i = 0; i < length; i++) {
            if (arr[counter][i] == 1) {
                index1 = counter;
                index2 = i;
                break;
            }

            if (i == length - 1) {
                counter++;
                i = 0;
            }
        }

这是否仍被视为具有相同时间复杂度的嵌套循环? 如果有一个很好的链接可以理解如何分析我的代码复杂度,请附上!

这是一个示例代码,我知道可以做一些增强,但我只是询问时间复杂度(忽略边缘情况和错误处理)强>

【问题讨论】:

  • 第二个示例中的counter 将永远保留0
  • 如果您需要遍历两个数组以在特定的“行”/“列”中找到一个值,那么它的实现方式并不重要——您无法避免任何一次迭代。您可以控制它何时停止迭代,但这不会改变它的 O.
  • 我的错,请再次检查body,因为我忘记重新分配i并正确增加计数器。
  • 这不是另一个迭代吗?最终结果是一样的(而且更难考虑)。
  • 现在看来,如果数组没有1,程序永远不会结束。无论如何,它的复杂度仍然是 O(NxM)。

标签: java loops nested time-complexity big-o


【解决方案1】:

你的两个解决方案的时间复杂度是一样的。

有些人犯了只计算循环的错误。因此,如果只有一个可识别的循环,他们会说这是一种线性算法。但事实并非如此,它仍然是二次方。您需要检查的是重复的操作重复了多少次。

在这种情况下,让我们专注于比较(将数组值与 1 进行比较)。这是重复次数最多的操作(您可以自己验证),因此它重复的次数与 N(数组的一维大小,意味着数组本身为 NxN)成正比,决定了大 O。

因此,在您的第一个双循环示例中,每次执行内部循环时,都会进行 N 次比较。由于外部循环,内部循环本身重复了 N 次,因此您进行了 NxN 比较,最终得到 O(N²)。

在您的第二个单循环示例中,比较执行的迭代次数与循环次数一样多。假设最坏的情况是 1 在最后一个单元格中,这里会发生什么?每次到达行尾时,都会重置 i。所以你又开始从 0 数到 N-1。因此,您对 counter = 0 进行了 N 次比较,然后对 counter = 1 进行了 N 次比较,依此类推,直到 counter 达到 N-1。因此,您再次得到 NxN 比较,因此 O(N²)。

所以检查循环只是一个经验法则。您真正应该计算的是操作

【讨论】:

    【解决方案2】:

    您可以使用 Test App 来检查每种方法的性能,如下面的代码 sn-p

    package test;
    
    public class TestLoops {
    
        static int numOfItrr = 30000;
        static int[][] arr = new int[numOfItrr][numOfItrr];
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            initArr();
    
            System.out.println("=====================Start======================");
            System.out.println("numOfItrr :" + numOfItrr);
            initArr();
            testWithForLoops();
            testWithCounter();
            System.out.println("=====================Finish=====================");
        }
    
        public static void initArr() {
            for (int i = 0; i < numOfItrr; i++) {
                for (int j = 0; j < numOfItrr; j++) {
                    arr[i][j] = (int) (Math.random() * 1000);
                }
            }
        }
    
        public static void testWithForLoops() {
            long start = System.nanoTime();
            int max = arr[0][0];
    
            for (int i = 0; i < numOfItrr; i++) {
                for (int j = 0; j < numOfItrr; j++) {
                    if (arr[i][j] > max) {
                        max = arr[i][j];
                    }
                }
            }
    
            long end = System.nanoTime();
            System.out.println("time With loops    = " + (end - start));
    
        }
    
        public static void testWithCounter() {
            long start = System.nanoTime();
    
            int counter = 0;
            int max = arr[0][0];
    
            for (int i = 0; i < numOfItrr; i++) {
                if (arr[counter][i] > max) {
                    max = arr[counter][i];
                }
                if (i == numOfItrr - 1 && counter < numOfItrr - 1) {
                    counter++;
                    i = 0;
                }
            }
    
            long end = System.nanoTime();
            System.out.println("time With Counter  = " + (end - start));
        }
    
    }
    

    这里是示例结果

    ====================开始=====================

    numOfItrr :10000

    循环时间 = 86591300

    使用计数器的时间 = 110362600

    ====================完成=====================

    ====================开始=====================

    numOfItrr :30000

    循环时间 = 754436400

    计数器时间 = 978285600

    ====================完成=====================

    ====================开始=====================

    numOfItrr :30000

    循环时间 = 754230300

    计数器时间 = 970180300

    ====================完成=====================

    【讨论】:

    • 我喜欢这个答案,它确实显示了差异,但是对于这种时间差异有解释吗?!他们在技术上做同样数量的循环
    • 据我所知,没有循环比 while 和基本的 for 循环更快。由于 JVM 已针对运行这些循环进行了优化。 mkyong.com/java/… 并且从我的角度来看,评估此条件“if (i == numOfItrr - 1 && counter
    【解决方案3】:

    您应该将此条件添加到 for 循环中,否则您将遇到 ArrayOutOfBoundException:

     for (int i = 0; i < length && counter < length-1; i++) //if the array is something like new int[5][5]
    

    尝试使用 currentTimeMillis() 测量两者的执行时间,使用不同的数组和迭代:)

    【讨论】:

    • 我提到请避免极端情况只是为了缩短问题并专注于要点,因为我只是想指出不使用第二个 for 循环的想法。并且该数组中总有一个匹配 1。
    【解决方案4】:

    它将 O() 的内容视为执行所需的最大操作数,并以 'n' 参数来标识输入的大小。

    for 循环通常会迭代一定的大小,在这种情况下就是数组的大小。

    因此,for 循环最多执行“n”次操作,这就是它被称为线性时间或 O(n) 的原因。

    在第一种情况下,您显示了两个嵌套的 for 循环,在这种情况下,必须进行简单的乘法运算,因为复杂度 O(n) 的循环最多完成“n”次,因此称为二次时间或 O(n^2)。

    O(n) 的值越大,复杂度越大。

    为简单起见:

    • For循环是O(n),没关系

    • 2+ 对于非嵌套循环是 O(n+n),但它仍然是 O(n) 并且没关系

    • 2 对于嵌套循环是 O(n*n) 并且不好

    • 3 对于嵌套循环是 O(nnn) 并且非常糟糕

    这就是为什么您发送的第二个代码(假设没有错误)肯定比第一个更快。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多