字典树 c++ 实现

  • Post author:
  • Post category:其他


字典树


又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。
典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),
所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符
串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,
查询效率比哈希树高(摘自百度百科)
好了现在描述我现在要解决的问题:


在一个词库里 我要找出以已知串A为前缀的所有单词.
算法一:
我最先想到的算法是这个样子的:


对词库进行遍历,找出前缀相同的串记录下来.
我对上面算法的评价是: 简单粗暴,容易实现,算法复杂度 o(n) 貌似在接受范围
但是如果单词量巨大,1亿个,不够多?100亿,首先存储就是个问题,进行一个简单的
计算,假设平均每个串只有6个字母 可表示的串个数有大约140亿个(认为区分大小写)


(10^11 * 6 * 8)/(8*1024*1024*1024) = 558GB;
所以这个简单粗暴算法的时空复杂度是线性上升的, 故而需要优化之.
下面我给出
算法二:应用字典树进行实现



思想:


利用树的原理如下图所示


这样的时间复杂度可以降到常数级别,搜索变得十分容易,公共前缀的引入,空间复杂度也


随之减小.



废话不多说 下面给出原代码,可能实现起来看着比较复杂,原因是我实现了一些不必要的链表

操作


lib.cpp

#include "lib.h"
#include <iostream>
#include <vector>
#include <cstring>


#define IsLetter(Letter) ((Letter>=65 && Letter <=90) || (Letter>=97 && Letter<=122))

using namespace std;

// 将不使用模板 因而有一定局限性
// 使用单链表作为基本的存储结构,构造效率比较低下
// 当且仅当链表中节点数据唯一
// 定义链表节点

struct LinkList_Node{
    char NodeData;
    int BIA;
    LinkList_Node *pNext;
    LinkList_Node *pDown;
};

class TrieTree{
    public:
        TrieTree();                                                     // 构造函数
        ~TrieTree();
        vector<char*> WordVector;
        void TrieTree_Insert(char*,int);                                     // 给字典树插入值
        int TrieTree_Find(char*,int,LinkList_Node**);                                          // 指示找到或没有找到
        void TrieTree_CompleteWord(char*,int);                              // 补全单词
		void Vector_Clear();
    private:

        // 关于链表的操作
        LinkList_Node* LinkList_InIt();                                 // 初始化函数

        bool LinkList_Insert(LinkList_Node*,char*,int);                 // 插入函数 在pHead之后插入

        LinkList_Node* LinkList_Find(LinkList_Node*,char,int*);                 // 在链表中寻找变量的索引 从1开始计数                //
        LinkList_Node* LinkList_Find(LinkList_Node*,int);

        bool LinkList_Clear(LinkList_Node *);                               // 释放务必调用头指针
        bool LinkList_Del(LinkList_Node*,char);                             // 通过数据内容删除
        LinkList_Node* LinkList_Push_Back(LinkList_Node*,char);                           // 在链表末尾加入值 返回指向其的指针
        // 关于字典树的操作

        LinkList_Node* PTrieTreeHead;                                       // 树的根节点
        bool TrieTree_Clear();                                              // 释放整棵树
        void TrieTree_Clear(LinkList_Node*);                                // 递归的释放整棵树
        void TrieTree_CompleteWord(char*,int,LinkList_Node*);
        
};

void TrieTree :: Vector_Clear(){
	int len = WordVector.size();
	int i;

	for(i=0;i<len;i++)
		delete[] WordVector[i];
	WordVector.clear();
}


// 初始化一个头指针 指向头节点
LinkList_Node* TrieTree :: LinkList_InIt(){
    LinkList_Node *pHead = new LinkList_Node;
    pHead->pNext = NULL;
    pHead->pDown = NULL;

    return pHead;
}

bool TrieTree :: LinkList_Insert(LinkList_Node *pn,char *Ch_Array,int len){

    if(pn == NULL) return false;

    LinkList_Node *pt = pn,*pv;
    pv = pn->pNext;

    try{
        for(int i=0;i<len;i++){
            pt->pNext = new LinkList_Node;
            pt->pNext->pNext = NULL;
            pt->pNext->NodeData = Ch_Array[i];
            pt = pt->pNext;
        }

        pt->pNext = pv;
    }

    catch(char *str){
        return false;
    }
    return true;
}


// 可以选择调用或者不调用index
// index 是相对位置 我们将会检索pHead指针的数据域
LinkList_Node* TrieTree::LinkList_Find(LinkList_Node *pHead,char ch,int *index=NULL){
    LinkList_Node *pt = pHead;
    int Count = 0;
    while(pt){
        if(pt->NodeData == ch) {
            if(index != NULL) *index = Count;
            return pt;
        }
        Count++;
        pt = pt->pNext;
    }

    if(index != NULL) *index = -1;
    return NULL;
}

// 注意 index是相对位置 相对于 pn
LinkList_Node* TrieTree :: LinkList_Find(LinkList_Node *pn,int index){
    LinkList_Node *pt = pn;
    int Count = 0;
    while(pt != NULL && Count <=index){
        if(Count == index) return pt;
        pt = pt->pNext;
        Count++;
    }

    return NULL;
}

bool TrieTree :: LinkList_Clear(LinkList_Node *pHead){
    LinkList_Node *pt = pHead,*pn;
    try{
        while(pt != NULL){
            pn = pt->pNext;
            delete pt;
            pt = pn;
        }
    }
    catch(char *str){
        return false;
    }
    return true;
}

bool TrieTree :: LinkList_Del(LinkList_Node *pHead,char ch){
    if(pHead == NULL) return false;

    LinkList_Node *pt = pHead->pNext,*pu = pHead;
    while(pt != NULL){
        if(pt->NodeData == ch){
            pu->pNext = pt->pNext;
            delete pt;
            return true;
        }
        pu = pt;
        pt = pt->pNext;
    }

    return false;
}

// 清理这棵树 采用中序遍历的方法

bool TrieTree :: TrieTree_Clear(){
    if(PTrieTreeHead == NULL) return false;
    TrieTree_Clear(PTrieTreeHead);
    return true;
}

void TrieTree :: TrieTree_Clear(LinkList_Node *pt){
    if(pt->pDown != NULL) TrieTree_Clear(pt->pDown);
    if(pt->pNext != NULL) TrieTree_Clear(pt->pNext);
    delete pt;
}

// 初始化一棵字典树
TrieTree :: TrieTree(){
    // 初始化根节点
    // 指向第一个链表的头节点
    PTrieTreeHead = LinkList_InIt();
}

TrieTree :: ~TrieTree(){

    TrieTree_Clear();
   Vector_Clear();
}

LinkList_Node * TrieTree :: LinkList_Push_Back(LinkList_Node* pt, char Ch){
    LinkList_Node *pn;
    while(pt){
        pn = pt;
        pt = pt->pNext;
    }

    pt = pn->pNext = new LinkList_Node;
    pt->pDown = NULL;
    pt->pNext = NULL;
    pt->NodeData = Ch;
    pt->BIA = 0;
    return pt;
}


void TrieTree :: TrieTree_Insert(char *Ch,int len){
    int i;
    LinkList_Node* pt = PTrieTreeHead,*pf;


    for(i=0;i<len-1;i++){
        // 如果没有找到

        pf = LinkList_Find(pt,Ch[i]);
        if(pf == NULL){
            pt = LinkList_Push_Back(pt,Ch[i])->pDown = LinkList_InIt();
            pt->pDown = NULL;
            pt->pNext = NULL;
            pt->BIA = 0;
        }
        else{
            pt = pf->pDown;
        }
    }
	

    // 将最后一个单独处理
    pf = LinkList_Find(pt,Ch[i]);
    if(pf == NULL){
        pf = LinkList_Push_Back(pt,Ch[i]);
        // 务必new一个新链表  后续插入依赖其实现 原则是 要访问到的pDown 不为空
        // 认为每一个元素都有后继 很好的处理了被查询字母长度大于树深度的情况
        pf->pDown = LinkList_InIt();
        pf->pNext = NULL;
        pf->BIA = 0;
    }
    pf->BIA++;

}

int TrieTree :: TrieTree_Find(char *Word,int len,LinkList_Node** Pn = NULL){
    // 根节点
    int i;
    LinkList_Node* pt = PTrieTreeHead;
    for(i=0;i<len-1;i++){
        pt = LinkList_Find(pt,Word[i]);
        if(pt == NULL) {if(Pn != NULL) *Pn = pt; return 0;}
        else {
            pt = pt->pDown;
        }

    }

    // 将最后一个单独处理 避免重新查找的情况
    pt = LinkList_Find(pt,Word[i]);
    if(Pn != NULL) *Pn = pt;

    // 不存在有两种情况  1 这点不存在 2 这点是其他点的子集
    if(pt == NULL || pt->BIA == 0)  return 0;

    return pt->BIA;

}

void TrieTree:: TrieTree_CompleteWord(char* Word,int len){
    LinkList_Node *pt = PTrieTreeHead,*Pn;
    int i;
    // 这是一个 new运算符 注意del的使用

    char *CompleteWord = new char[100];

    TrieTree_Find(Word,len,&Pn);
    if(Pn == NULL) return;

    for(i=0;i<len;i++)
        CompleteWord[i] = Word[i];

     //如果此点有词 加入队列

    char* AddVector = new char[len+1];
    if(Pn->BIA >0){
         for(i=0;i<len;i++)
            AddVector[i] = CompleteWord[i];

            AddVector[i] = '\0';
            WordVector.push_back(AddVector);
    }

    TrieTree_CompleteWord(CompleteWord,i,Pn->pDown);
    delete []CompleteWord;
}

void TrieTree :: TrieTree_CompleteWord(char* CompleteWord,int len,LinkList_Node*pt){
    int i;
    int AddNum = 0;

    if(IsLetter(pt->NodeData) ){
        CompleteWord[len] = pt->NodeData;
        /*
            只为报复社会
            其实可以等价于
            AddNum++;       // 先自增表示此行添加单词
            len += AddNum;  // 表示已添加到补全单词数组中
        */
        len += ++AddNum;
        char* AddVector = new char[len+1];
        if( pt->BIA !=0){
            for(i=0;i<len;i++)
                AddVector[i] = CompleteWord[i];

            AddVector[i] = '\0';
            WordVector.push_back(AddVector);
        }


    }

    if(pt->pDown != NULL)
        TrieTree_CompleteWord(CompleteWord,len,pt->pDown);

    if(pt->pNext != NULL){
        TrieTree_CompleteWord(CompleteWord,len-AddNum,pt->pNext);
    }
}


/*书写入口函数*/
TrieTree t;
int Iterator = 0;
void InsertStr(char* Str,int len){
	t.TrieTree_Insert(Str,len);
}

void CompleteWord(char* Str,int len){
	t.Vector_Clear();

	t.TrieTree_CompleteWord(Str,len);
}


char *ReturnStr(){
	
    t.WordVector;
	if(Iterator <  t.WordVector.size())
		return t.WordVector[Iterator++];
	else
		return NULL;

}




lib.h

#ifndef LIB_H
#define LIB_H
extern "C" char  __declspec(dllexport) *ReturnStr();
extern "C" void  __declspec(dllexport) InsertStr(char*,int);
extern "C" void  __declspec(dllexport) CompleteWord(char*,int);

#endif

dlltest.cpp

#include <stdio.h>
#include <windows.h>
#include <iostream>
#include <time.h>


char Word[26] = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};

typedef char*(*lpReturnStr)(); //定义函数指针类型
typedef void(*lpInsertStr)(char*,int);
typedef void(*lpCompleteWord)(char*,int);

void test(lpInsertStr InsertStr,lpCompleteWord CompleteWord,lpReturnStr ReturnStr){
    int a,b,c,d,e,f;
    char cmd[10];

    for(a=0;a<26;a++)
        for(b=0;b<26;b++)
        for(c=0;c<26;c++)
        for(d=0;d<26;d++)
        {
        char array[6] ={Word[a],Word[b],Word[c],Word[d],'\0'};
        InsertStr(array,4);
    }

    printf("创建完毕");

    scanf("%s",cmd);
    int m = clock();
    CompleteWord(cmd,2);
    int n = clock();

    char* pt;
    pt = ReturnStr();
    while(pt != NULL){
        printf("%s %d",pt,n-m);
        pt = ReturnStr();
    }
}


int main(int argc, char *argv[])
{
	HINSTANCE hDll; //DLL句柄

	lpReturnStr ReturnStr; //函数指针
	lpInsertStr InsertStr;
    lpCompleteWord CompleteWord;


	hDll = LoadLibrary("E:\\MyDll\\dllTest\\Debug\\dllTest.dll");
	if (hDll != NULL)
	{
		ReturnStr = (lpReturnStr)GetProcAddress(hDll, "ReturnStr");
        InsertStr = (lpInsertStr)GetProcAddress(hDll,"InsertStr");
        CompleteWord = (lpCompleteWord)GetProcAddress(hDll,"CompleteWord");




        test(InsertStr,CompleteWord,ReturnStr);

		FreeLibrary(hDll);
	}


	return 0;
}

实验得知查询可以在常数时间完成



版权声明:本文为rongdongzhu1原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。