1. 程式人生 > >搜尋引擎的那些事(中文分詞)

搜尋引擎的那些事(中文分詞)

               

【 宣告:版權所有,歡迎轉載,請勿用於商業用途。  聯絡信箱:feixiaoxing @163.com】 

    前面,我們在介紹搜尋引擎的時候也談到過中文分詞。和英文不一樣,中文上所有的漢字都是連在一起的,所以我們的一項工作就是把這些詞語拆分成一個一個片語。因為只有這樣才能構建索引資料庫、才能查詢索引,我們構建搜尋引擎的工作才能繼續進行下去。

    現在關於中分分詞有好多的分詞方法,什麼從左向右最大長度法、從右向左最大長度法、最少片語法、貝葉斯概率處理法等等。但是說了這麼多,我們分詞的標準是什麼,關鍵還在於有一個好的分詞字典。中國漢字那麼多,但是數量上估計幾萬個足夠了。但是如果漢字與漢字組合起來構成片語,那數量就多了去了,比如說文學詞語、口語、人名、地名、詩詞、專業術語等等。說到這裡,可以給大家舉個例子看一下。大家都喜歡搜狗輸入法,一方面它的設計比較人性化,另外一方面不正是因為它詞庫很多、使用方便嗎?

    關於分詞的演算法,有的人覺得很玄乎,其實寫一個也不復雜,我們就可以寫一個最大長度遍歷的分詞演算法。當然這裡只是考慮了漢字分詞,如果是英文、數字、繁體字或者符號,那就要另外考慮了。這也驗證了我們之前反覆說的一句話,簡單做一件事不難,關鍵是怎麼做好了、幹漂亮了、高效又能節省成本。

#include <stdio.h>#include <string.h>#include <memory.h>#include <malloc.h>#define LENGTH 256static char* dic[] = {"北京", "大學", "北京大學"};#define
NUMBER (sizeof(dic) / sizeof(char*))
static char* buffer[LENGTH] = {0};static int max_len = 0;static int min_len = 0;static int  find_max_length()int index; unsigned int len; len = 0for(index = 0; index < NUMBER; index ++) {  if(len < strlen(dic[index]))  {   len = strlen(dic[index]);  } }  return
len;}static int find_min_length()int index; unsigned int len; len = strlen(dic[0]); for(index = 1; index < NUMBER; index++) {  if(strlen(dic[index]) < len)  {   len = strlen(dic[index]);  } } return len;}static void show_buffer(int end)int start; for(start = 0; start < end; start ++) {  printf("%s ", buffer[start]); }   printf("\n");}static void _process_segment(char* str, int index){ int start; int len ; int  found; char* data; char* label;  if('\0' == *str) {        show_buffer(index);  return; }  label = str + strlen(str);retry:  len = strlen(str); if(len > LENGTH) {  len = LENGTH; }  if(len > max_len) {  len = max_len; } found = 0while(len >= min_len) {  for(start = 0; start < NUMBER; start ++)  {   if(0 == strncmp(str, dic[start], len))   {    found = 1;    break;   }  }    if(found)  {   break;  }    len --; }  /* if no str was found, just step forward, but cannot beyond label */ if(len < min_len && str < label) {  str ++;  goto retry; }  /* if no str was left, show all the str */ if(str >= label) {  show_buffer(index);  return; }  data = (char*) malloc(len + 1); if(NULL == data) {  return; }  data[len] = '\0'; memmove(data, str, len); buffer[index] = data;  _process_segment(str + len, index + 1); free(data); buffer[index] = NULL;}void process_segment(char* str)int length; if(NULL == str) {  return; }  length = strlen(str); if(length > LENGTH) {  return; }  _process_segment(str, 0);}void segment_init(){ min_len = find_min_length(); max_len = find_max_length(); memset(buffer, 0sizeof(buffer));}int main(int argc, char* argv[]){ segment_init(); process_segment("北京 大學 日本"); return 1;} 

    程式碼中最複雜的函式其實就是_segment_process這個函式,中間涉及的情況比較多,可以簡單介紹一下:

    (1)分詞的時候建議考慮一下當前詞庫的最小長度和最大長度,可以避免不必要的比較;

    (2)分詞如果查詢失敗,跳過從下一個位元組開始繼續查詢;

    (3)函式採用了遞迴的方法,注意函數出口;

    (4)函式編寫的時候注意buffer堆疊的浮動處理;

    (5)注意函式中判斷條件的依據和原因。

    (6)這裡dic的詞庫數量太少,要想利用process_segment進行分詞處理,詞庫數量必須足夠多。