引言:昨天写了一个简单的通过字典树来索引比较大的字母集合的程序。通过字典树,确实能够大大减少查询时间,是一种不错的字母表的匹配方案。这里我就拿出来分享一下。(ps:英文单词集大概有35W+ 条记录,数据量确实不小,在操作中为了简化,去除了英文单词中的 " ' " "-" 等等,作为字典树,必须以26 个英文字母作为树的子节点的索引。)
看看字典树的结构定义:
typedef struct _dict_tree_ { struct _dict_tree_ * dt[TREENODENUM]; char c ; char flag ; }DT ;
dt 指针指向的是通过字母转化的子节点,c可以忽略。
flag后面会提到。
这里只要把字典树看做一个26叉树就可以。
比如一个英文单词:banana 首先通过b 转化为ASCII index = b - 97 ;
把整个单词看做字符串, index = str[i] - 97 ;
然后通过遍历字符串,来构建这棵树、
字符串大小写转换:
static int toSmall( char *str ) { if( str == NULL ) return -1 ; int i = 0 ; while( str[i] ) { if( str[i] <= 'Z' && str[i] >= 'A') str[i] = str[i] | 0x20 ; i++; } return 1 ; }
简单的宏定义:
#define TREENODENUM 27 #define OCCUPY 0x0001 #define EMPTY 0x0000
上述的2 ,3 宏就是字典树结构体里面的flag 的值
创建一棵树:实际上这里只是创建一个根节点
DT * createTree() { DT * retdt = (DT *)malloc( sizeof(DT) ); int i = 0 ; for( ; i < TREENODENUM ; i++ ) { retdt->dt[i] = NULL ; retdt->flag = EMPTY ; } return retdt ; }
插入一个单词到树:
int insert( DT * t , char *str ) { int i = 0 ; int j = 0 ; int index = 0 ; if( t == NULL ) return -1; if( toSmall(str) == -1 ) { printf( "toSmall \n"); return -1; } DT *pt = t ; int len = strlen(str); while( i < len ) { index = str[i] - 97 ;
/*通过 index 来找到子节点*/ if( pt->dt[index] == NULL ) { pt->dt[index] = ( DT *)malloc( sizeof( DT) ); pt->dt[index]->c = str[i] ; pt->dt[index]->flag = EMPTY ;
for( j = 0 ; j < TREENODENUM ; j++ ) { pt->dt[index]->dt[j] = NULL ; } } pt = pt->dt[index] ; i++; } pt->flag = OCCUPY ; return 1; }
在这里我们可以看到flag 标志位的作用了,作为字典树,用以表示一个单词在树中的结束,也就是说,比如
abuse 这个单词,如果不在插入最后一个字符 e 的时候表示结束,那么我们在查找这个单词的过程中,abu 也是存在
于字典中的,故需要一个标示来表明这个可以是结尾,所以当查询的时候如果节点为空或者标志位为 EMPTY , 表示这
个单词不在树中,可能难以理解,不过仔细思考以后可以理解。
查找一个单词是否在树中:
int findstr( DT * t , char *str ) { int i = 0 ; int j = 0 ; int index = 0 ; if( t == NULL ) return -1; if( toSmall(str) == -1 ) { printf( "toSmall \n"); return -1; } DT *pt = t ; int len = strlen(str); while( i != len ) { index = str[i] - 97 ; if( pt->dt[index] == NULL ) return 0 ; pt = pt->dt[index] ; i++; } if( pt->flag == OCCUPY ) return 1; else return 0; }
当然,我们需要从一个buff中批量的将单词插入树中,实际上顶部的节点的复用度是很高的。
int puttodic( DT * t ,char * buf ) { char lword[1024]; char *p = buf ; int i = 0 ; while( *p == '\n' ) p++ ; while( *p ) { if( ( *p > 'z' || *p < 'a') && *p !='\n' ) { while( *p != '\n' ) { p++ ; } memset(lword,0,40); i = 0 ; p++; } if( !(*p) ) break ; if( *p == '\n' ) { lword[i+1] = '\0' ; insert(t,lword); memset(lword,0,40); i = 0 ; p++; } else { if( *p <='z' && *p >= 'a' ) { lword[i++] = *p ; p++; } else { memset(lword,0,40); i = 0 ; p++; } } } }
在这里,博主过滤了很多非标准的英文单词形式,如果需要可以做相应的改变。
这里的buffer 来自:
char *buf = (char *)malloc( 1024*1024*30); memset(buf,0,1024*1024*30); int fd = open("Dict.txt",O_RDONLY); read(fd,buf,1024*1024*30);
测试代码:
1 #ifdef _DT_UNIT_TEST_ 2 int main() 3 { 4 5 DT * mytree = createTree(); 6 7 char *buf = (char *)malloc( 1024*1024*30); 8 memset(buf,0,1024*1024*30); 9 int fd = open("Dict.txt",O_RDONLY); 10 read(fd,buf,1024*1024*30); 11 //printf("%s",buf); 12 13 puttodic( mytree,buf ); 14 fprintf(stderr,"%d\n",findstr(mytree,"abuse")); 15 fprintf(stderr,"%d\n",findstr(mytree,"banana")); 16 fprintf(stderr,"%d\n",findstr(mytree,"diandian")); 17 fprintf(stderr,"%d\n",findstr(mytree,"abus")); 18 19 close(fd); 20 free(buf); 21 return 0; 22 } 23 24 25 #endif
Dict.txt 可以从Linux 的 /usr/share/dict 目录导出,Debian体系可能没有,我的Ubuntu就没有。