【问题标题】:Arrays.sort() vs sorting using mapArrays.sort() 与使用 map 进行排序
【发布时间】:2020-12-10 13:56:19
【问题描述】:

我有一个要求,我必须遍历一个包含字符串列表的数组:

String[] arr = {"abc","cda","cka","snd"}

并匹配字符串"bca",忽略字符的顺序,这将返回true,因为它存在于数组中("abc")。

为了解决这个问题,我有两种方法:

  1. 使用 Arrays.sort() 对两个字符串进行排序,然后使用 Arrays.equals 进行比较。
  2. 创建2个hashmaps并添加字符串中每个字母的频率,然后最后使用equals方法比较两个char映射。

我读到使用Arrays.sort() 方法的复杂性更高。因此,考虑使用第二种方法,但是当我同时运行两种代码时,第一种方法执行程序所需的时间非常少。

有什么建议为什么会发生这种情况?

【问题讨论】:

  • “非常少的时间”多少(绝对值)以及数组有多大?
  • 如果您使用您在此处发布的示例(4 个长度为 3 的字符串),时间复杂度是无关紧要的。当输入更大时,时间复杂度实际上开始成为一个因素。
  • 抱歉 - 为什么不直接在数组中搜索目标字符串?我错过了什么吗?
  • 我不能在这里使用 equals 因为字符串的顺序不一样。数组中的 fox 示例是“cab”,我正在搜索“abc”。然后我将不得不运行两个嵌套循环 1 是遍历字符串数组,其他是遍历该字符串中的字符
  • 时间复杂度只告诉您,该方法将如何随着(显着)更大的输入进行扩展。它不会告诉你哪种方法更快。对于您的小字符串和数组,散列方法较慢也就不足为奇了。您需要非常大的输入才能看到不同时间复杂度的影响。

标签: java arrays algorithm sorting java-8


【解决方案1】:

方法 1: 将是 O(NlogN)

方法 2: 将是 O(N*M),其中 M 是数组中每个字符串的长度。

你应该在 O(N) 中线性搜索:

for (String str : arr) {
    if (str.equals(target)) return true;
}
return false;

【讨论】:

  • if (str.equals(target)) : equals 会说“abc”和“bca”相等吗?
【解决方案2】:

Time Complexity 仅告诉您,该方法将如何随着(显着)更大的输入进行扩展。它不会告诉你哪种方法更快。

完全有可能解决方案对于较小的输入大小(字符串长度和/或数组长度)会更快,但由于其时间复杂性,因此对于较大的输入规模很差。但是,当输入大小的自然限制阻止它时,您甚至可能永远不会遇到具有更好时间复杂度的算法变得更快的点。

您没有显示您的方法的代码,但您的第一种方法可能会在字符串上调用类似 toCharArray() 的方法,然后是 Arrays.sort(char[])。这意味着 sort 对原始数据进行操作。

相比之下,当您的第二种方法使用HashMap<Character,Integer> 来记录频率时,它将受到字符和计数的装箱开销,并且还使用需要处理的大得多的数据结构。

因此,散列方法对于小字符串和数组来说速度较慢也就不足为奇了,因为它具有明显更大的固定开销以及依赖于大小的 (O(n)) 开销。

所以第一种方法必须承受O(n log n) 的时间复杂度,才能改变这个结果。但这不会发生。一般而言,时间复杂度是排序的最坏情况。正如this answer 中所解释的,the documentation of Arrays.sort 中指定的算法不应被视为理所当然。当您调用Arrays.sort(char[]) 并且数组大小超过某个阈值时,实现将转向Counting Sort,时间复杂度为O(n)(但暂时使用更多内存)。

因此,即使使用大字符串,您也不会遇到更糟糕的时间复杂度。事实上,计数排序与频率图有相似之处,但通常更有效,因为它避免了装箱开销,使用int[] 数组而不是HashMap<Character,Integer>

【讨论】:

    【解决方案3】:

    让我们分解问题:

    您需要一个函数来按字符对字符串进行排序 (bccabc -> abbccc) 以便能够将给定字符串与现有字符串进行比较。

    Function<String, String> sortChars = s -> s.chars()
            .sorted()
            .mapToObj(i -> (char) i)
            .map(String::valueOf)
            .collect(Collectors.joining());
    

    您可以预先计算一组唯一标记(数组中的值,已排序的字符),而不是在比较时对给定字符串的字符进行排序:

    Set<String> tokens = Arrays.stream(arr)
            .map(sortChars)
            .collect(Collectors.toSet());
    

    这将产生值"abc","acd","ack","dns"

    之后你可以创建一个函数来检查给定的字符串,当按字符排序时,匹配任何给定的标记

    Predicate<String> match = s -> tokens.contains(sortChars.apply(s));
    

    现在您可以轻松地检查任何给定的字符串,如下所示:

    boolean matches = match.test("bca");
    

    匹配只需要对给定的输入进行排序并进行哈希集查找以检查它是否匹配,因此它非常有效

    您当然可以将 Function 和 Predicate 编写为方法(String sortChars(String s)boolean matches(String s),如果您不熟悉函数式编程。

    【讨论】:

      【解决方案4】:

      其他答案的更多附录。当然,您的两个选项具有不同的性能特征。但是:了解性能不一定是做出决定的唯一因素!

      含义:如果您谈论的是在大型数据集上每分钟运行数百或数千次的搜索:那么可以肯定的是,您应该投入大量时间来提出能够提供最佳性能的解决方案。最有可能的是,这包括在处理真实数据时使用实际测量值进行各种实验。时间复杂度是一种理论结构,在现实世界中,还有诸如 CPU 缓存大小、线程问题、IO 瓶颈等因素会对实数数产生重大影响。

      但是:当您的代码每分钟只执行一次工作时,即使是几十或几百 MB 的数据......那么可能值得关注性能。

      换句话说:“排序”解决方案听起来很直接。它易于理解、易于实现,并且很难出错(有一些不错的测试用例)。如果该解决方案“足够好”地完成了工作,那么请考虑使用该解决方案:简单的解决方案。

      性能是一个奢侈问题。只有在有理由的情况下才能解决它。

      【讨论】:

        猜你喜欢
        • 2019-10-18
        • 2013-09-21
        • 1970-01-01
        • 2012-11-12
        • 1970-01-01
        • 1970-01-01
        • 2018-09-05
        • 1970-01-01
        相关资源
        最近更新 更多