STL MAP及字典树在关键字统计中的性能分析

本文分析了使用C++ STL中的map进行统计与使用字典树进行统计在运行速度、空间及适用场合的差异。通过随机生成的字符串集验证了两种方法在查询效率、内存使用及特定场景下的优劣。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明出处:http://blog.youkuaiyun.com/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优于字典树。 

<think>好的,我现在需要回答用户的问题:在C++中,使用哪个STL容器进行键值对的查找最快。用户可能希望了解不同容器的性能差异,特别是查找操作的效率。我需要回忆C++中的相关容器,它们的底层实现以及时间复杂度。 首先,STL中常用的键值对容器有std::map、std::unordered_map,还有C++11引入的std::unordered_map。另外,有时候可能也会考虑其他结构比如std::vector配合pair,但查找效率可能不高。 std::map是基于红黑树实现的,这是一种平衡二叉搜索树,因此它的查找、插入和删除操作的时间复杂度都是O(log n),其中n是元素的数量。红黑树保证了元素的有序性,按关键字顺序遍历时会得到排序后的结果。 而std::unordered_map则是基于哈希表实现的。理想情况下,哈希表的查找、插入和删除操作的平均时间复杂度是O(1),最坏情况下是O(n),但这种情况很少发生,尤其是在良好的哈希函数和适当的桶大小的情况下。不过,哈希表中的元素是无序的。 所以,从时间复杂度来看,std::unordered_map的平均情况下的查找速度应该比std::map快,尤其是在数据量较大的时候,因为O(1)比O(log n)要快。但是需要考虑到哈希冲突的问题,如果哈希函数设计不好,导致很多冲突,可能会影响性能。 另外,用户可能还关心内存使用情况或者是否需要有序存储。如果不需要有序,unordered_map更合适;如果需要有序遍历,map是更好的选择。但问题中只提到查找速度,所以主要比较两者的查找效率。 还需要注意,C++标准库中的容器具体实现可能因编译器而异,但一般情况下,哈希表的平均O(1)确实更优。例如,当键是整数或字符串时,哈希函数通常效率较高,冲突较少,查找速度更快。 可能的例外情况是当数据量非常小的时候,比如几个元素,这时O(log n)和O(1)可能差别不大,甚至可能由于常数因子较小,红黑树更快。但这种情况较少,通常讨论的是数据量较大的情况。 总结:对于纯查找操作,尤其是在平均情况下,std::unordered_map是最快的STL容器,因为它基于哈希表,时间复杂度为O(1)。而std::map的查找是O(log n)。不过,需要根据具体情况选择合适的容器,比如是否需要有序、哈希函数的质量等。</think>在C++中,**`std::unordered_map`** 是用于键值对查找最快的STL容器,其查找操作的平均时间复杂度为 **O(1)**(常数时间)。这是因为它基于**哈希表**实现,通过哈希函数直接定位键的存储位置,无需遍历或树形结构的层级搜索。 ### 关键对比: 1. **`std::unordered_map`(哈希表)**: - **查找速度**:平均 O(1),最坏 O(n)(哈希冲突严重时)。 - **特点**:元素无序,依赖高效的哈希函数。 - **适用场景**:需要高频查找且无需有序遍历键值对的场景(如缓存、字典)。 2. **`std::map`(红黑树)**: - **查找速度**:稳定 O(log n)。 - **特点**:元素按键有序(默认升序),适合需要有序性的场景。 - **适用场景**:需按顺序遍历或范围查询(如按时间戳排序的日志)。 ### 性能示例: - 若存储 **10^6 个键值对**,`std::unordered_map` 的查找通常比 `std::map` 快数十倍(哈希冲突少时)。 - 但若哈希函数设计不佳(如大量碰撞),性能可能退化为链表查询(O(n))。 ### 选择建议: - **优先 `std::unordered_map`**:当键类型(如 `int`、`std::string`)已有高质量哈希函数,且无需有序性时。 - **选择 `std::map`**:若需要有序遍历键,或键类型难以哈希(需自定义高质量哈希函数避免冲突)。 ### 代码示例: ```cpp #include <unordered_map> #include <map> #include <string> int main() { // 哈希表实现:最快查找 std::unordered_map<std::string, int> hash_map = {{"apple", 5}, {"banana", 3}}; int val = hash_map["apple"]; // O(1) 查找 // 红黑树实现:有序但稍慢 std::map<std::string, int> tree_map = {{"apple", 5}, {"banana", 3}}; val = tree_map["banana"]; // O(log n) 查找 return 0; } ``` ### 总结: 若追求极致查找速度且无需顺序,**`std::unordered_map`** 是最优选择;若需有序性,则用 **`std::map`**。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值