N-ary Trie的实现与分析(字典树)

实现功能

初始化文档(默认文档中全是小写字母,并且无重复单词),将内部单词存入字典树(每行一个单词),实现以下功能:

  1. 查找单词(输出单词行号)
  2. 查找前缀(输出前缀行号们)
  3. 删除单词(在字典树中删除单词,文档中原始结构不变)
  4. 插入单词(在字典树中插入单词,原始文档结构不变,默认行号是最后一行)
  5. 按序输出单词(输出仍然在字典树中的单词,按照字典序)

结构

字典树使用链表与数组结合的方式实现,父子关系使用链表,儿子节点之间的关系使用数组。

注意,删除操作只是在字典树中删除对应的单词,其他单词在原始文档中对应的行号不变。

完整代码

/*
 * 实验名称:N-ary Trie
 * 作者:龙征天
 * 环境:MacOs
 * 编译器:CLion
 */
//#pragma GCC optimize(2)
//#pragma G++ optimize(2)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <algorithm>
#include <queue>
#include <vector>
#include <fstream>
using namespace std;

struct TreeNode{
    TreeNode(){
        rows.clear();
        endrow=0;
        for (int i=0; i<26; i++) child[i]=nullptr;
    }
    TreeNode* child[26];//固定26个孩子元素
    vector<int> rows;
    int endrow;
};
class LinkedTree{
public:
    LinkedTree(){ root=new TreeNode; rows=0; }
    ~LinkedTree(){}
    void init(const string& _file);
    int getRows(){return rows;};
    TreeNode* getRoot(){return root;};
    void insert(const string& _s,int _row);//插入一个单词
    bool erase(const string& _s);//删除一个单词
    void dfserase(const string& _s,int index, TreeNode* &a, int delrow);//返回值表示类型,1为分支类型,2为前缀类型
    int FindWord(const string& _s);//查找完整单词的行号
    vector<int> FindPrefix(const string& _s);//查找前缀的行号(包含单词)
    void traverse(TreeNode *node,string _s,int index);
private:
    TreeNode *root;
    int rows;
};
void LinkedTree::init(const string& _file){
    ifstream fin(_file);
    if(!fin.is_open()){
        perror("Error: "); exit(0);
    }
    string s;
    while(fin>>s) insert(s,++rows);
    printf("初始化成功!\n");
}
void LinkedTree::insert(const string& _s,int _row){
    TreeNode *currentNode=root;
    for (int i=0; i<_s.size(); i++){
        if(currentNode->child[_s[i]-'a']==nullptr) currentNode->child[_s[i]-'a']=new TreeNode;
        currentNode->child[_s[i]-'a']->rows.push_back(_row);
        currentNode=currentNode->child[_s[i]-'a'];
    }
    currentNode->endrow=_row;
}
int LinkedTree::FindWord(const string& _s){
    TreeNode *currentNode=root;
    for (int i=0; i<_s.size(); i++){
        if(currentNode->child[_s[i]-'a']==nullptr) return -1;
        currentNode=currentNode->child[_s[i]-'a'];
    }
    if(currentNode->endrow) return currentNode->endrow;
    else return -1;
}
vector<int> LinkedTree::FindPrefix(const string& _s){
    TreeNode *currentNode=root;
    for (int i=0; i<_s.size(); i++){
        if(currentNode->child[_s[i]-'a']==nullptr) return vector<int>{-1};
        currentNode=currentNode->child[_s[i]-'a'];
    }
    return currentNode->rows;
}
void LinkedTree::dfserase(const string &_s, int index, TreeNode* &a, int delrow){//最后一个参数要删除的行号
    if(index==_s.size()-1) {
        auto it=find(a->rows.begin(),a->rows.end(),delrow);
        if(it!=a->rows.end()) a->rows.erase(it);
        a->endrow=0;
        return;
    }

    dfserase(_s,index+1,a->child[_s[index+1]-'a'],delrow);

    if(a->child[_s[index+1]-'a']->rows.empty()){//当前节点的儿子没有儿子,这是分支点
        delete a->child[_s[index+1]-'a'];
        a->child[_s[index+1]-'a']=nullptr;
        auto it=find(a->rows.begin(),a->rows.end(),delrow);
        if(it!=a->rows.end()) a->rows.erase(it);
        //if(a->endrow==delrow) a->endrow=0;
        return;
    }
    else {//进入前缀情况
        auto it=find(a->child[_s[index+1]-'a']->rows.begin(),a->child[_s[index+1]-'a']->rows.end(),delrow);
        if(it!=a->child[_s[index+1]-'a']->rows.end()) a->child[_s[index+1]-'a']->rows.erase(it);
        //if(a->child[_s[index+1]-'a']->endrow==delrow) a->child[_s[index+1]-'a']->endrow=0;
        return;
    }
}
bool LinkedTree::erase(const string& _s){
    int temp=FindWord(_s);
    if(temp==-1) return false;//如果不存在这个字符串,返回false
    dfserase(_s,-1,root,temp);
//    auto it=find(root->child[_s[0]-'a']->rows.begin(),root->child[_s[0]-'a']->rows.end(),temp);
//    if(it!=root->child[_s[0]-'a']->rows.end()) root->child[_s[0]-'a']->rows.erase(it);
    return true;
}
void LinkedTree::traverse(TreeNode *node,string _s,int index){//测试用,遍历整棵树
    if(node->endrow){
        for (int i=0; i<index; i++) printf("%c",_s[i]);
        printf("\n");
    }
    for (int i=0; i<26; i++){
        if(node->child[i]!=nullptr){
            _s[index]=char(i+'a');
            traverse(node->child[i],_s,index+1);
        }
    }
}
int main(){
    LinkedTree trieTree;
    printf("请输入您想要索引化的文档目录绝对路径:");
    string file; cin>>file;
    trieTree.init(file);//初始化
    while(true){
        printf("请输入需要进行的操作:1表示查找前缀,2表示查找单词,3表示插入新的单词,4表示删除单词,5表示按序输出单词,6表示退出程序。\n");
        printf("请输入:");
        int op; scanf("%d",&op);
        if(op==1){
            printf("请输入你想要在字典树中查找的前缀:");
            string s; cin>>s;
            vector<int> tempvector=trieTree.FindPrefix(s);
            if(tempvector[0]==-1) printf("不存在此前缀!\n");
            else {
                printf("出现此前缀的单词所在的行号为:");
                for (auto i:tempvector) printf("%d ",i);
                printf("\n");
            }
        }
        else if(op==2){
            printf("请输入你想要在字典树中查找的单词:");
            string s; cin>>s;
            int x=trieTree.FindWord(s);
            if(x==-1) printf("不存在此单词!\n");
            else printf("此单词出现的行号为:%d\n",x);
        }
        else if(op==3){
            printf("请输入你想要在字典树中插入的单词,默认行号为最后一行:");
            string s; cin>>s;
            trieTree.insert(s,trieTree.getRows());
            printf("插入成功!\n");
        }
        else if(op==4){
            printf("请输入你想要在字典树中删除的单词,注意,删除后,源文件中仍然存在,请输入:");
            string s; cin>>s;
            if(trieTree.erase(s)) printf("删除成功!\n");
            else printf("删除失败!\n");
        }
        else if(op==5) {
            string s; s.clear();
            trieTree.traverse(trieTree.getRoot(),s,0);
        }
        else if(op==6) {
            printf("谢谢使用!再见!\n"); break;
        }
    }
    return 0;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章