第十五章 字符串(一)

    第十五章是《编程珠玑》的最后一章,本章由易到难围绕字符串处理主要分三个部分:1,单词。2,短语。3,文本。在这三个问题中涉及到的技术有C++中的标准模板库,哈希表,新介绍的一种数据结构“后缀数组”。先从第一个部分“单词”入手。

    “我们的第一个问题是为文档中包含的单词生成一个列表”。这个问题用C++标准模板库中的set和string可以很容易解决。

[cpp]  view plain copy
  1. // Sorted list of words(between white space) in file   
  2.   
  3. #include <iostream>  
  4. #include <string>  
  5. #include <set>  
  6.   
  7. using namespace std;  
  8.   
  9. int main()  
  10. {  
  11.     string str;  
  12.     set<string> S;  
  13.     set<string>::iterator i;  
  14.     //while((cin>>str) != EOF)  
  15.     while(cin>>str)  
  16.         S.insert(str);  
  17.     for(i=S.begin(); i!=S.end(); i++)  
  18.         cout<<*i<<endl;  
  19.           
  20.     return 0;  
  21. }  
这里需要注意的问题是C++的标准输入函数cin()的用法,其自动识别空格,并且在输入终止符后自动退出,在我的编译环境下为Ctrl+Z加回车。


    “接下来的问题是对文档中每个单词的出现次数进行统计”。这个问题利用map容器即可方便地解决。

[cpp]  view plain copy
  1. // Sorted list of words and counts in file   
  2.   
  3. #include <iostream>  
  4. #include <string>  
  5. #include <map>  
  6.   
  7. using namespace std;  
  8.   
  9. int main()  
  10. {  
  11.     //int count=0;  
  12.     string str;  
  13.     map<string, int> M;  
  14.     map<string, int>::iterator i;  
  15.     //while((cin>>str) != EOF)  
  16.     /* 
  17.     while(cin>>str) 
  18.         M.insert(str, ++count); 
  19.     for(i=S.begin(); i!=S.end(); i++) 
  20.         cout<<(*i)->first<<"\t"<<(*i)->second<<endl; 
  21.     */  
  22.     /* 
  23.     while(cin>>str) 
  24.         M.insert(make_pair(str, ++count)); 
  25.     */  
  26.     while(cin>>str)  
  27.         M[str]++;  
  28.     for(i=M.begin();i!=M.end();++i)  
  29.         cout<<i->first<<"\t"<<i->second<<endl;      
  30.       
  31.     return 0;  
  32. }  
这里面主要需要注意的问题就是map的用法。


    程序达人们为了追求效率,减少处理的时间,对这个程序进行改进,定制了散列表,将字符串通过哈希算法分布到散列表中,这样就很有效地减少了程序运行过程中“插入”和“输出”的时间,据书中的记载是这个算法处理有29131个不同的单词詹姆斯一世钦定版《圣经》一共只需要3.0秒(map版本为7.6秒),其中处理时间(主要是插入和输出)是0.56秒(以前是5.2秒),所以用30行代码定制的散列表比C++标准模板库中的映射快一个数量级。

    标准模板库中的set和map大部分实现都使用到了“平衡搜索树”这个结构,其将字符串看作是不可分割的对象进行操作。平衡搜索树中的元素始终处于有序状态,从而很容易执行寻找前驱结点或者按顺序输出元素之类的操作。散列表的平均速度很快,但缺乏平衡树提供的最坏情况性能保证,也不能支持其他涉及顺序的操作。

    在散列表程序中,有初始条件如下:《圣经》中有29131个不同的单词,因此用跟29131最近接的质数作为散列表的大小,并将乘数定义为31:。

[cpp]  view plain copy
  1. //  Sorted list of words with counts (using hash method)  
  2.   
  3. #include <stdio.h>  
  4. #include <string.h>  
  5. #include <stdlib.h>  
  6.   
  7. #define NHASH 29989     //   
  8. #define MULT  31        //  
  9.   
  10. typedef struct node  
  11. {  
  12.         char *word;  
  13.         int count;  
  14.         struct node *next;  
  15. }node, *nodeptr;  
  16.   
  17. nodeptr bin[NHASH];  
  18.   
  19. #define NODEGROUP 1000  // 一次分配1000个节点块   
  20. int nodesleft = 0;  
  21. nodeptr freenode;  
  22.   
  23. nodeptr nmalloc()  
  24. {  
  25.         if(nodesleft == 0)  
  26.         {  
  27.                      freenode=(nodeptr)malloc(NODEGROUP*sizeof(node));  
  28.                      nodesleft=NODEGROUP;  
  29.         }  
  30.         nodesleft--;  
  31.           
  32.         return freenode++;  
  33. }  
  34.   
  35. #define CHARGROUP 10000 // 一次分配10000个字符   
  36. int charsleft = 0;  
  37. char *freechar;  
  38.   
  39. char *smalloc(int size)  
  40. {  
  41.      //if(charsleft == 0)  
  42.      if(charsleft < size)  
  43.      {  
  44.                   freechar=(char *)malloc((size+CHARGROUP)*sizeof(char)); // size+chargroup  
  45.                   charsleft=size+CHARGROUP;  
  46.      }  
  47.      charsleft-=size;  
  48.      freechar+=size;  
  49.        
  50.      return freechar-size;  
  51. }  
  52.   
  53. unsigned int hash(char *p)  
  54. {  
  55.     unsigned int h=0;  
  56.     for(;*p;++p)  
  57.         h=h*MULT+*p;  
  58.       
  59.     return h%NHASH;  
  60. }  
  61.   
  62. int inword(char *w)  
  63. {  
  64.     int x=hash(w);  
  65.     nodeptr p;  
  66.     for(p=bin[x];p!=NULL;p=p->next)  
  67.         //if(strcmp(p->word, w))  
  68.         if(strcmp(p->word, w)==0) //  
  69.         {  
  70.                            p->count++;  
  71.                            return 0;  
  72.         }  
  73.       
  74.     // THE wrong code  
  75.     /* 
  76.     p=(nodeptr)malloc(sizeof(node)); 
  77.     p->word=(char *)malloc(sizeof(w));    // calculate the size of new input word 
  78.     strcpy(p->word, w);                   // use strcpy() 
  79.     p->count=0; 
  80.     p->next=bin[0]->next; 
  81.     bin[0]->next=p; 
  82.     */  
  83.       
  84.     // THE inefficient code   
  85.     /* 
  86.     p=(nodeptr)malloc(sizeof(node)); 
  87.     p->word=(char *)malloc(strlen(w)+1);  // calculate the size for strcpy 
  88.     strcpy(p->word, w); 
  89.     p->count=1; 
  90.     //p->next=bin[x]->next; 
  91.     p->next=bin[x]; 
  92.     //bin[x]->next=p; 
  93.     bin[x]=p; 
  94.     */  
  95.       
  96.     // THE efficient code   
  97.     p=nmalloc();  
  98.     p->word=smalloc(strlen(w)+1);  
  99.     p->count=1;  
  100.     strcpy(p->word, w);  
  101.     p->next=bin[x];  
  102.     bin[x]=p;  
  103.       
  104.     return 0;  
  105. }  
  106.   
  107. int main()  
  108. {  
  109.     int i;  
  110.     //char *input;  
  111.     char input[100];  
  112.     nodeptr p;  
  113.     for(i=0;i<NHASH;++i)  
  114.         bin[i] = NULL;  
  115.     //while(scanf("%s", &input) != EOF)  
  116.     while(scanf("%s", input) != EOF)    
  117.         inword(input);  
  118.     for(i=0;i<NHASH;++i)  
  119.     {  
  120.                         for(p=bin[i];p!=NULL;p=p->next)  
  121.                             printf("%s\t%d\n",p->word,p->count);  
  122.     }  
  123.       
  124.     return 0;  
  125. }  
这个程序中需要注意的问题是:1,首先对内存分配算法进行了优化,利用整块的分配取代每次单独的malloc()。2,还是需要注意scanf()函数的用法,(这个问题到后面的单词级别生成随机文本时才被我仔细地发现),其自动识别空格键,并在为每个单词(由空格分隔的字符串)提供空字符作为结束标志。3,哈希函数的用法,其中包括哈希表大小的确定和乘数的选择。哈希表的大小可以确定为是质数为最好,可以证明的是这样会使冲突尽量减少而且使数据的分布更加均匀。但是这个质数是选择比数据量大的好还是少的好呢?这个我还没有太多考虑深入研究,就本程序来说,其定义的NHASH为29989要比不同的单词数29131大,但在后面的一个单次级别随机文本生成的问题的哈希表解决方法中,其定义的NHASH要比实际的数量少。我想还是应该定义哈希表的大小比实际的数据量大小小,但是要比实际的哈希值的数量大小大,这样冲突处理方法链地址法才有用武之地,而又不浪费太多的空间又减少了冲突,在本程序中真正的单词数量要远比29131大得多,这个数据只是不同单词的数量。总之哈希部分要再研究研究。

你可能感兴趣的:(数据结构,算法,String,文档,input,pair)