题目:
Phone List |
Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) |
Total Submission(s): 239 Accepted Submission(s): 96 |
Problem Description Given a list of phone numbers, determine if it is consistent in the sense that no number is the prefix of another. Let’s say the phone catalogue listed these numbers: |
Input The first line of input gives a single integer, 1 <= t <= 40, the number of test cases. Each test case starts with n, the number of phone numbers, on a separate line, 1 <= n <= 10000. Then follows n lines with one unique phone number on each line. A phone number is a sequence of at most ten digits. |
Output For each test case, output “YES” if the list is consistent, or “NO” otherwise. |
Sample Input 2 3 911 97625999 91125426 5 113 12340 123440 12345 98346 |
Sample Output NO YES |
Source 2008 “Insigma International Cup” Zhejiang Collegiate Programming Contest - Warm Up(3) |
Recommend lcy |
题目分析:
前一篇博客介绍了这道题的非Trie做法。那么这一篇介绍一下这道题的设计初衷:Trie的基本使用——在前缀匹配中的应用。
以下介绍一下Trie的基本知识点(个人理解,请各位批判地接受)。
1、性质
1)根节点不包含任何字符
2)某一结点的所有子节点包含的字符都不相同。
3)从根节点开始到某一结点所经过的路径就是到该节点为止所能形成的单词。
2、基本操作
1)添加:
假设存在字符串str,Trie树的根结点为root。i=0,p=root。
1)取str[i],判断p->next[str[i]-97]是否为空,若为空,则建立结点temp,并将p->next[str[i]-97]指向temp,然后p指向temp;若不为空,则p=p->next[str[i]-97];
2)i++,继续取str[i],循环1)中的操作,直到遇到结束符‘\0‘,此时将当前结点p中的isStr置为true。
void insert(Trie* root,char* s){ if(root == NULL || s == ‘\0‘){ return ; } Trie* p = root; while(*s != ‘\0‘){ if(p->next[*s-‘a‘] == NULL){ Trie* temp = malloc(sizeof(Trie)); int i; for(i = 0 ; i < MAX ; ++i){ temp->next[i] = NULL; } temp->isStr = false; p->next[*s-‘a‘] = temp; p = p->next[*s-‘a‘]; }else{ p = p->next[*s-‘a‘]; } s++; } p->isStr = true; }
2)查询:
假设要查找的字符串为str,Trie树的根结点为root,i=0,p=root
1)取str[i],判断判断p->next[str[i]-97]是否为空,若为空,则返回false;若不为空,则p=p->next[str[i]-97],继续取字符。
2)重复1)中的操作直到遇到结束符‘\0‘,若当前结点p不为空并且isStr为true,则返回true,否则返回false。
bool search(Trie* root,char* s){ Trie* p = root; while(p != NULL && *s != ‘\0‘){ p = p->next[*s-‘a‘]; s++; } return (p != NULL && p->isStr); }
3)删除:
以递归的形式进行删除。
void del(Trie* root){ int i; for(i = 0 ; i < MAX ; ++i){ if(root->next[i] != NULL){ del(root->next[i]); } } free(root); }
3、实现的基本思路
1)从根节点开始一次搜索
2)去除关键词的第一个字符,并根据该字符选择相应的子树,然后转到相应的子树中去继续搜索
3)不断地重复2)操作指导完成搜索
4)在某个节点处,如果关键词的所有字符已经被取出。则读出该节点的附加信息。
代码如下:
/* * c2.cpp * * Created on: 2015年3月7日 * Author: Administrator */ #include <iostream> #include <cstdio> using namespace std; const int MAX = 10;//每个节点的孩子节点数 bool flag;//用于标记是否存在一个号码是另外一个号码的前缀的情况 typedef struct TrieNode{//字典树Trie结点 bool isStr;//用于标记到该节点是否形成单词 struct TrieNode* next[MAX];//每个节点的孩子节点 }Trie; /** * 字典树(前缀树)Trie的插入操作 * 将字符串s插入根为root的字典树中 */ void insert(Trie* root,char* s){ if(root == NULL || s == ‘\0‘){//如果这个字典树为空||需要插入的字符串为空 return ;//直接返回 } Trie* p = root; while(*s != ‘\0‘){//顺序遍历要插入的这个字符串 if(p->next[*s-‘0‘] == NULL){//如果目前这个孩子节点还不存在 //则新建该节点 Trie* temp = (Trie*)malloc(sizeof(Trie)); int i; for(i = 0 ; i < MAX ; ++i){ temp->next[i] = NULL; } temp->isStr = false; p->next[*s-‘0‘] = temp;//将p中的孩子节点指向新建的这个temp结点 p = p->next[*s-‘0‘];//更新当前在字典树中查找到的结点 }else{ p = p->next[*s-‘0‘];//更新当前在字典树中查找到的结点 } if(p->isStr == true){//如果当前查找到的结点是之前某一个单词的结束结点,则表明存在某一个号码是另外一个号码的前缀的情况 flag = true;//将flag标记为true.表明存在某一个号码是另外一个号码的前缀的情况 return ;//已经能得到结果.返回 } //如果能继续执行以下代码,表明到当前结点为止,不存在号码是另外一个号码的前缀的情况. s++;//继续遍历下一个节点 } p->isStr = true;//字符串已经插入完毕.将该节点的isStr标记为true,表明在这里可以形成一个号码 int i; for(i = 0 ; i < MAX ; ++i){//遍历该节点的所有子节点 if(p->next[i] != NULL){//如果该子节点存在. flag = true;//说明该号码是另外一个号码的前缀.将flag标记为true break;//跳出循环 } } } /** * 字典树(前缀树)Trie的查找操作 * 如果以root为根的字典树中存在s字符串.则返回true, * 否则返回false */ bool search(Trie* root,char* s){ Trie* p = root; while(p != NULL && *s != ‘\0‘){//如果当前遍历到的结点不为NULL&&字符串s还没有遍历完 //则继续遍历 p = p->next[*s-‘0‘]; s++; } return (p != NULL && p->isStr);//判断结束结点是否存在&&是否在该节点出形成一个号码 } /** * 字典树(前缀树)Trie的删除操作 * */ void del(Trie* root){ int i; for(i = 0 ; i < MAX ; ++i){//遍历该节点的所有孩子节点 if(root->next[i] != NULL){//如果该孩子节点不为NULL del(root->next[i]);//递归删除该孩子节点的孩子节点 } } free(root);//释放空间 } int main(){ int t; scanf("%d",&t); char s[11]; while(t--){ flag = false; //初始化操作 Trie* root = (Trie*)malloc(sizeof(Trie)); root->isStr = false; int i; for(i = 0 ; i < MAX ; ++i){ root->next[i] = NULL; } int n; scanf("%d",&n); for(i = 0 ; i < n ; ++i){ scanf("%s",s); if(flag == false){//如果当前还不存在某一个号码是另外一个号码的前缀的情况 insert(root,s); } } if(flag == true){ printf("NO\n"); }else{ printf("YES\n"); } del(root);//释放空间。如果在这里不释放空间,会导致内存激增而最后MLE } return 0; }