搜尋引擎的那些事(中文分詞)
【 宣告:版權所有,歡迎轉載,請勿用於商業用途。 聯絡信箱: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 = 0; for(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 = 0; while(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, 0, sizeof(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進行分詞處理,詞庫數量必須足夠多。