有没有办法让这个更快的算法?

时间:2013-08-13 20:22:29

标签: c# .net algorithm big-o

如果我理解Big O Notation,并且相信我此时的理解可能远低于大多数,以下代码行是 O(n 2 )< / em> 根据Keyser的评论,这实际上已经是 O(n)操作:

"Hello, World!".ToLower().Contains("a");

因为ToLower()O(n)操作,Contains也是O(n + n)。也许它是Release,我的理解仍然是模糊的。

注意:下面列出了在Stopwatch版本中运行的测试方法,并利用private static void TestToLower(int i) { var s = "".PadRight(i, 'A'); var sw = Stopwatch.StartNew(); s.ToLower().Contains('b'); sw.Stop(); _tests.Add(string.Format("ToLower{0}", i), sw.ElapsedMilliseconds); } private static void TestHashSet(int i) { var s = "".PadRight(i, 'A'); var sw = Stopwatch.StartNew(); var lookup = new HashSet<char>(s.ToLower().AsEnumerable()); lookup.Contains('b'); sw.Stop(); _tests.Add(string.Format("ToHashSet{0}", i), sw.ElapsedMilliseconds); } private static void TestHashSet2(int i) { var s = "".PadRight(i, 'A'); var sw = Stopwatch.StartNew(); var lookup = new HashSet<char>(s.ToLower().ToArray()); lookup.Contains('b'); sw.Stop(); _tests.Add(string.Format("ToHashSet2{0}", i), sw.ElapsedMilliseconds); } 类来跟踪运行时间。

然而,我想让它更快,所以请考虑以下三种测试方法:

TestToLower(1000000);
TestToLower(2000000);
TestToLower(4000000);

TestHashSet(1000000);
TestHashSet(2000000);
TestHashSet(4000000);

TestHashSet2(1000000);
TestHashSet2(2000000);
TestHashSet2(4000000);

现在考虑执行以下内容:

ToLower1000000: 22.00 ms
ToLower2000000: 40.00 ms
ToLower4000000: 84.00 ms
ToHashSet1000000: 48.00 ms
ToHashSet2000000: 73.00 ms
ToHashSet4000000: 145.00 ms
ToHashSet21000000: 58.00 ms
ToHashSet22000000: 107.00 ms
ToHashSet24000000: 219.00 ms

结果如下:

ToLower

他们每个人显然都必须使用HashSet方法,但我正在尝试使用TestHashSet来更快地进行查找。理想情况下,您不必扫描整个字符串。此外,我真的认为第二个整体测试HashSet会更快,因为它不需要创建大量内存来分配{{1}}。

回想起来,我认为为什么最后两种方法的速度较慢。我相信它们比较慢,因为我有与第一个算法相同的算法(即我必须至少完成两次整个字符串),但最后我会在那之后进行查找。

如何让这个算法更快?我们经常使用它,我们必须比较字符串,无论情况如何。

2 个答案:

答案 0 :(得分:3)

没有违法行为,但你不懂大O. O(n + n)与O(n)相同。 big-O的重点是“隐藏”不变因素。在这个问题上,使用一个处理器不能比O(n)更好。您可以通过将字符串拆分为k个片段并使用单独的线程搜索它们来获得k核上的O(n / k)。

将字符转换为小写是一个恒定时间操作。检查与所需字符的匹配是一种廉价的恒定时间操作。在哈希集中插入字符是一种相当昂贵的常量时间操作。在哈希集测试中,您已经将这个相当大的常量成本添加到每个字符的处理中。由于它大于仅仅查看字符以查看它是否与模式字符串匹配的常量成本,因此运行时间会变长。

只有在查找多个值时才使用哈希集进行查找。如果你需要在同一个字符串上进行多次查找以查看它是否包含k个不同字符中的任何一个或全部,那么你可能会受益于构建哈希集,因为k个查找将需要O(k )时间而不是O(kn)时间来扫描每个字符的整个字符串。

如果您在每个字符串中只查找一个字符,请忘记big-O。不变因素是你最大的希望。你应该考虑一个低级循环。它会是这样的:

static bool findChar(string str, char charToFind) {
  char upper = Char.toUpper(charToFind);
  char lower = Char.toLower(charToFind);
  for (int i = 0; i < str.length; i++) {
    if (str[i] == upper || str[i] == lower) {
      return true;
    }
  }
  return false;
}

提前抱歉语法问题。我不是C#程序员。请注意,这会扫描字符串最多一次。如果早期找到角色,它就会停止。检查的字符的预期数量是字符串中的一半。此函数也不会产生垃圾。

另一方面,

触及的预期字符数
str.ToLower().Contains("a");

str长度的1.5倍,将生成垃圾。所以你可能会通过显式循环获胜

如果这仍然太慢,原生函数可能会产生一个小的增益。你必须尝试找出它。

答案 1 :(得分:1)

我相信你的代码是 O(2n)= O(n)。那是因为每次调用遍历输入字符串2次。为了减少运行时间的算法限制,你需要一个具有对数界限或 O(n ^ k)的算法,其中k <1 算法,我相信在你的场景中是不可能的。我建议的最好的方法是使用不变的特定信息:例如,如果您知道您的字符串始终以大写字母开头,则只更改字符串中的第一个字符。这是一个如何利用特定领域知识的示例。