用于查找唯一字符串的内存有效方法

时间:2009-05-15 02:13:01

标签: c++ algorithm string data-structures

我有一个如下所示的数据集:

000 100 200 300 010 020 030 001 002 003     
001 101 201 301 011 021 031 000 002 003    
002 102 202 302 012 022 032 001 000 003    
003 103 203 303 013 023 033 001 002 000    
010 110 210 310 000 020 030 011 012 013     
020 120 220 320 010 000 030 021 022 023     
030 130 230 330 010 020 000 031 032 033     
033 133 233 333 013 023 003 031 032 030     
100 000 200 300 110 120 130 101 102 103     
133 033 233 333 113 123 103 131 132 130     
200 100 000 300 210 220 230 201 202 203     
233 133 033 333 213 223 203 231 232 230     
300 100 200 000 310 320 330 301 302 303     
303 103 203 003 313 323 333 301 302 300     
313 113 213 013 303 323 333 311 312 310     
330 130 230 030 310 320 300 331 332 333    
331 131 231 031 311 321 301 330 332 333    
332 132 232 032 312 322 302 331 330 333    
333 133 233 033 313 323 303 331 332 330 

我打算做的是从中生成唯一字符串列表,产生:

000
001
002
003                      
010
011
012
013
020
021
022
023
030
031
032
033
100
101
102
103
110
113
120
123
130
131
132
133
200
201
202
203
210
213
220
223
230
231
232
233
300
301
302
303
310
311
312
313
320
321
322                      
323
330
331      
332      
333

我必须生成的代码就是这个。但它非常耗费记忆力。 因为实际上弦长> 36并且超过3500万 文件中的行。每行具有> 36 * 3列/条目数。 有记忆效率的方法吗?

#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <map>
using namespace std;


int main  ( int arg_count, char *arg_vec[] ) {
    if (arg_count !=2 ) {
        cerr << "expected one argument" << endl;
        return EXIT_FAILURE;      
    }

    string line;
    ifstream myfile (arg_vec[1]);


     map <string,int> Tags;    

    if (myfile.is_open())
    {
        while (getline(myfile,line) )
        {
            stringstream ss(line);    
            string Elem;


            while (ss >> Elem) {      

                Tags[Elem] = 1;           

            }


        }
        myfile.close();  
    }
    else { cout << "Unable to open file";} 


   for (map <string,int>::iterator iter = Tags.begin(); iter !=
           Tags.end();iter++) {      
       cout << (*iter).first << endl; 
   }



    return 0;
}

9 个答案:

答案 0 :(得分:7)

这取决于数据集的特征。在更糟糕的情况下,在所有字符串都是唯一的情况下,您将需要O(n)内存来记录您的看见集,或者需要O(n ^ 2)时间来重新扫描每个单词上的整个文件。但是,可以进行改进。

首先,如果你的数据集只包含3位数的整数,那么1000个bool的简单数组将比地图具有更高的记忆效果。

如果你正在使用一般数据,那么另一种好的方法是对集合进行排序,因此相同字符串的副本最终会相邻,然后只需删除相邻的单词。 sorting a dataset too large to fit in memory有很好的研究算法。当集合中大部分单词是唯一的时,这是最有效的,因此在内存中保存一组看到的单词非常昂贵。

顺便说一下,这可以通过shell管道轻松实现,因为GNU sort为你做了外部排序:

tr " " "\n" < testdata | LC_ALL=C sort | uniq

传递LC_ALL = C进行排序会禁用区域设置处理和多字节字符集支持,这可以显着提高速度。

答案 1 :(得分:4)

O(1)记忆[ram]:

如果你想根本不使用任何内存(除了几个临时变量),你可以简单地一次读取1个条目,如果输出文件中尚不存在,则将其添加到输出文件中。由于您必须一次从输出文件中读取1个条目,因此时间会慢一些。

您可以按字母顺序将条目插入到输出文件中,然后通过插入每个条目的二进制搜索,您可以在O(logn)时间内查看条目是否已存在。要实际插入,您需要重新创建文件,尽管是O(nlogn)。你为每个输入字符串执行n次,所以整个算法将在O(n ^ 2logn)中运行(包括查找以查找插入位置+插入)并使用O(1)RAM内存。

由于您的输出文件已按字母顺序排列,但未来的查询也只能通过二进制搜索进行O(logn)。

您还可以通过在文件中的条目之间留出过多空间来最小化文件的重新创建阶段。算法完成后,您可以对文件执行真空操作。这将把它归结为O(nlogn)。


减少记忆的另一种方法:

如果您的字符串共享公共前缀很常见,那么您可以使用trie并且可能使用更少的内存,因为您提到的字符串是&gt;长度36.但这仍然会占用大量内存。

答案 2 :(得分:3)

嗯,std :: set可能会比std :: map稍快一点,并且使用的内存更少。

答案 3 :(得分:2)

似乎给定了大量条目,符号序列中将存在合理的重叠量。您可以使用每个位置的条目构建树,每个序列作为节点。假设您有一个条目12345和12346,那么序列中的前四个条目重叠,因此可以存储在具有6个节点的树中。

你可以走树,看看给定的符号是否包含在给定的位置,如果没有,你会添加它。当您到达给定字符串的末尾时,您只需将该节点标记为终止字符串节点。再现唯一条目将是树的深度优先遍历的问题,从根节点到每个终结器的路径表示唯一条目。

如果你对数据集进行分区,比如说X千行的块并聚合树,它会做一个很好的map-reduce作业。

您正在查看10 ^ 36的节点空间,因此如果数据完全是随机的,您将查看可能数量较多的节点。如果存在大量重叠和较少数量的唯一条目,您可能会发现使用较少的条款

答案 4 :(得分:1)

你可能只是为它写一个就地排序。您仍然需要将文件加载到内存中,因为使用IO进行就地排序效率不高。你必须为每次比较阅读和写作。

答案 5 :(得分:1)

std::set会更好,但你应该研究哈希集。那些在当前的C ++标准库中不可用(虽然它应该在C ++ 0x中)但是有些编译器有实现。 Visual C ++有stdext::hash_set和boost有一些东西,请参阅Boost Multi-index Containers Library

答案 6 :(得分:1)

尝试将STXXL作为大型数据集的外部stl。

答案 7 :(得分:1)

我建议的解决方案是使用内存映射文件来访问数据和基数排序来对列表进行排序。如果字符串具有相同的长度,则这是微不足道的。 如果字符串具有不同的长度,则使用基数排序来使用n个第一个字符进行快速预分类,然后使用最合适的方法对未排序的子列表进行排序。使用非常短的子列表,可以进行简单的冒泡排序。使用较长的列表使用快速排序或使用STL集与提供比较运算符的自定义类。

使用内存映射和基数排序,内存要求和性能将是最佳的。您所需要的只是映射空间(〜文件大小)和一个32位值的表,每个字符串有一个值来保存链表以进行基数排序。

您还可以使用更紧凑的字符串编码来节省内存并加快排序速度。一个简单的编码将使用每个字符2位,使用值1,2和3表示三个字母,0表示字符串结束。或者更高效,使用base 3编码,打包成整数并编码前面的字符串长度。假设您有字符c1,c2,C3,c4,编码将产生整数c1 * 3 ^ 3 + c2 * 3 ^ 2 + c3 * 3 ^ 1 + c4 * 3 ^ 0。这假设您为每个字符分配一个0到2的值。通过使用这种编码,您将节省内存,并且如果要排序的字符串数量很大,将优化排序。

答案 8 :(得分:1)

您可以查看优秀的图书馆Judy arrays。基于紧凑的基于trie,非常快速且内存有效的数据结构,可扩展到字符串。比任何搜索树都好。 您可以将JudySL功能用于您的目的。您可以使用它类似于您的程序,但速度更快,内存效率更高。