[原创]关键词搜索算法改进——顺序表字典二分法逐级检索

本文关键词:关键词搜索, 顺序表字典, 二分法, 逐级检索

问题重述:有一个内含有大约40万条常用词汇的词库。现给定一篇文章,使用这个词库分析出常用词汇的出现次数,并按出现次数由高到低排序这些词语。

改进算法的思路:
  1. 通常一篇文章所包含的词语远少于词库中40万的数量;
  2. 数据库建立索引之后,可采用“二分法”对词语进行快速定位;
  3. 逐字缩小查询范围,如果查询到某个字符时范围已经为0,那么可以预测其后的词一定也不存在,(例如查询到forest时已经没有匹配的词了,就可以到此结束)。

该算法将时间复杂度由 O(m*n) 减少为 O( log2(m) * n ) (m为字典词汇数量,n为待搜索文本的长度)。测试结果表明:344KB 文本搜索耗时1.39s,而采用遍历法则需105s,可见速度的提升是很可观的。
以下是算法的实现:

一、首先,利用文本文件制作词典(二进制文件)。包括导入字符串数据、排序、剔除重复项、创建索引表。

字典文件格式描述如下:

  1. 文件头(16字节):
---------------------------------------------------------------------------
| "MAODICT"字符串(8字节) | 索引区开始位置(4字节) | 索引区结束位置(4字节) |
---------------------------------------------------------------------------

  2. 字符串存储区:

     每条字符串均以'/0'结尾,连续存放。

  3. 索引区:

     每个索引表项格式(5字节):
---------------------------------------------------
| 字符串偏移量(4字节) | 词条长度(1字节) |
---------------------------------------------------

字符串紧跟文件头存放,索引区在字符串存储区之后。

文件头和索引表项结构体:

  1. // Dictionary file header 
  2. typedef struct _DictHeader 
  3.     char maodict[8]; // string "MAODICT" 
  4.     long so;  // index start offset 
  5.     long eo;  // index end offset 
  6. } DictHeader; 
  7.  
  8. // Index item structure(5 bytes) 
  9. typedef struct _IndexItem 
  10.     union 
  11.     { 
  12.         long offset;  // string offset 
  13.         char * str;   // string pointer(unused) 
  14.     }; 
  15.     char length;  // string length 
  16. } IndexItem; 

数据导入代码暂略,详见附件msearch.cpp中的textToBinaryFile()函数。

二、利用创建的字典文件,编写检索程序。SearchTextFile()函数利用传入的文件名打开并进行“内存文件映射”,利用传入的数据流读取文本数据。从某个位置起始,向后组成“词语”进行查询,到一定长度“失配”后,起始位置移到下一个字符。由于数据流不能回退,故需缓存已读取的字符,每次“失配”后将缓冲区向前整体移动一个字符位置(memmove())。算法利用了两个变量:j 用于记录当前字符相对于起始位置的偏移,k 用于记录缓冲区中已读取的字符的数量。

该部分代码如下:

  1. j = 0;  // word char index 
  2. k = 0;  // number of buffered chars 
  3.  
  4. do 
  5.     j = 0;  // return zero 
  6.  
  7.     si = 0; 
  8.     li = rcCount-1; 
  9.     for(j=0; ; j++) 
  10.     { 
  11.         while(k<=j) 
  12.             cbuf[k++] = fgetc(fp); 
  13.         if(cbuf[j]==EOF) 
  14.             break
  15.  
  16.         ret = getCharIndex(dbuf, idx, cbuf[j], j, &si, &li); 
  17.  
  18.         //====================================== 
  19.         // if this is a complete word, add it 
  20.         if(ret && j==idx[si].length-1) 
  21.         { 
  22.             ...添加到查询结果列表,代码省略... 
  23.         } 
  24.         //====================================== 
  25.         if(!ret) 
  26.             break
  27.         else 
  28.         { 
  29.             if(li-si==0) 
  30.                 if(j==idx[si].length-1) 
  31.                     break
  32.         } 
  33.  
  34.     } 
  35.     // move buffer one step foward (overlapped spaces!!!) 
  36.     if(k>1) 
  37.         memmove(cbuf, cbuf+1, (k-1)*sizeof(cbuf[0])); 
  38.     //printf("%d/n", k); 
  39.     k --; 
  40. } while(cbuf[0]!=EOF); 

三、二分法逐字检索 是查询程序的核心算法,代码如下:

  1. /*
  2. * dbuf: data area pointer
  3. * idx:  index area pointer
  4. * ch:   current character
  5. * j:    current character's position in word
  6. * _si, _li: previous range
  7. * return: 1 - fonnd; 0 - not found
  8. */ 
  9. static inlineint getCharIndex( constchar      * dbuf, 
  10.                                 const IndexItem * idx,  
  11.                                 int ch,  
  12.                                 int j,int * _si, int * _li) 
  13.     int si = *_si; 
  14.     int li = *_li; 
  15.     int mi; 
  16.     int ssi, lli, mmi; 
  17.  
  18. #define GETCH(x) ( (unsigned char)*(dbuf + idx[x].offset + j) ) 
  19.  
  20.     if(ch < GETCH(si)) 
  21.     { 
  22.         // above the upper border [not found - case 1] 
  23.         *_si = *_li = si; 
  24.         return 0; 
  25.  
  26.     } 
  27.     else if(ch == GETCH(si)) 
  28.     { 
  29.         // start position 
  30.         *_si = si; 
  31.         if(ch == GETCH(li)) 
  32.         { 
  33.             // li is just the end 
  34.             *_li = li; 
  35.             return 1; 
  36.         } 
  37.         else 
  38.         { 
  39.             /* ch < GETCH(li) */ 
  40.             // using binary search, find the end 
  41.             ssi = si; 
  42.             lli = li; 
  43.             while(lli-ssi>1) 
  44.             { 
  45.                 mmi = (ssi + lli) / 2; 
  46.                 if(ch < GETCH(mmi)) 
  47.                     lli = mmi; 
  48.                 else 
  49.                     ssi = mmi; 
  50.             } 
  51.             *_li = ssi; 
  52.             return 1; 
  53.         } 
  54.  
  55.     } 
  56.     else 
  57.     { 
  58.         /* ch > GETCH(si) */ 
  59.  
  60.         if(ch > GETCH(li)) 
  61.         { 
  62.             // below the lower border [not found - case 2] 
  63.             *_si = *_li = li+1; 
  64.             return 0; 
  65.         } 
  66.         else if(ch == GETCH(li)) 
  67.         { 
  68.             *_li = li; 
  69.             // using binary search, find the start 
  70.             ssi = si; 
  71.             lli = li; 
  72.             while(lli-ssi>1) 
  73.             { 
  74.                 mmi = (ssi + lli) / 2; 
  75.                 if(ch <= GETCH(mmi)) 
  76.                     lli = mmi; 
  77.                 else 
  78.                     ssi = mmi; 
  79.             } 
  80.             *_si = lli; 
  81.             return 1; 
  82.         } 
  83.         else 
  84.         { 
  85.             /* ch < GETCH(li) */ 
  86.             // the most common case 
  87.             while(li-si>1) 
  88.             { 
  89.                 mi = (si + li) / 2; 
  90.                 if(ch < GETCH(mi)) 
  91.                     li = mi; 
  92.                 else if(ch > GETCH(mi)) 
  93.                     si = mi; 
  94.                 else 
  95.                 { 
  96.                     /* == found */ 
  97.  
  98.                     // search the upper border 
  99.                     ssi = si; 
  100.                     lli = mi; 
  101.                     while(lli-ssi>1) 
  102.                     { 
  103.                         mmi = (ssi + lli) / 2; 
  104.                         if(ch <= GETCH(mmi)) 
  105.                             lli = mmi; 
  106.                         else 
  107.                             ssi = mmi; 
  108.                     } 
  109.                     *_si = lli; 
  110.  
  111.                     // search the lower border 
  112.                     ssi = mi; 
  113.                     lli = li; 
  114.                     while(lli-ssi>1) 
  115.                     { 
  116.                         mmi = (ssi + lli) / 2; 
  117.                         if(ch < GETCH(mmi)) 
  118.                             lli = mmi; 
  119.                         else 
  120.                             ssi = mmi; 
  121.                     } 
  122.                     *_li = ssi; 
  123.  
  124.                     return 1; 
  125.  
  126.                 } 
  127.             } 
  128.             // not included [not found - case 3] 
  129.             *_si = *_li = li; 
  130.             return 0; 
  131.  
  132.         } 
  133.  
  134.     } 
  135.  

四、程序的执行效果:

  1. 使用方法:

  1. J:/Projects/cpp/msearch/Release>msearch -h 
  2. Usage: 
  3.   msearch -c <source file>  .... Convert text file to dictionary. 
  4.   msearch <dict file>       .... Input text to search, ended with [Ctrl+Z]. 
  5.   msearch -h                .... Print help information. 
  6. Examples: 
  7.   msearch -c English.txt    .... Create English.dat. 
  8.   msearch English.dat <gpl3.txt >result.txt 
  9.              .... Search keywords in gpl3.txt and write results to result.txt, 
  10.                   using dictionary English.dat 

  2. 运行结果:

  1. J:/Projects/cpp/msearch/Release>msearch English.dat 
  2.   The licenses for most software and other practical works are designed 
  3. to take away your freedom to share and change the works. 
  4. ^Z 
  5. Processed in 0.012s. 
  6. Totally allocated memory: 35.06KB 
  7. re -- 4 
  8. or -- 3 
  9. are -- 3 
  10. an -- 3 
  11. he -- 3 
  12. and -- 2 
  13. works -- 2 
  14. work -- 2 
  15. the -- 2 
  16. to -- 2 
  17. ha -- 2 
  18. freed -- 1 
  19. freedom -- 1 
  20. free -- 1 
  21. hang -- 1 
  22. hare -- 1 
  23. for -- 1 
  24. her -- 1 
  25. do -- 1 
  26. ice -- 1 
  27. lice -- 1 
  28. license -- 1 
  29. licenses -- 1 
  30. designed -- 1 
  31. of -- 1 
  32. design -- 1 
  33. other -- 1 
  34. our -- 1 
  35. practical -- 1 
  36. change -- 1 
  37. reed -- 1 
  38. share -- 1 
  39. sig -- 1 
  40. sign -- 1 
  41. signed -- 1 
  42. so -- 1 
  43. soft -- 1 
  44. software -- 1 
  45. take -- 1 
  46. cense -- 1 
  47. tic -- 1 
  48. tical -- 1 
  49. away -- 1 
  50. war -- 1 
  51. ware -- 1 
  52. way -- 1 
  53. act -- 1 
  54. most -- 1 
  55. you -- 1 
  56. your -- 1 


==========================================================
完整的程序和源代码请到这里下载:http://down.chinaz.com/soft/24828.htm
==========================================================

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值