昨天面试电话中的一道题,题目如下:

1、给你一个姓名的集合,查找你的名字是否在里面出现。

我的回答是用set,把集合中所有的姓名放到set集合中,直接用find查找我的姓名在这个集合里面是否出现。

2、追问,如果要搜索姓氏为叶的人,输入关键字叶,那么会出现所有姓为叶的人,应该如何设计?

当时的回答是,姓为key,名为value,存放到multimap中,使用multimap中的count函数统计key为叶的个数,然后用find函数找到第一个key为叶的指针,使用迭代器从该指针向后查找count个元素,判断这count个元素中是否有姓名为叶x的人。后来想想我的方法其实可以优化的,网上的思路基本上都是采用字典树,但仅针对英文单词。如果把字典树和我的在面试时候的方法结合起来就非常棒了(可惜当时没想到>_<)。

什么是字典树?
Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
  1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
  3. 每个节点的所有子节点包含的字符都不相同。
对于每个单词,如果直接去查找别的单词是否包括它,那么时间复杂度就是O(n^2),如果采用查找公共前缀,例如对于abcd,只用查找以a为前缀的节点假设为x,对于bcd,只用查找x节点中以b为前缀的节点,依次类推。

下面给出一种常见的英文字典树结构体设计:
#define MAX_CHILD 26
struct trie_node
{trie_node(){count = 0;for (int i = 0; i < MAX_CHILD; i++){child[i] = NULL;}}int count;//表示以该节点结束的单词的个数trie_node *child[MAX_CHILD];//存放孩子节点指针的数组
};

对于每个节点都有指向26个英文字母的指针,如果字符串到某个节点(叶子节点)结束了,那么把该节点的count加一,代表从根节点到当前节点路径上出现的字母所组成的字符串出现过一次。如果不存在以该字符串为前缀的字符串,那么该叶子节点上的26个指针均为NULL。比如有字符串ae、b、 b、 bac、c、ca、ceh、ceb、cebg、cebg、f、fi。组成的字典树如下图:
然而,存储中文的时候问题就来了,常见的中文汉字有5W个,如果按照上面的来设计,那么每个节点都要存放5W个节点的指针数组,这样会浪费很多空间,因为对于每个几点的5W个指针只用到了极少部分(其他都为NULL)。
我的设计思路:对于每个节点都有map(或hash_map),键值为前缀汉字(单个),实值为指向后续汉字节点的指针。和英文字典树一样,从根节点到当前节点所经过的汉字连接起来成为某个字符串。对于出现过的字符串,该字符串最后一个汉字所在节点的count加一。时间复杂度O((logn)^2)
例如中文字符串:数字、数据、数据集、数据库、数理化、测试、计量、记事本、计算机、计算器。那中文字典树如下:
使用map还是hash_map?
看情况而定,如果出现姓名特别多,而且普遍比较短(比如中文名一般是两三个汉字),用hash_map,这样整个树基本只要2~3层就能搞定,如果姓名很长,还是用map吧,毕竟每个汉字出现频率低,使用hash_map会浪费太多空间,得不偿失。
代码如下:
#include <string>
#include <map>
#include <vector>
#include <iostream>
#define CH_SIZE 3//汉字大小linux为3,win为2
using namespace std;  struct trie_node
{  trie_node()  {  count = 0;  }  int count;//表示以该汉字结束的字符串个数  map<string, trie_node *> child;//键值为当前汉字,实值为后面汉字节点的指针
};  class trie
{
public:  trie();  ~trie();  void insert_str(string str);//插入字符串  trie_node *search_str(string str);//查询字符串  trie_node *search_str_pre(string str_pre);//查询字符串前缀  void delete_str(string str);//删除字符串  vector<string> get_str_pre(string str);//返回所有前缀为str的字符串  void clear();//清空
private:  void add_str(trie_node *root, string pre_str, vector<string> &ret);    //递归添加以pre_str为前缀的字符串到ret集合
private:  struct trie_node *root;
};  trie::trie()
{  root = new trie_node();
}  trie::~trie()
{
}  //插入字符串
void trie::insert_str(string str)
{  if (root == NULL || str == "")  {  return ;  }  trie_node *cur_node = root;  for (int i = 0; i < str.size();)  {  string sub_str = str.substr(i, CH_SIZE);  map<string, trie_node *>::iterator iter = cur_node->child.find(sub_str);  if (iter == cur_node->child.end())//如果在map中没有找到则插入新节点  {  trie_node *tmp_node = new trie_node();  cur_node->child.insert(pair<string, trie_node *>(sub_str, tmp_node));  cur_node = tmp_node;  }  else//如果找到了value即为指向下一个节点的指针  {  cur_node = iter->second;  }  i = i + CH_SIZE ;  }  cur_node->count++;
}  //删除字符串
void trie::delete_str(string str)
{  trie_node *find_node = search_str(str);  if (find_node)  {  find_node->count--;  }
}  //查询字符串前缀
trie_node * trie::search_str_pre(string str)
{  if (str == "")  {  return root;  }  if (NULL == root )  {  return NULL;  }  trie_node *cur_node = root;  int i;  for ( i = 0; i < str.size(); )  {  string sub_str = str.substr(i, CH_SIZE);  map<string, trie_node *>::iterator iter = cur_node->child.find(sub_str);  if (iter == cur_node->child.end())  {  return NULL;  }  else  {  cur_node = iter->second;  }  i = i + CH_SIZE;  }  if (i == str.size())  {  return cur_node;  }  else  {  return NULL;  }
}  //查询字符串
trie_node * trie::search_str(string str)
{  trie_node * find_pre_node = search_str_pre(str);  if (find_pre_node != NULL)  {  if (find_pre_node->count == 0)  {  return NULL;  }  else  {  return find_pre_node;  }  }
}  //清空
void trie::clear()
{  vector<trie_node *> que;  que.push_back(root);  while (!que.empty())  {  for (map<string, trie_node *>::iterator iter = root->child.begin(); iter != root->child.end(); iter++)  {  que.push_back(iter->second);  }  trie_node *del_node = que.front();  que.pop_back();delete del_node;  }
}  //递归添加以pre_str为前缀的字符串到ret集合
void trie::add_str(trie_node *root, string pre_str, vector<string> &ret)
{  for (map<string, trie_node *>::iterator iter = root->child.begin(); iter != root->child.end(); iter++)  {  add_str(iter->second, pre_str + iter->first, ret);  }  if (root->count != 0)  {  ret.push_back(pre_str);  }
}  //返回所有前缀为str的字符串
vector<string> trie::get_str_pre(string str)
{  vector<string> ret;  trie_node *find_node = search_str_pre(str);  if (find_node != NULL)  {  add_str(find_node, str, ret);  }  return ret;
} 
int main()
{trie t;int n;string str;vector<string> ret;cout << "please input the num of the dictionary:" << endl;cin >> n;for (int i = 0; i < n; i++){cin >> str;t.insert_str(str);}cout << "please input the key word:" << endl;cin >> str;ret = t.get_str_pre(str);for (vector<string>::iterator iter = ret.begin(); iter != ret.end(); iter++){cout << *iter << endl;}return 0;
}

参考:
http://baike.baidu.com/link?url=qtnTyg6zgFj2uoaBg16hIQMP-M6fA-romTgV1CZaKKWYJsZcB_7WSI2qbNNTZtXd0IgusIidkHzIQUC5kumau_
http://m.blog.csdn.net/article/details?id=6897097

数据结构——从英文字典树到中文字典树相关推荐

  1. Trie树 -- 高效的字典树

    文章目录 简介 原理 构造一棵Trie树 使用Trie树进行查询 实现 模拟(瞎暴力)实现 空间优化 简介 Trie树是一种数据结构,它有一个好听的中文名字,叫"字典树".顾名思义 ...

  2. 012-数据结构-树形结构-哈希树[hashtree]、字典树[trietree]、后缀树

    一.哈希树概述 1.1..其他树背景 二叉排序树,平衡二叉树,红黑树等二叉排序树.在大数据量时树高很深,我们不断向下找寻值时会比较很多次.二叉排序树自身是有顺序结构的,每个结点除最小结点和最大结点外都 ...

  3. 字符串处理【字典树】 - 原理 字典树详解

    字符串处理[字典树] - 原理 字典树详解 字典树,又称Trie树.单词查找树,是一种树形结构,也是哈希树的一种变种,主要用于统计.排序和存储大量的字符串(但不限于字符串),所以经常被搜索引擎系统用于 ...

  4. dmol3给定关键字不在字典中_python中的数据结构与算法(2):字典与集合

    1. 字典是什么 字典是便于信息检索的一种数据结构,鉴于信息检索在程序中无处不在,字典的使用场景也非常广泛,包括许多 python 内部机制的实现,也依赖字典结构,比如命名空间的管理等. 检索一般是根 ...

  5. 字典树实现_trie 字典树的实现方法

    trie 字典树 (前缀树) 什么是字典树 又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文 ...

  6. 字典树(单词查找树、Trie)

    文章目录 单词查找树 1 基本概念 2 字典树的应用 3 字典树基本操作的实现 4 参考资料 单词查找树 1 基本概念   百度百科对字典树的介绍为:是一种树形结构,是一种哈希树的变种.   < ...

  7. 字典树(Trie/前缀树)

    目录 字典树的概念 字典树的逻辑 字典树的实现 易混点剖析 代码示例 字典树的概念 字典树(Trie)是一种空间换时间的数据结构,是一棵关于"字典"的树.主要用于统计.排序和保存大 ...

  8. 字典树与01字典树详解

    [镇楼]   不满足于粗浅的表面了解字典树吗,今天!由我给大家带来!字典树与01字典树的解析!! 目录 [引入] [字典树] [01字典树] [引用参考] [引入] 字典是干啥的?查找字的.那么字典树 ...

  9. 从Trie树(字典树)和后缀树

    从Trie树(字典树)谈到后缀树 转载:http://blog.csdn.net/v_july_v/article/details/6897097#t22 感谢作者,侵删. 引言 常关注本blog的读 ...

最新文章

  1. MySQL锁表解决方法
  2. 求素数的方法完整归纳,学的不仅是“求素数”!
  3. No transaction aspect-managed TransactionStatus in scope
  4. 最近一直因为工作的事情,耽误学习了
  5. 第一篇:你不一定了解的推荐系统
  6. okHttp3连接池简单使用
  7. springboot的学习笔记,这个很重要
  8. p12文件和mobileprovision文件导入到xcode如何使用
  9. 《RFID技术与应用》试题库(含答案)
  10. 考察数据结构——第三部分:二叉树和BSTs[译]
  11. c语言源文件经过编译后生成文件的后缀是什么?
  12. 还在用vuex吗,pinia(菠萝头)才香
  13. 互联网和人工智能之间,主要是什么关系?
  14. 2022世界杯看球指南
  15. 英语字母表及其冠词用法
  16. 如何在VM16上安装虚拟机(win7)及联网
  17. 大数据Spark(三):框架模块初步了解
  18. 脏读、重复读、幻读;
  19. 启发式搜索的实现,特性
  20. EXCEL函数之“VLOOKUP”

热门文章

  1. July文章《从头到尾彻底理解KMP(2014年8月22日版)》中,关于求解next数组,进一步理解如何递归前缀索引 k=next[k]
  2. 美国电视剧小超人剧情在线
  3. 吐秀山下,菜子湖畔,一年四季,家乡盛景
  4. PDF去除水印(1)-根据文本移除对象
  5. 微信小程序的抽奖页面
  6. 移除Selenium的浏览器window.navigator.webdriver等20多个指纹特征
  7. html+css实战181-header-logo和导航
  8. 自定义原生相机+拍照+GLSurfaceView+Camera
  9. 自动驾驶路径规划论文解析(1)
  10. 数字摄影测量学 计算航片邻接矩阵