【问题标题】:Java - Comparing efficiency of two O(n) algorithmsJava - 比较两种 O(n) 算法的效率
【发布时间】:2015-01-27 10:16:33
【问题描述】:

我正在研究链表,问题是 - 编写一个函数来打印给定链表的中间项(假设 LL 具有奇数个节点)。

方法 1 - 遍历 LL 并使用计数器计算节点数。添加 1(使其成为偶数)并将计数器除以 2(忽略数学差异)。再次遍历 LL,但这次只到达反项并返回。

void GetMiddleTermMethod1(){
            //Count the number of nodes
            int counter = 0;
            Node n = FirstNode;
            while (n.next != null){
                counter = counter + 1;
                n = n.next;             
            }
            counter=counter+1/2;
            //now counter is equal to the half of the number of nodes

            //now a loop to return the nth term of a LL 
            Node temp = FirstNode;
            for(int i=2; i<=counter; i++){
                temp = temp.next;
            }
            System.out.println(temp.data);
        }

方法 2 - 初始化 2 个对节点的引用。一个一次遍历2个节点,另一个只遍历1个。当快速引用到达null(LL结束)时,慢速引用会到达中间并返回。

void GetMiddleTermMethod2(){
            Node n = FirstNode;
            Node mid = FirstNode;
            while(n.next != null){
                n = n.next.next;
                mid = mid.next;
            }
            System.out.println(mid.next.data);
        }

我有 3 个问题 -

Q1 - 如果我在求职面试中被问到这个问题,我如何知道哪种算法更有效?我的意思是这两个函数都遍历 LL 一次半(第二个函数在一个循环中而不是 2 次,但它仍然遍历 LL 一次半)...

Q2 - 由于两种算法都有 O(n) 的大 O,哪些参数将决定哪个更有效?

Q3 - 计算此类算法效率的一般方法是什么?如果您能将我链接到合适的教程,我将不胜感激...

谢谢

【问题讨论】:

  • 我推荐 Thomas H. Cormen 的《算法导论》一书
  • 我在代码中看到了一些问题——比如在你的第二种方法中,如果列表中有奇数个项目(例如 3 个)或 1 个项目,你就会遇到问题。为什么不对各种类型的输入运行它,看看结果如何?当 2 种算法具有相同的渐近复杂度时,您通常需要查看其他特征。

标签: java performance algorithm linked-list big-o


【解决方案1】:

好吧,对此并没有真正简单的答案,答案可能因编译器优化、JIT 优化和运行程序的实际机器而异(出于某种原因,可能针对一种算法进行了更好的优化)。

事实是,除了给我们渐近行为的理论上的大 O 表示法之外,很少有一种“干净的、理论上的”方法来确定算法 A 在条件 (1)、(2) 中比算法 B 快。 ..,(k)。

但是,这并不意味着您无能为力,您可以通过创建各种随机数据集来benchmark代码,并确定每个算法所需的持续时间。不止一次这样做是非常重要的。还有多少?直到你得到一个statistical significance,当使用一些已知和接受的statistical test,比如Wilcoxon signed ranked test

此外,在许多情况下,微不足道的性能通常不值得花时间优化代码,如果它降低了代码的可读性,那就更糟了——因此更难维护。

【讨论】:

    【解决方案2】:

    我刚刚在 java 中实现了您的解决方案,并在一个包含 1.111.111 个最多 1000 的随机整数的 LinkedList 中对其进行了测试。结果非常相似:

    方法一:

    time: 162ms
    

    方法二:

    time: 171ms
    

    此外,我想指出您的方法有两个主要缺陷:

    方法一:

    counter = counter + 1 / 2; 更改为counter = (counter + 1) / 2; 否则你会得到列表的末尾,因为计数器仍然是计数器:)

    方法二:

    System.out.println(mid.next.data); 更改为System.out.println(mid.data);

    【讨论】:

    • 太棒了!感谢时间分析。我承认第一个缺陷,但不承认第二个。 while 循环将执行比节点数少一倍。因此,我打印了下一个节点,而不是 mid 指向的那个。不过谢谢,没什么大不了的。
    • 我不太确定。想象一个包含 7 个项目的列表,索引从 0 到 6。您的 n 和 mid 在开始时是索引 0,而 3 是我们想要的 midindex。第一轮你的 n 进入索引 2,你的 mid 进入索引 1,然后是 n[4]mid[2],最后是 n[6]mid[3]。这是我们想要获得的索引,而不是下一个,n[4]
    猜你喜欢
    • 2015-02-24
    • 2023-03-25
    • 2019-01-27
    • 1970-01-01
    • 2014-04-20
    • 2014-03-16
    • 1970-01-01
    • 2021-09-25
    • 2019-09-10
    相关资源
    最近更新 更多