【问题标题】:Reducing time complexity of string comparison降低字符串比较的时间复杂度
【发布时间】:2015-10-14 08:29:02
【问题描述】:

我有一个dictionary .txt 文件,其中可能包含一千多个单词及其定义。我已经编写了一个程序来从这个文件中获取每一行的第一个单词,并根据用户输入的字符串检查它:

void checkWord(string input)
{

    std::ifstream inFile;
    inFile.open("Oxford.txt");
    if (inFile.is_open())
    {
        string line; //there is a "using std::string" in another file
        while (getline(inFile, line))
        {
            //read the first word from each line
            std::istringstream iss(line);
            string word;
            iss >> word;
            //make sure the strings being compared are the same case
            std::transform(word.begin(), word.end(), word.begin(), ::tolower);
            std::transform(input.begin(), input.end(), input.begin(), ::tolower);
            if (word == input)
            {
                //Do a thing with word
            }
        }
        inFile.close();
        return "End of file";
    }
    else
    {
        return "Unable to open file";
    }

}

但如果我检查的不仅仅是一个句子,处理时间就会变得很明显。我想了一些方法来缩短这段时间:

  • 为字母表中的每个字母制作一个 .txt 文件(很容易做到,但从长远来看并不是真正的解决方案)
  • 使用 unordered_set 比较字符串(如在 this 问题中)唯一的问题可能是从文本文件中初始创建这些映射
  • 使用其他数据结构来比较字符串? (如 std::map)

鉴于数据已经“排序”,我应该采用哪种数据结构或方法来(如果可能)降低时间复杂度?另外,我用来比较字符串的函数有什么问题吗? (例如,string::compare() 会比“==”更快吗?)

【问题讨论】:

  • std::ifstream 速度很慢,你可以考虑换一个。
  • 搜索 1000 个单词真的需要那么长时间吗?一些真正简单的改进是在读取文件之前将其小写,并在函数开头仅将输入单词小写一次。并且如果多次搜索,将文件内容加载到内存中并搜索加载的列表,而不是多次读取文件。
  • @MatsPetersson:对我来说似乎是一个答案!!
  • 我认为没有必要在每次迭代时将 input 转换为小写。可能不会带来任何改进,因为一个好的编译器可能无论如何都会优化这部分,但它只会使代码看起来更好。另外,我相信is >> word 与字符串标记化方法相比可能是一项相当昂贵的操作(请参阅cplusplus.com/reference/cstring/strtokstackoverflow.com/questions/53849/…)。更熟悉 C++ 内部的人可能想对此发表评论。
  • 谢谢大家!

标签: c++ string


【解决方案1】:

一棵树 (std::map)?还是哈希图 (std::unsorted_map)?您的线性搜索显然是一个蛮力解决方案!对于多次搜索,上述两种方法都显着更胜一筹。

当然,只有在每次程序运行时要多次使用此数据时,这才真正有帮助,而您没有在问题中指定。如果不是,那么加载、解析和存储所有数据只是为了执行一次查找然后退出并没有太大的好处。至少在成功时输入break

您暗示您的输入文件已排序。您可以将二进制搜索解决方案与文件搜索(非常便宜)结合在一起,并在每次迭代时捕捉到最近的换行符,以大致确定文件中具有相同前导(例如)三个字符的所有单词的位置。但是,对于一千个条目来说,这可能是多余的。

【讨论】:

  • 从字符串计算的哈希值也可能有帮助。
  • 老实说,总的来说,我对数据结构还很陌生,所以我不确定要研究哪一个?另外,在整个程序过程中,我可能会经常调用这个函数,所以花费的时间越少越好。通过说我的文件已排序,我的意思是单词从 A 到 Z 列出,所以我也要研究二进制搜索? (谢谢)
  • @Lucas:看看他们俩?
  • @LucasSaldyt 如果您只查找一次预程序运行数据结构将无济于事,只有更有效的方法可以在文件中查找单词。
  • @LucasSaldyt 用于多次运行读入std::unordered_map&lt;std::string,std::string&gt;(除非您出于某种原因需要订购它们,然后使用 std::map<:string>)然后看那里。
【解决方案2】:

不要将所有内容存储在.txt 文件中,而是将其存储在真实数据库中。

SQLite3 是简单任务的好选择,因为它是在进程中而不需要外部服务器。

为了很简单,C API和SQL语句应该很容易学。

类似:

-- Only do this once, for setup, not each time you run your program.
sqlite> CREATE TABLE dictionary (word TEXT PRIMARY KEY);
sqlite> .import /usr/share/dict/words dictionary;
-- Do this every time you run your program.
sqlite> select count(*) from dictionary where word = 'a';
1

【讨论】:

  • 我并没有真正想到这一点,但我也会尝试一下。 (我以前从未使用过数据库,所以我很欣赏这种简洁的方法)
【解决方案3】:

因此,有一些“简单”的修复,也有一些更复杂的修复。

第一步是将所有不必要的东西移出搜索循环:小写input 一次,在循环之前,而不是每次 - 毕竟,它没有改变。如果可能,请将Oxford.txt 也小写,这样您就不必为每一行都小写word

如果您要多次搜索文件,多次读取文件绝对不是一个好的解决方案——即使它第二次缓存在文件系统中。

因此,将其读入某个容器一次,非常简单的一个是std::vector [同时将字符串小写] 并对其进行迭代。下一个改进是对向量进行排序,然后我们进行二分搜索(但你必须自己编写二分搜索——这并不难)

稍微复杂一点的解决方案 [但搜索速度更快] 是使用 std::map&lt;std::string, std::string&gt; wordlist(但这也需要更多空间),然后使用 auto pos = wordlist.find(input); if (pos != wordlist.end() ... found word ...

【讨论】:

    【解决方案4】:

    您可以受益于使用前缀树,也称为trie 数据结构,因为它适合拥有字典和频繁查找的用例里面的话。

    最简单的 trie 模型是一棵树,其中每个节点都包含一个字母和一个标志,以判断当前字母是否是一个单词的结尾(此外,还有指向该单词其他数据的指针)。

    包含字典 @​​987654325@ 的 trie 示例图片:

    要搜索单词,从词根开始,对于单词的每个字母,跟随包含当前字母的节点(如果它不作为当前节点的子节点出现,则停止)。搜索时间与查找词的长度成正比,而不是与字典的大小成正比。

    trie 还可以让您轻松获取字典中单词的字母(字典顺序)顺序:只需对其进行 pre-order traversal

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-24
      • 2011-06-15
      • 2012-07-28
      • 2016-10-07
      相关资源
      最近更新 更多