【字典樹】2828 -> 字典樹

字典樹

先上個定義。

又稱單詞查找樹,Trie樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計,排序和保存大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:利用字符串的公共前綴來減少查詢時間,最大限度地減少無謂的字符串比較,查詢效率比哈希樹高。

字典樹有兩種構建方式,一種是指針構建,一種是數組構建。思路答題一樣不過自我感覺指針好理解,數組好寫好構建。可以先看指針是怎麼做的再來看數組會有不一樣的感覺。都這樣說了就先上指針的做法。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
    int cnt;//判斷是否爲單詞
    struct tree *next[26];
}tree;

int top;
struct tree a[2000000];//記得申請靜態

tree *createtree()//初始化樹
{
    int i;
    tree *treeNode = &a[top++];
    for(i = 0; i < 26; i++)
    treeNode->next[i] = NULL; //節點的next全指向爲NULL
    treeNode -> cnt = 0;
    return treeNode;
}

int Inserttree(tree *ptrRoot, char *str)
{
    int i, index;
    tree *tempNode = ptrRoot;//用以操作樹的節點的指針
    for( i = 0 ;i < strlen(str); i++)
    {
        index = str[i] - 'a';
        if(tempNode->next[index]!=NULL)//也從樹的頭處開始詢問當前字母是否存在
        {
            tempNode = tempNode->next[index]; //存在,進入該字母所在的節點
            continue;
        }
        tempNode->next[index] = createtree();//不存在,先建立這樣一個節點
        tempNode = tempNode->next[index];
    }
    tempNode->cnt++;//這裏循環完成到達單詞結尾,標記爲單詞
    return 0;
}

int SearchTrieTree(tree *ptrRoot, char *str)
{
    int i, index;
    tree *tempNode = ptrRoot;

    for(i = 0 ;i < strlen(str); i++)//遍歷前綴
    {
        index = str[i] - 'a';
        if(tempNode->next[index]!=NULL)//也從樹的頭處開始詢問當前字母是否存在
        {
            tempNode  = tempNode->next[index];//存在進入下一指針
        }
        else
        {
            return 0;//不存在直接返回
        }
    }
    if(tempNode->cnt)//!!!這就是爲什麼要標記單詞結尾因爲可能出現前綴比較相同但只事子串的可能性,比如:absd上找ab前綴相同但並沒有ab這個單詞。
    {
    return tempNode->cnt;
    }
    else
    {
    return 0;
    }
}

int main()
{
    int n,m;
    char s[15];
    while(~scanf("%d %d", &n, &m)&&n&&m)
    {
         top = 0;
        tree *tree = createtree();
        getchar();
        while(n--)
        {
            gets(s);
            Inserttree(tree, s);
        }
        while(m--)
        {
            gets(s);
            int res = SearchTrieTree(tree, s);
            if(res == 1)
                printf("Yes\n");
            else
                printf("No\n");
        }
    }
}

接下來是數組構造,好寫但第一次看真的不好理解。可以手寫一下感受一波。說明下參數 p表示當前節點的地址,j表示字母的通道,數值就是通到鏈接下個頭節點的地址了 關鍵就是這句話。

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#define N 2100000//一定要開大!!!
int str[N][26];//看成N個連續的地址空間 每個空間有26個通道和一個計數 
int num;//模擬連續內存的首地址,是幾無所謂啦,默認0
int mark[N];

void Insert(char *s)
{
    int i, len = strlen(s);
    int p = 0; //類似指針,數值就是地址了
    for(i = 0; i < len; i++)
    {
        int t = s[i] - 'a';
        if(!str[p][t]) str[p][t] = ++num;//0地址的空間作爲root了,判斷當前字母是否存在。不存在,選取下一個空的空間連通,作爲該字母的節點
        p = str[p][t];//進入當前字母鏈接的節點
    }
    mark[p]++;//老樣子標記單詞結尾
}

int find(char *s)//比較和鏈式的沒區別不多BB了
{
    int len = strlen(s);
    int p = 0, i;
    for(i = 0; i < len; i++)
    {
        int t = s[i] - 'a';
        if(!str[p][t]) return 0;
        p = str[p][t];
    }
    return mark[p];
}

int main()
{
    int n, m, i, j;
    char a[55];
    while(~scanf("%d%d", &n, &m))
    {
        if(!n && !m)
            return 0;
        else
        {
            memset(str, 0, sizeof(str));//記得初始化!
            memset(a, '\0', sizeof(a));//記得初始化!!
            memset(make, 0, sizeof(make));//記得初始化!!!
            num = 0;
            while(n--)
            {
                getchar();
                scanf("%s", a);
                Insert(a);
            }
            while(m--)
            {
                getchar();
                scanf("%s", a);
                if(find(a))
                    printf("Yes\n");
                else
                    printf("No\n");
            }
        }
    }
    return 0;
}

參考博客:字典樹(鏈式+數組模擬)–最基礎的算法,最詳細的註釋,寫的真的很好。

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