第7章 使用关联容器
本章主要讲解如何使用关联关联容器来实现高效查找。
1、在C++中最常见的一种关联就是map(映射表),它是在头文件<map>头定义的。
映射表的行为特性跟向量很相似,但是它们之间有一个基本的区别,就是映射表的索引不一定是整数,它可以是字符串,或者是任何其他类型,但是要求每一个这样的类型的值都是可以比较的,这样我们才可以为这些值排序。
关联容器和顺序容器一个重要的区别在于:
关联容器是自动排序的,所以我们的程序不可以做任何动作来改参数素的顺序,对容器的内容进行修改的算法经常会对关联容器失效。
2、计算单词数
<span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;">// lesson7_1.cpp : 定义控制台应用程序的入口点。
//功能:计算单词数
//时间:2014.5.27
#include "stdafx.h"
#include <map>
#include <iostream>
#include <string>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
string s;
map<string,int> counters;//储存每一个单词和一个关联的计数器
//读输入,跟踪每一个单词出现的次数
while(cin >> s)
++counters[s];
//输出单词以及相关的数目
for(map<string,int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
cout << it->first << "\t" << it->second << endl;
}
return 0;
}
</span></span>
运行结果:
引申:扩展上面程序,使得它的输出按照出现的次数来排列,也就是说,输出应该把所有出现一次的单词组合起来,之后是所有出现二次的单词。
<span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;">// lesson7_1_1.cpp : 定义控制台应用程序的入口点。
//功能:扩展上面程序,使得它的输出按照出现的次数来排列,也就是说,输出应该把所有出现一次的单词组合起来,之后是所有出现二次的单词。
//时间:2014.5.27
#include "stdafx.h"
#include <string>
#include <iostream>
#include <map>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
string s;
map<string,int> counters; //储存每一个单词和一个关联的计数器
//读输入,跟踪每一个单词出现的次数
while(cin >> s)
++ counters[s];
map<string,int>::size_type max = 0;
map<string,int>::const_iterator it = counters.begin();
while(it != counters.end())
{
max = max >(it->second) ? max:(it->second);
++it;
}
for(map<string, int>::size_type i = 1; i != max+1; ++i)
{
cout << "These words appeared " << i << "times: ";
for(map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
if(it->second == i)
cout << it->first << "\t";
}
}
return 0;
}
</span></span>
运行结果:
程序说明:首先使用关联容器map<string,int> counters 存储输入的单词,此时单词出现的次数已计数存在int 中,并通过一个while 循环求出所输入单词中出现次数最多的存在max中,方便题目中的按序输出。
3、产生一个交叉引用表
<span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;">// lesson7_2.cpp : 定义控制台应用程序的入口点。
//功能:产生一个交叉引用表
//时间:2014.5.27
#include "stdafx.h"
#include <map>
#include <iostream>
#include <string>
#include <vector>
#include "split.h"
using namespace std;
//查找指向输入中每一个单词的所有行
map<string, vector<int> > xref(istream& in,vector<string> find_words(const string&) = split)
{
string line;
int line_number = 0;
map<string, vector<int> > ret;
//读入下一行
while(getline(in,line))
{
++line_number;
//把所有行分割成单词
vector<string> words = find_words(line);
//记住出现在当前行的每一个单词
for(vector<string>::const_iterator it = words.begin();it != words.end(); ++it)
{
ret[*it].push_back(line_number);
}
}
return ret;
}
int _tmain(int argc, _TCHAR* argv[])
{
//缺省使用split来调用xref
map<string,vector<int> > ret = xref(cin);
//输出结果
for(map<string,vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
{
//输出单词
cout << it->first << "occurs on lines: ";
//后面跟着一个或多个的行编号
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it;
++line_it;
//如果有的话输出多余的行编号
while(line_it != it->second.end())
{
cout << ", " << *line_it;
++line_it;
}
//换一个新行以便把每一个单词与下一个分隔开来
cout << endl;
}
return 0;
}
</span></span>
运行结果:
引申:修改代码以使它检测到同一行编号的多次重复出现并且仅仅插入这一行编号一次
<span style="font-family:KaiTi_GB2312;"><span style="font-family:KaiTi_GB2312;">// lesson7_2_1.cpp : 定义控制台应用程序的入口点。
//功能:修改代码以使它检测到同一行编号的多次重复出现并且仅仅插入这一行编号一次
//时间:2014.5.27
#include "stdafx.h"
#include <algorithm>
#include <map>
#include <iostream>
#include <string>
#include <vector>
#include "split.h"
using namespace std;
//查找指向输入中每一个单词的所有行
map<string, vector<int> > xref(istream& in,vector<string> find_words(const string&) = split)
{
string line;
int line_number = 0;
map<string, vector<int> > ret;
//读入下一行
while(getline(in,line))
{
++line_number;
//把所有行分割成单词
vector<string> words = find_words(line);
//记住出现在当前行的每一个单词
for(vector<string>::const_iterator it = words.begin();it != words.end(); ++it)
{
if(find(ret[*it].begin(),ret[*it].end(),line_number) == ret[*it].end())
ret[*it].push_back(line_number);
}
}
return ret;
}
int _tmain(int argc, _TCHAR* argv[])
{
//缺省使用split来调用xref
map<string,vector<int> > ret = xref(cin);
//输出结果
for(map<string,vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
{
//输出单词
cout << it->first << "occurs on lines: ";
//后面跟着一个或多个的行编号
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it;
++line_it;
//如果有的话输出多余的行编号
while(line_it != it->second.end())
{
cout << ", " << *line_it;
++line_it;
}
//换一个新行以便把每一个单词与下一个分隔开来
cout << endl;
}
return 0;
}
</span></span>
运行结果:
程序说明:这里修改的就是
还得注意的是因为加了find,所以必须声明algorithm.if (find(ret[*it].begin(), ret[*it].end(), line_number) == ret[*it].end())ret[*it].push_back(line_number);
4、生成句子
我们用一个映射表来编写一个程序,这个程序描述一个句子结构,或者说一种文法,并且能生成符合这个描述的随机的句子,例如我们能把一个英语句子描述成一个名词跟一个动词或者是一个名词或动词跟一个对象的组合等等。
<span style="font-family:KaiTi_GB2312;">// lesson7_3.cpp : 定义控制台应用程序的入口点。
//功能:生成句子
//时间:2014.5.27
#include "stdafx.h"
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <time.h>
#include <vector>
#include "split.h"
using namespace std;
//定义三个类型
typedef vector<string> Rule; //规则类型
typedef vector<Rule> Rule_collection; //规则集合类型
typedef map<string, Rule_collection> Grammar; //映射表的类型
//从一个特定的输入流读入一个文法
Grammer read_grammer(istream& in)
{
Grammer ret;
string line;
//读输入
while(getline(in,line))
{
//把输入分割成单词
vector<string> entry = split(line);
if(!entry.empty())
//用种类来储存相关联的规则
ret[entry[0]].push_back(Rule(entry.begin() + 1,entry.end()));
}
return ret;
}
void gen_aux(const Grammar&, const string&, vector<string>&);
//返回[0,n)中一个随机整数
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");
const int bucket_size = RAND_MAX / n;
int r;
do r = rand()/bucket_size; while(r >= n);
return r;
}
//生成句子
vector<string> gen_sentence(const Grammar& g)
{
vector<string> ret;
gen_aux(g, "<sentence>", ret);
return ret;
}
//gen_aux必须判断一个单词是不是代表一个种类,检查这个单词是否括起
bool bracketed(const string& s)
{
return s.size() > 1 && s[0] == '< '&& s[s.size() - 1] == '> ';
}
void gen_aux(const Grammar& g,const string& word,vector<string>& ret)
{
if(!bracketed(word))
{
ret.push_back(word);
}
else
{
//为对应的word的规则定位
Grammar::const_iterator it = g.find(word);
if(it == g.end())
throw logic_error("empty rule");
//获取可能的规则集合
const Rule_collection& c = it->second;
//从规则集合中随机选择一条规则
const Rule& r = c[nrand(c.size())];
//递归展开所选定的规则
for(Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
//生成句子
vector<string> sentence = gen_sentence(read_grammar(cin));
//输出第一个单词,如果存在的话
vector<string>::const_iterator it = sentence.begin();
if(!sentence.empty())
{
cout << *it;
++it;
}
//输出其余的单词,每一个单词之前都有一个空格
while(it != sentence.end())
{
cout << " " << *it;
++it;
}
cout << endl;
return 0;
}
</span>
扩展:如果要改成list时,作为数据结构,该怎么办???
<span style="font-family:KaiTi_GB2312;">//表示规则
typedef list<string> Rule;
typedef list<Rule> Rule_collection;
typedef map<string, Rule_collection> Grammar;</span>
gen_aux函数中的随机读取单词部分改为:
void gen_aux(const Grammar&, const string&, list<string>&);
// return a random integer in the range `[0,' `n)'int nrand(int n) {if (n <= 0 || n > RAND_MAX)throw domain_error("Argument to nrand is out of range");
const int bucket_size = RAND_MAX / n;int r;
do r = rand() / bucket_size;while (r >= n);
return r;}
list<string> gen_sentence(const Grammar& g) {list<string> ret;gen_aux(g, "<sentence>", ret);return ret;}
5、修改交叉引用程序查找一个文件中所有的URL,并且输出每一个不同的URL在其中出现的所有行
<span style="font-family:KaiTi_GB2312;">// lesson7_4.cpp : 定义控制台应用程序的入口点。
//功能:修改交叉引用程序查找一个文件中所有的URL,并且输出每一个不同的URL在其中出现的所有行
//时间:2014.5.28
#include "stdafx.h"
#include <map>
#include <iostream>
#include <string>
#include <vector>
#include "split.h"
#include "urls.h"
using namespace std;
//查找指向输入中每一个单词的所有行
map<string,vector<int> > xref(istream& in, vector<string> find_words(const string&) = split)
{
string line;
int line_number = 0;
map<string, vector<int> > ret;
//读下一行
while(getline(in, line))
{
++line_number;
//把输入行分割成单词
vector<string> words = find_urls(line);
//记住出现在当前行的每一个单词
for(vector<string>::const_iterator it = words.begin(); it != words.end(); ++it)
ret[*it].push_back(line_number);
}
return ret;
}
int _tmain(int argc, _TCHAR* argv[])
{
//缺省使用split来调用xref
map<string, vector<int> > ret = xref(cin);
//输出结果
for(map<string,vector<int> >::const_iterator it = ret.begin();it != ret.end(); ++it)
{
//输出单词
cout << it->first << "occurs on lines: ";
//后面跟着一个或多个的行编号
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; //输出第一个行编号
++line_it;
//如果有的话输出其余的行编号
while(line_it != it->second.end())
{
cout << ", " << *line_it;
++line_it;
}
//换一个新行以便把每一个单词与下一个分隔开来
cout << endl;
}
return 0;
}
</span>
运行结果:
程序说明:唯一的变动
<span style="font-family:KaiTi_GB2312;"> //把输入行分割成单词
vector<string> words = find_urls(line);</span>
——To_捭阖_youth