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;
}



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