基于字典树原理对二进制文件进行广度优先搜索实现模糊匹配

背景记录成长

开博客的第一篇文章便是字典树实现模糊匹配,那个时候提到会把字典树导入二进制文件后进行搜索

优缺点比较:

对字典树的深搜实现模糊匹配:

缺点:每次启动程序需要加载一颗字典树,浪费时间,浪费空间。
优点:程序较为简单,实现起来相对容易

对二进制文件的广搜实现模糊匹配:

缺点:代码繁琐,难以实现,设计二进制文件的数据结构极为难难难。
优点:设计的数据结构较优等时省时间,空间,线下实现,程序直接可以用。

就目前来说设计了一款比较low的二进制文件的数据格式

这里写图片描述

这款数据结构的设计花了我一个周的时间,虽然很low,但是要基于自己的能力去设计,考虑的因素很多,设计完以后怎么在二进制文件上进行搜索,包括二分,广度优先搜索,考虑到目前自己的能力只能在结构相等的情况去进行指针的偏移,所以这个结构设计下来到最终的实现,亲测10000组地区名称的数据,平均每组数据花费空间52B,有点难以接受,如果是万亿级别的数据(还是有点糟糕),后续会寻求师傅的帮助进行优化。预计还有一个月的时间。
实现对我自己设计的结构的搜索也锻炼了,提升了自己很多方面的能力,下面就我这种实现贴出讲解及代码。

offsize 即 取代字典树里结点的指针的功能,通过偏移offsize找到儿子结点。
idoffsize 由于文件最前面存id可以省去非常多的结点不用存8bsize的id,同时使得搜索的结构对称。

一下代码没有任何依赖,单独的工程,可在vs上运行。

有关于原始字典树的代码第一篇文章已经有了,在这里就不贴出来了。

充当迭代器的struct,用于记录搜索中间过程。

//充当迭代器的作用
//typedef struct SBinaryNode* pSBinaryNode;
struct SBinaryNode
{
    char pNamevalue;
    int pNodesonoff;
    int pNodeidoff;
    int pFirstchile;
    SBinaryNode(){
        pNodesonoff = -1;
        pNodeidoff = -1;
        pNamevalue = 'a';
        pFirstchile = -1;

    }
};

在存二进制文件之前,要计算好的offsize,广搜一遍记录id顺序



第一个儿子占10个字节,多一个字节记录父节点的儿子数目,便于搜索
所以这里结点设计出一个pBrotherNum,第一个儿子记录父结点的所有
儿子数量,其余的全部为0
void 
CTree::IdentFirstChild(pSTreeNode pNode)
{
    if (pNode == NULL) {
        return;
    }
    queue<pSTreeNode> NodeQueue;
    NodeQueue.push(pNode);

    while (!NodeQueue.empty()) {
        pSTreeNode Node_temp = NodeQueue.front();
        NodeQueue.pop();
        if (0 < Node_temp->pChildList.size()) {

            for (int i =0 ; i < Node_temp->pChildList.size(); ++i) {
                if (i == 0) {
                    Node_temp->pChildList[i]->pBrotherNum = Node_temp->pChildList.size();
                }

                else {
                    Node_temp->pChildList[i]->pBrotherNum = 0;
                }

                NodeQueue.push(Node_temp->pChildList[i]);
            }   

        }

    }
}

计算偏移,也就是建立所有结点在二进制文件里的物理关系,
方便认亲(哈哈哈),方便搜索
void 
CTree::IdentNumber(pSTreeNode pNode)
{
    if (pNode == NULL) {
        return;
    }

    long long m_Index = 0;
    queue<pSTreeNode> NodeQueue;
    NodeQueue.push(pNode);

    while (!NodeQueue.empty()) {
        pSTreeNode Node_temp = NodeQueue.front();
        if (pRoot != Node_temp) {
            if (Node_temp->pBrotherNum > 0) {
                m_Index += FIRSTCHILDV;
            }
            else {
                m_Index += NFIRSTCHILDV;
            }
            Node_temp->m_IdenxNum = m_Index;
        }
        NodeQueue.pop();
        if (0 < Node_temp->pChildList.size()) {

            for (int i =0 ; i < Node_temp->pChildList.size(); ++i) {

                NodeQueue.push(Node_temp->pChildList[i]);
            }   

        }

    }
}

预处理之后开始写文件


写文件的逻辑是光搜字典树按层次存放结点,文件分为两部分,
一部分是结点的值极逻辑关系,另一部分是结点的value  ID值
void 
CTree::WriteNodeInfo(pSTreeNode pNode)
{
    if (pNode == NULL) {
        return;
    }

    int pNodeOff;
    int pNodeIdOff = 0;

    fstream iofile("NodeInfo.dat",ios::in|ios::out|ios::binary);
    int pNodeIdCount = GetNodeIdOffsize();
    iofile.seekg(LONGLONGV * pNodeIdCount + INTVALUE,ios::beg);//偏移之后写Node信息

    queue<pSTreeNode> NodeQueue;
    NodeQueue.push(pNode);

    while (!NodeQueue.empty()) {
        pSTreeNode Node_temp = NodeQueue.front();
        if (pRoot != Node_temp) {
            if (Node_temp->pBrotherNum > 0) {
                iofile.write((char *)&Node_temp->pBrotherNum, CHARVALUE);
            }
            iofile.write((char *)&Node_temp->namevalue, CHARVALUE);

            if (0 < Node_temp->pChildList.size()) {
                pNodeOff = Node_temp->pChildList[0]->m_IdenxNum - FIRSTCHILDV;
            }
            else {
                pNodeOff = 0;
            }

            iofile.write((char *)&pNodeOff, INTVALUE);
            if (Node_temp->id > 0) {
                pNodeIdOff++;
                iofile.write((char *)&pNodeIdOff, INTVALUE);
            }
            else {
                int tmp_Off = 0;
                iofile.write((char *)&tmp_Off, INTVALUE);
            }
        }

        NodeQueue.pop();
        if (0 < Node_temp->pChildList.size()) {

            for (int i =0 ; i < Node_temp->pChildList.size(); ++i) {

                NodeQueue.push(Node_temp->pChildList[i]);
            }   
        }
    }   

    iofile.seekg(0, ios::beg);

    iofile.write((char *)&pNodeIdCount, INTVALUE);

    pSTreeNode Node_temp = GetTreepRoot();
    //queue<pSTreeNode> NodeQueue;
    NodeQueue.push(Node_temp);

    while (!NodeQueue.empty()) {
        pSTreeNode Node_temp = NodeQueue.front();
        if (pRoot != Node_temp) {
            if (Node_temp->id > 0) {
                iofile.write((char *)&Node_temp->id, LONGLONGV);
            }
        }
        NodeQueue.pop();
        if (0 < Node_temp->pChildList.size()) {
            for (int i =0 ; i < Node_temp->pChildList.size(); ++i) {
                NodeQueue.push(Node_temp->pChildList[i]);
            }   
        }
    }
    iofile.seekg(0, ios::beg);
    iofile.close();
}   

二分,最好传过来一段buff,省的对文件频繁的seek操作
SBinaryNode
CTree::SearchBinaryThVal(int idx, char* buff_tmp, char pNum)
{


    int Listleft = 0;
    int Listright = pNum;
    int Listmid = 0;
    char pValue_tem;
    SBinaryNode Node_tmp;


    while (Listleft < Listright) {
        Listmid = (Listleft + Listright) / 2;
        buff_tmp += Listmid * ONESTRUCT;
        pValue_tem = *(char*) buff_tmp;

            if (idx < pValue_tem) {
                buff_tmp -= Listmid * ONESTRUCT;
                Listright = Listmid;
            }

            else if (idx > pValue_tem) {
                buff_tmp -= Listmid * ONESTRUCT;
                Listleft = Listmid + 1;
            }

            else {
                Node_tmp.pNamevalue = pValue_tem;
                buff_tmp += CHARVALUE;
                Node_tmp.pNodesonoff = *(int*)buff_tmp;
                //Node_tmp.pNodesonoff = Node_tmp.pNodesonoff - (pNum - Listmid -1) * ONESTRUCT;//易出错,与现在的位置差
                buff_tmp += INTVALUE;
                Node_tmp.pNodeidoff = *(int*)buff_tmp;
                return Node_tmp;    
            }
    }
    return Node_tmp;

}
 模糊匹配,遍历每个字母,对最后一个字母广搜,求出所以符合
 条件的地区名称

void
CTree::BinarySearch(char* name, vector<long long>&nodeids, int pSonOffsize)
{   

    int idx = 0;  
    if(NULL == name) {
        return;
    }

    fstream iofile("NodeInfo.dat",ios::in|ios::out|ios::binary);    
    iofile.seekg(0, ios::beg);
    /*int pNodeIdCount = 0;
    iofile.read((char *)&pNodeIdCount, INTVALUE);
    iofile.seekg(LONGLONGV * pNodeIdCount, ios::cur);*/
    nodeids.clear();    
    //vector<int>Id;
    long long id;
    queue<SBinaryNode> NodeQueue;


    int nameLen = strlen(name);
    for (int i = 0; i < nameLen; i++) {
        //Id.clear();
        nodeids.clear();
        idx = get_index(name[i]);  
        if(IDXERR == idx) {
            return;  
        }

        iofile.seekg(INTVALUE + GetNodeIdOffsize() * LONGLONGV + pSonOffsize);

        char pNum;
        iofile.read((char *)&pNum, CHARVALUE);

        char *buff_tmp = new char[pNum * ONESTRUCT];
        iofile.read(buff_tmp, pNum * ONESTRUCT);

        SBinaryNode Node_tmp = SearchBinaryThVal(idx, buff_tmp, pNum);
        if (NULL != buff_tmp) {
            delete buff_tmp;
            buff_tmp = NULL;
        }

        if (Node_tmp.pNodeidoff < 0) {
            return ;
        }

        if (Node_tmp.pNodeidoff > 0) {
            iofile.seekg(INTVALUE + (Node_tmp.pNodeidoff - 1) * LONGLONGV);
            iofile.read((char*)&id, LONGLONGV);
            nodeids.push_back(id);
        }
        int position_tmp = 0;
        int id_tmp;
        int sonoff_tmp;

        if (i == nameLen - 1) {

            NodeQueue.push(Node_tmp);

            while (!NodeQueue.empty()) {

                SBinaryNode Node_temp = NodeQueue.front();
                NodeQueue.pop();
                iofile.seekg(INTVALUE + GetNodeIdOffsize() * LONGLONGV + Node_temp.pNodesonoff);
                char pNum;
                iofile.read((char *)&pNum, CHARVALUE);

                char *Buff = new char[pNum * ONESTRUCT];
                iofile.read(Buff, pNum * ONESTRUCT);

                if (0 < pNum) {
                    position_tmp = 0;
                    for (int j = 0; j < pNum; j++) {


                        Buff += 1;
                        sonoff_tmp = *(int*)Buff;
                        if (sonoff_tmp > 0) {
                            SBinaryNode node;
                            node.pNodesonoff = sonoff_tmp;
                            NodeQueue.push(node);
                        }
                        Buff += 4;
                        id_tmp = *(int*)Buff;
                        if (id_tmp > 0) {
                            iofile.seekg(INTVALUE + (id_tmp - 1) * LONGLONGV);
                            iofile.read((char*)&id, LONGLONGV);
                            nodeids.push_back(id);
                        }
                        Buff += 4;
                    }
                }

                if (NULL != Buff) {
                    Buff -= pNum * ONESTRUCT;
                    delete Buff;
                    Buff = NULL;
                }
            }
        }
        pSonOffsize = Node_tmp.pNodesonoff;
    }
}

预计一个月后发布,最终的算法及设计,优化程度会大大的提升,可能会有分块的思想(我所不会的),记录下自己的设计,自己的成就~

以上,欢迎留言交流~ 字典树的设计及实现在第一篇文章~

猜你喜欢

转载自blog.csdn.net/breakpoints_/article/details/80853357
今日推荐