【问题标题】:What is the complexity of these two string based algorithms?这两种基于字符串的算法的复杂性是多少?
【发布时间】:2015-04-30 19:41:19
【问题描述】:

我编写了这两种算法来检查字符串中是否存在重复字符(ABBC、AAAC)。第一个使用 hashset 数据结构,而第二个完全依赖于迭代。

算法 1

String s = "abcdefghijklmnopqrstuvwxxyz";

public boolean isUnique(String s) {

        Set<Character> charSet = new HashSet<Character>();

        for(int i=0; i<s.length(); i++) {
            if(charSet.contains(s.charAt(i))) {
                return false;   
            } 
            charSet.add(s.charAt(i));
        }   
        return true;
}

算法 2

String s = "abcdefghijklmnopqrstuvwxxyz";

public boolean isUnique2(String s) {

        for(int i=0; i<s.length()-1; i++) {
            for(int j = i+1; j<s.length(); j++) {
                if(s.charAt(i) == s.charAt(j)) {
                    return false;
                }
            }
        }
        return true;
}

我的想法是第一个算法是O(N),第二个算法是O(N^2)。当我在我的(可能不可靠的)笔记本电脑上运行执行时间测试时,第一个算法的平均速度是 2020ns,而第二个算法是 995ns。这违背了我对算法复杂度的计算,有人可以告诉我吗?

【问题讨论】:

  • @zubergu:不! HashSets 的工作方式不同。
  • @fabian 我刚刚意识到 HashSet 部分 :)
  • 了解运行时的性能不能仅从大 O 表示法中收集。
  • 您如何衡量执行时间?您是在测试整个可执行文件运行需要多长时间,还是仅测试方法体?如果是前者,你可能不会注意到这么小的数据集有很大的不同。无论哪种方式,时间复杂度和运行时间都是两个不同的东西。时间复杂度不假设您实际运行代码的对象是什么,在本例中是 jvm,这会引入一些初始开销。
  • 用超过 100 万个字符的字符串试一试,看看会得到什么。

标签: java performance algorithm big-o time-complexity


【解决方案1】:

使用 O() 表示法时,您会忽略常量,这意味着 O(n) == (10^10*n)。因此,虽然 O(n^2)>O(n) 渐近为真,但对于较小的 n 值,它不一定为真。 在您的情况下,想象一下调整哈希集后面的数组的大小可能比迭代输入更耗时。

【讨论】:

    【解决方案2】:

    假设charAt方法在O(1)时间内运行,第一个算法是O(N),第二个是O(N^2)。对于所有输入,线性时间算法不应该比二次算法更快。在某个 N(可能是数百万)之后,它会比二次方更快。

    例如:

    void funcA(int n){
        for (int i = 0; i < n; i++){
            for (int j = 0; j < 10000; j++){
                int k = i + j;
            }
        }
    }
    
    
    void funcB(int n){
        for (int i = 0; i < n; i++){
            for (int j = 0; j < n; j++){
                int k = i + j;
            }
        }
    }
    

    即使 funcA 是线性的,而 funcB 是二次的,很容易看出 funcB 在 n

    【讨论】:

      【解决方案3】:

      您正在进行的微基准测试可能会提供关于算法复杂性的非常误导性的信息。

      很容易“移植”您的算法以检查整数数组中的重复项。

      然后我建议在 10^7 个元素的数组上测试性能,您肯定会看到差异。

      这样您就可以确认您最初对哈希集的正确估计 O(N) 与第二个“循环”版本的 O(N^2)。

      【讨论】:

      • 我现在用一个大整数数组试试这个,谢谢
      • 尝试先用 [pseudo-] 随机数初始化足够大的整数数组,然后将相同的输入一个接一个地传递给一个被测方法,测量执行时间。
      【解决方案4】:

      您的测试数据有问题,例如,如果您将自己限制为英文字符(a-z),则如果字符串长度> 26,则保证有重复。在具体示例中,您提供了字符串@987654321 @ 已排序,重复元素 x 在最后找到。因此,迭代数组查找速度更快,因为在您继续解析字符串时构建 HashSet 会产生开销。

      更好的测试是使用随机生成的大尺寸整数序列和较大的最大值来测试它,例如Long.MAX_VALUE

      下面是一个测试,它反驳了您关于数组搜索更快的断言。运行几次,自己看看。或者您可以从 1000 次运行中取平均值,等等:

      public class FindDuplicatesTest {
      
        public static final String s = generateRandomString(100000);
      
        private static String generateRandomString(int numChars) {
          Random random = new Random();
          StringBuilder sb = new StringBuilder();
          for (int i = 0; i < numChars; i++) {
            int codePoint = random.nextInt(65536);
            sb.append(Character.toChars(codePoint));
          }
          return sb.toString();
        }
      
        public boolean isUnique(String s) {
      
          Set<Character> charSet = new HashSet<Character>();
      
          for (int i = 0; i < s.length(); i++) {
            if (charSet.contains(s.charAt(i))) {
              return false;
            }
            charSet.add(s.charAt(i));
          }
          return true;
        }
      
        public boolean isUnique2(String s) {
      
          for (int i = 0; i < s.length() - 1; i++) {
            for (int j = i + 1; j < s.length(); j++) {
              if (s.charAt(i) == s.charAt(j)) {
                return false;
              }
            }
          }
          return true;
        }
      
        public static void main(String[] args) {
          FindDuplicatesTest app = new FindDuplicatesTest();
      
          long start = System.nanoTime();
          boolean result = app.isUnique(s);
          long stop = System.nanoTime();
          System.out.println(result);
      
          System.out.println("HashSet Search Time: " + (stop - start));
      
          start = System.nanoTime();
          result = app.isUnique2(s);
          stop = System.nanoTime();
          System.out.println(result);
      
          System.out.println("Array Search Time: " + (stop - start));
      
        }
      }
      

      【讨论】:

      • 字符排序的事实是否相关?另外,使用整数值肯定会遇到同样的问题,因为我们会在前 9 个数字之后出现重复?
      • 在您的示例中,初始化 HashSet 并将元素插入其中进行比较的开销超过了简单的数组比较。我的意思不是 0-9,我的意思是 100,000 个数字的任意序列,并检查是否存在重复。
      • 排序后的数据搜索速度更快。
      • @MadConan 我同意,但在我的算法中,数据是否经过排序没有区别
      • @DomShahbazi 我添加了测试代码以显示 HashSet 的平均性能如何更好。但是像你这样带有字符 a-z 的小型数据集不足以证明性能优势
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      相关资源
      最近更新 更多