利用多叉树(字典树)实现简单的快速搜索

15 篇文章 0 订阅
7 篇文章 0 订阅

今晚在公众号上看到一条题:

 

 

看到题目第一时间想到树,而且是多叉树。为什么呢?

       首先说说为何不选择顺序表,我们来试想想,如果500万个单词放在顺序表上,不加索引而且乱序,那么搜索一个关键词为开头的单词的时间按最差算要500万次比较,简直疯狂。但是如果先按首字母a~z排序一次,并记住每个首字母的第一个单词在表格第几个位置,效率又要好些,如果第二个字母再排序一些再做索引,又好些。可是这样插入单词的效率就太低了,所以顺序表无论如何并不是一个好的选择。

 

      然后我们换个想法,我们可以先把单词按照字母为单位分割,然后第1个字母在树的第一层搜索,若不存在该字母则添加该字母为本层的兄弟节点,存在该字母就选择第2个字母,跳到下一层的首节点,再逐个搜索,若不存在该字母则添加该字母为本层的兄弟节点,存在该字母就选择第3个字母,跳到下一层的首节点,再逐个搜索……,如此类推,直到搜索或添加最后一个字母时,把该节点标记为“单词最后字母”,即完成添加一个单词的任务。然后每个单词均进行一次这个行为,就完成了单词树,不仅不需要存储单词中同样的部分,而且在搜索时,最差情况仅 =(该语言字母数 * 单词长度),例如搜索一个10个字母构成的英文单词,最差只需要搜索26*10=260次,无论增加多少个单词也只需要这么多次,效率比搜索一个乱序表高好几个数量级了,也使得单词表的排序变得十分简单。

 

 

具体C语言实现代码如下:

字母单元定义:

typedef struct vocalUnit VocalUnit;

struct vocalUnit {
	char isWord; //如果深搜的时候发现是单词的时候根据这个标记进行显示 
	char c;  //保存的字母 
	struct vocalUnit *nextLayerHead;  //以本节点开始的下一层的首节点 
	struct vocalUnit *nextBrother; //同一层的邻居字母 
};

      

 

全部实现代码:

       插入一个单词的大概流程:把单词划分成以字母为单位的数组,第一个字母进入第一层结点进行遍历,没有该字母就增加为该层兄弟,有就选择数组第二个字母,跳到对应字母的子结点头(也就是下一层已存字母),进行刚才的对比操作,直到数组最后一个字母在对应层遍历时,找到该字母的结点或创建了该字母的结点后,标记isWord为1。

         全部单词遍历的流程:新建一个数组构成的简单栈,随后先使用递归进行深搜,每次准备进入下层递归之前记录本层字母,放入栈,layer++,搜索到isWord结点时输出整个栈所有元素拼成一个字符串;然后当前遍历分支到底后,出栈栈顶字母,layer--,然后选择本结点兄弟结点继续深搜,没有就返回上一层重选其他兄弟。反复进行此操作即可遍历完输出所有单词。

         前序搜索:提前通过循环找到对应开头的N个字母对应结点,并把这N个字母提前输入到栈,再layer += N,然后把该结点当成一个根结点输入之前说的单词遍历递归函数即可输出该字母序列开头的所有单词。

#include "stdio.h"
#include "stdlib.h"
#include < sys / types.h > 
#include < sys / stat.h > 
#include < fcntl.h >

/**************ChenJieZhu created at 20181030 18:00***********************/

typedef struct vocalUnit VocalUnit;

struct vocalUnit {
    char isWord; //如果深搜的时候发现是单词的时候根据这个标记进行显示 
    char c;   //保存的字母 
    struct vocalUnit * nextLayerHead; //以本节点开始的下一层的首节点 
    struct vocalUnit * nextBrother; //同一层的邻居字母
};

/**初始化节点**/
void initDictTrees(VocalUnit * unit) {
    unit ->isWord = 0;
    unit ->nextBrother = NULL;
    unit ->nextLayerHead = NULL;
    unit ->c = NULL;
}

/**添加单词到多叉树**/
void addWord(VocalUnit * vHead, char * word) {
    int len = strlen(word);
    int i;
    VocalUnit * cursor = vHead;
    for (i = 0; i < len; i++) {
        char c = word[i];
        char nextBrotherisEmpty = 0;
        while (cursor != NULL) {
            if (cursor->c == NULL) { //如果结点内容为空,即为以下情况之一:1、该字母与该层所有兄弟都对比过之后发现不存在,需要在本层添加  2、在该分支中该字母到此层未出现过,需要保存 
                VocalUnit * newLayerHead = (VocalUnit * ) malloc(sizeof(VocalUnit));
                initDictTrees(newLayerHead);
                cursor->c = c;
                if (i == len - 1) {
                    cursor - >isWord = 1;
                } else { //创建下一层为新字母保存做准备 
                    cursor->nextLayerHead = newLayerHead;
                    cursor = cursor - >nextLayerHead;
                }
                break;
            } else {
                if (cursor->c == c) {
                    if (i == len - 1) { //如果最后一个字母与该层已存在的其中一个字母一样,标记为到此为一个单词 
                        cursor->isWord = 1;
                    } else if (cursor - >nextLayerHead == NULL) { //如果需要遍历下一层,可是不存在下一层,就新建一层 
                        VocalUnit * newLayerHead = (VocalUnit * ) malloc(sizeof(VocalUnit));
                        initDictTrees(newLayerHead);
                        cursor - >nextLayerHead = newLayerHead;
                    }
                    cursor = cursor->nextLayerHead; //预备遍历下一个字母,对应就是多叉树的下一层 
                    break;
                } else { //如果该字母与该层当前兄弟不一致,移动到下一个兄弟进行比较 
                    if (cursor->nextBrother == NULL) { //如果没有下一个兄弟就创建下一个兄弟 
                        VocalUnit * nextBrother = (VocalUnit * ) malloc(sizeof(VocalUnit));
                        initDictTrees(nextBrother);
                        cursor - >nextBrother = nextBrother;
                    }
                    cursor = cursor->nextBrother;
                }
            }

        }
    }
}

int layer = 0;
char content[100];
/**读取树中所有的单词**/
void readVolcalProc(VocalUnit * cursor) {
    if (cursor != NULL) {
        layer++;
        //printf("layer:%d:%c\n", layer, cursor->c);
        content[layer - 1] = cursor - >c;
        if (cursor->isWord) {
            printf("word:%s\n", content);
        }
        readVolcalProc(cursor ->nextLayerHead);
        content[layer - 1] = 0;
        layer--;
        readVolcalProc(cursor ->nextBrother);
    }
}

void readVolcal(VocalUnit * cursor) {
    layer = 0;
    memset(content, 0, 100);
    readVolcalProc(cursor);
}

/**搜索某开头的所有内容**/
void searchProc(VocalUnit * cursor, char * key, int keyLen) {
    while (cursor != NULL) {
        if (key[layer] != cursor->c) {
            cursor = cursor->nextBrother;
        } else {
            content[layer++] = cursor->c;
            if (layer == keyLen && cursor->isWord) { //如果被搜索词发现本身也是一个单词,也显示一下 
                if (cursor - >isWord) {
                    printf("word:%s\n", content);
                }
            }
            cursor = cursor->nextLayerHead;
            if (layer == keyLen) {
                break;
            }
        }
    }
    readVolcalProc(cursor);
}

/**搜索某关键字开头的条目**/
void search(VocalUnit * cursor, char * key, int keyLen) {
    layer = 0;
    memset(content, 0, 100);
    searchProc(cursor, key, keyLen);
}

int main() {
    int in =open("dict.txt", O_RDONLY, S_IRUSR);
    int i = 0;
    char p;
    char input[100];
    int flag;
    //init///
    VocalUnit * vHead = (VocalUnit * ) malloc(sizeof(VocalUnit));
    char * str = malloc(100);
    memset(str, 0, 100);
    initDictTrees(vHead);
    while ((flag = read( in , &p, 1)) > 0) {
        if (p == '\n') {
            addWord(vHead, str);
            memset(str, 0, 100);
            i = 0;
        } else {
            str[i] = p;
            i++;
        }
    }
    /**someTest:**/
    addWord(vHead, "a");
    addWord(vHead, "apple");
    addWord(vHead, "app");
    addWord(vHead, "banana");
    addWord(vHead, "ash");
    addWord(vHead, "123456789");
    printf("Loading finished.\n");
    //readVolcal(vHead);
    do {
        printf("\nplease input what you want to search:");
        scanf("%s", input);
        search(vHead, input, strlen(input));
    } while ( strcmp ( input , "!") > 0);
    return 0;
}

 

 

具体运行效果:

 

代码下载地址:

https://download.csdn.net/download/cjzjolly/10754197

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值