转载请注明出处:http://blog.csdn.net/mxway/article/details/21321541
在搜索引擎在通常会对关键字出现的次数进行统计,这篇文章分析下使用C++ STL中的map进行统计,及使用字典树进行统计在运行速度,空间及适用场合进行分析。首先随机生成100万个3-6长度的字符串。为了简化问题,字符串中仅由小写字母组成。另外随机生成10万个长度3-8的字符串用于测试map和字典树在查询方面的效率。
下面是使用map和字典树实现的C++代码:
STL map实现统计的源码:
#include<iostream>
#include<ctime>
#include<fstream>
#include<string>
#include<map>
using namespace std;
int main()
{
clock_t start,end;
map<string,int> dict;
string word;
ifstream in("data.dat");
start = clock();
while(in>>word)
{
if(dict[word] == NULL)
{
dict[word] = 1;
}
else
{
dict[word]++;
}
}
in.close();
end = clock();
cout<<"STL MAP统计花费的时间为:"<<end-start<<"毫秒"<<endl;
map<string,int>::iterator itr = dict.begin();
start = clock();
ofstream out("out.txt");
while(itr != dict.end() )
{
out<<itr->first<<" "<<itr->second<<endl;
itr++;
}
end = clock();
cout<<"STL MAP输出到文件花费时间为:"<<end-start<<"毫秒"<<endl;
out.close();
start = clock();
int sum1=0,sum2=0;
ifstream searchIn("search.dat");
while(searchIn>>word)
{
if(dict[word] != 0)
{
sum1++;
}
else
{
sum2++;
}
}
end = clock();
cout<<"找到单词:"<<sum1<<"-->"<<"没有找到单词:"<<sum2<<endl;
cout<<"查询花费时间:"<<end-start<<endl;
return 0;
}
字典树实现源码:
#include<iostream>
#include<string.h>
#include<fstream>
#include<ctime>
using namespace std;
char str[20];//用于在输出字典树中的单词时使用。
struct Node
{
int cnt;
struct Node *child[26];
Node()
{
int i;
for(i=0; i<26; i++)
{
child[i] = NULL;
}
cnt = 0;
}
};
/*
*
* 将一个字符串插入到字典树中
*
*/
void Insert(Node *root, char word[])
{
Node *p = root;
int i,index;
int len = strlen(word);
for(i=0; i<len; i++)
{
index = word[i] - 'a';//这里是一个hash算法,只考虑小写字母的情况
if(p->child[index] == NULL)
{
p->child[index] = new Node();
}
p = p->child[index];
}
p->cnt++;//单词数加1。
}
/*
*
* 字符串输出到文件
*/
void OutToFile(char *word,int cnt)
{
ofstream out("out.txt",ios::app);
out<<word<<" "<<cnt<<endl;
out.close();
}
/*
*将字典树中的单词及其出现次数输出
*
*/
void OutputWord(Node *p,int length)
{
int i;
if(p->cnt != 0)//找到了一个字符串
{
str[length] = '\0';
OutToFile(str,p->cnt);
}
for(i=0; i<26; i++)
{
if(p->child[i] != NULL)
{
str[length] = i+'a';//根据下标还原字符
OutputWord(p->child[i],length+1);
}
}
}
/**
* 查询word是否在字典树中
*
*/
int SearchWord(Node *p,char word[])
{
int i,index;
int len = strlen(word);
for(i=0; i<len; i++)
{
index = word[i]-'a';
if(p->child[index] == NULL)//没有找到
{
return 0;
}
p = p->child[index];
}
if(p->cnt > 0)
{
return 1;//找到
}
else//前缀字符串不能算是有这个单词
{
return 0;
}
}
/*
*
*销毁字典树
*
*/
void DestroyTrieTree(Node *p)
{
int i;
for(i=0; i<26; i++)
{
if(p->child[i] != NULL)
{
DestroyTrieTree(p->child[i]);
}
}
delete p;
}
int main()
{
Node *Root = new Node();
char word[20];
clock_t start,end;
start = clock();
ifstream in("data.dat");
while(in>>word)
{
Insert(Root,word);
}
end = clock();
cout<<"使用字典树进行统计花费时间:"<<end-start<<"毫秒"<<endl;
start = clock();
OutputWord(Root,0);
end = clock();
cout<<"输出到文件花费时间:"<<end-start<<"毫秒"<<endl;
in.close();
int sum1=0,sum2=0;
start = clock();
ifstream searchIn("search.dat");
while(searchIn>>word)//
{
if(SearchWord(Root,word) )
{
sum1++;
}
else
{
sum2++;
}
}
searchIn.close();
end = clock();
cout<<"找到单词:"<<sum1<<"-->"<<"没有找到单词:"<<sum2<<endl;
cout<<"查询花费时间:"<<end-start<<endl;
/** 销毁字典树 */
for(int i=0; i<26; i++)
{
if(Root->child[i] != NULL)
{
DestroyTrieTree(Root->child[i]);//销毁字典树
}
}
return 0;
}
下面是两个程序在release版本下的运行情况:
一、运行时间方面:从上面可以看出在统计和查询过程中使用字典树的速度明显优于map。假设字符串长度为n,共有m个关键字。由于map其底层是由红黑树(红黑树本质一种排序二叉树)支持,所以将一个字符串插入到map中需要log(m)次才能找到其所在位置。在这log(m)次中每次极端情况下需要进行n次比较。所以往map中插入一个字符串需要O(n*log(m))的时间复杂度。对于字典树从上面的程序中可以看出。插入一个字符串只与字符串的长度有关而与关键字的个数无关,其时间复杂度为O(n)。而在将所有的关键字及其出现次数写到外部文件时,字典树花费了巨大的时间。这是由于字典树的遍历是递归的,大量的时间花在了栈的建立和销毁上。
二、在内存空间使用方面
以插入一个字符串a为例,插入到字典树中正真存储有用的数据只占一个空间,另外需要26个空间的指针域。而插入到map,其底层是红黑树,数据占用一个空间;另外再需两个空间的指针指向其左右孩子。所以在空间使用方面,map使用较少的内存空间。
三、适用场合
(1)字典树及map的比较:1.字典树在插入和查询一个的字符串的的时间较map快。2.map比字典树使用更少的内存空间。3.在需要在统计的数据写到外部文件时,map比字典树快很多。
(2)字典树的适用场合:
在不需要将字典树的数据写到外部文件的情况,并对内存空间没有太多要求以及对系统响应要求较高的系统中使用字典树优于map。比如在12306网站的订票页面,在出发地框中输入bj就会提示“北京”等信息。
在对系统响应要求不高而对内存有限制的系统,以及需要将内存中存储的数据写到外部文件的系统使用map优于字典树。