为啥我的 O(NLogN) 算法查找字谜比我的 O(N) 算法运行得更快?

Posted

技术标签:

【中文标题】为啥我的 O(NLogN) 算法查找字谜比我的 O(N) 算法运行得更快?【英文标题】:Why does my O(NLogN) algorithm to find anagrams run faster than my O(N) algorithm?为什么我的 O(NLogN) 算法查找字谜比我的 O(N) 算法运行得更快? 【发布时间】:2015-03-14 18:05:08 【问题描述】:

我有一组长度相同的单词。我想找到此哈希集中存在的所有字谜并将它们收集到另一个称为字谜的哈希集中。这是执行此操作的循环:

public HashSet<String> getUniqueAnagramsSlow(HashSet<String> paddedWords, int areAnagramsVersion)
    HashSet<String> anagrams = new HashSet<String>(); 
    Object[] paddedWordsArr = paddedWords.toArray();
    for(int i = 0; i < paddedWordsArr.length-1; i++)
        boolean foundAnagram = false;
        String wordOne = (String) paddedWordsArr[i];
        if(!anagrams.contains(wordOne)) 
            for(int j = i+1; j < paddedWordsArr.length; j++)
                String wordTwo = (String) paddedWordsArr[j];
                if(areAnagrams(wordOne, wordTwo, areAnagramsVersion))
                    foundAnagram = true;
                    anagrams.add(wordTwo);
                
            
        if(foundAnagram)
            anagrams.add(wordOne);
        
    
    return anagrams;

我编写此代码的目的是了解不同的 areAnagram() 函数如何影响运行时间。我写了两个版本的 areAnagrams()。一种对两个字符串进行排序并进行比较,另一种使用哈希图来比较字符频率。他们在这里:

public boolean areAnagramsVersionOne(String first, String second)
    char[] arr1 = first.toCharArray();
    Arrays.sort(arr1);
    String fSorted = new String( arr1 );
    char[] arr2 = second.toCharArray();
    Arrays.sort(arr2);
    String sSorted = new String(arr2);
    return fSorted.equals(sSorted);

public boolean areAnagramsVersionTwo(String first, String second)
    HashMap<String, Integer> wordOne = new HashMap<String,Integer>();
    for(int i = 0; i < first.length(); i++)
        String letOne = first.substring(i, i+1);
        if(wordOne.containsKey(letOne))
            int letOneFreq = wordOne.get(letOne);
            wordOne.put(letOne, letOneFreq + 1);
        else
            wordOne.put(letOne, 1);
        
    
    for(int i = 0; i < second.length(); i++)
        String letTwo = second.substring(i, i+1);
        if(!wordOne.containsKey(letTwo))
            return false;
        int freq = wordOne.get(letTwo);
        if(freq == 0)
            return false;
        wordOne.put(letTwo, freq-1);
    
    return true;

据我了解,areAnagramsVersionOne() 将在 NlogN 时间内运行,而 areAnagramsVersionTwo() 将在 N 时间内运行。但是,当我在原始循环中测试这两个版本的查找字谜时,版本二明显变慢了。这是为什么呢?

谢谢。

这是我如何测试运行时间的示例:

long startTime = System.currentTimeMillis();
getUniqueAnagramsSlow(words, 2);
long endTime = System.currentTimeMillis();
System.out.println("exec time: " + (endTime - startTime) );

【问题讨论】:

你如何测试它?在开始循环之前,您是否“热身”了 JVM?还是您使用基准框架? 性能如何随着 N 的增加而变化?第二个函数结束时不需要检查hashmap中的计数是否为0吗? @sje397 在我的 O(n) 算法中,我在减少哈希图中的值之前检查计数是否为 0。如果为 0,则返回 false,因为我知道第二个单词有一个单词中不存在的字符。 @ArjunPatel 但是如果第一个单词有一个不在第二个单词中的字符,您仍然可能返回 true,因为您没有检查 Map 中的所有值是否最后都为 0 . @Eran 所有单词的长度都是一样的。 【参考方案1】:

据我所知,仅当 N 值足够大时,O(NlogN) 才能保证大于 O(N),因为在较小的值下,O() 表示法中未表示的系数和常数仍然是相关的。考虑 2 种算法,它们的成本为:

算法1成本:100*N:O(N)

算法2成本:10*NlogN:O(NlogN)

O(NlogN) > O(N) => 10*NlogN > 100*N => 10*logN > 100 => logN > 10

所以在这种情况下,当 N > 2^10 时,算法 2 将比算法 1 花费更多。对于较小的值,算法 2 的成本会更低,即使根据 O() 表示法“效率较低”。

阅读the wikipedia page for O() notation了解更多详情。

【讨论】:

那是 Theta(n)。 O(n) 只是一侧的界限。 n^3 是 O(n^10),n^4 是 O(n^8)。 @DouglasZare 感谢您的意见。你能详细说明你的评论吗? @Arjun Patel:Big-O 和 little-o 表示法表示存在上限。 Theta 表示同时存在上限和下限。你不能说 O(n) 函数的渐近增长一定比 O(n^2) 函数慢。

以上是关于为啥我的 O(NLogN) 算法查找字谜比我的 O(N) 算法运行得更快?的主要内容,如果未能解决你的问题,请参考以下文章

在 O(1) 空间和 O(n) 时间中查找 2 个字符串是不是是字谜

各种查找排序算法比较

为啥快速排序算法的时间复杂度是O(nlogn)而不是O(n²)?

算法:分治法和时间复杂度 O(nlogn) 有啥关系?

算法 主定理

基础排序算法总览