STL之关联容器

        以前说到的顺序容器,其元素顺序都是由程序员决定的,程序员可以随意指定新元素插入的位置。而对于关联容器而言,它的每一个元素都有一个键值(key),容器中元素的顺序并不能由程序员随意指定,而是按照键值的取值升序排列的。也就是说,对于一个关联容器s,使用迭代器在[s.begin(),s.end())区间内遍历,访问到的序列总是升序的,即s.begin()所指向的元素总是最小的,s.end() - 1所指向的元素总是最大的,对于指向中间位置的迭代器iter,*iter <= *(iter + 1)总是成立的。关联容器的最大优势在于,可以高效的根据键来查找容器中的一个元素。

        关联容器这一概念又可以划分为多个子概念。按照容器中是否允许出现重复键值,关联容器可分为单重关联容器和多重关联容器。单重关联容器中的键值是唯一的,不允许重复,集合(set)和映射(map)属于这一类。多重关联容器中,相同的键值允许重复出现,多重集合(multiset)和多重映射(multimap)便属于这一类。按照键值和元素的关系可以分为简单关联容器和二元关联容器。简单关联容器以元素本身作为键值,集合和多重映射属于这一类。二元关联容器的元素是由键值和某种类型的附加数据共同构成的,键值只是元素的一部分,映射和多重映射属于这一类。

        简单关联容器只有一个类型参数,该类型既是键类型,又是容器类型,例如set<int>,multiset<string>。二元关联容器则有两个类型参数,前一个是键类型,后一个是附加数据的类型,例如map<int,double>,multimap<string,int>。二元关联容器的元素类型是键类型和附加数据类型的组合,这种组合类型可以用一个二元组(pair)来表示。

        我们用S来表示一个容器类型名,用s表示S类型的实例,用T表示S容器的元素类型,用t表示T类型的一个实例,用K表示容器S的键的类型,k为K的一个实例。用n表示一个整型数据,用p1和p2表示指向s的元素的迭代器,用q1和q2表示不指向s的元素的输入迭代器。则关联容器的基本功能为:

(1)构造函数

S s(q1,q2)             将[q1,q2)区间内的数据作为s的元素构造容器s

单重关联容器        当[q1,q2)区间内出现具有相同键的元素时,只有第一个元素会被加入到s中

多重关联容器        将[q1,q2)区间内的所有元素无条件的加入到s中

(2)元素的插入

        可以通过insert()成员函数向关联容器中插入一个或多个元素。关联容器的insert()函数与顺序容器相比主要区别在于关联容器无须通过迭代器指定插入位置。

s.insert(t)          将元素t插入到关联容器s中

单重关联容器             只有当不存在相同键的元素时才能被成功插入,该函数的返回类型为pair<S::iterator,bool>。插入成功时,返回被插入元素的迭代器和true,否则返回与t的键相同的元素的迭代器和false。

多重关联容器             插入总会成功,返回已插入元素的迭代器

s.insert(p1,t)              将元素t插入s容器中,p1是一个提示的插入位置。如果提示位置准确(即t的键大小刚好在p1 - 1和p1之间),则可以提高插入效率。即使提示位置不准确也可以正确完成插入操作,该函数总是返回一个迭代器。

单重关联容器            只有当不存在相同键的元素时才能被成功插入,插入成功时,返回被插入元素的迭代器和true,否则返回与t的键相同的元素的迭代器

多重关联容器            插入总会成功,返回已插入元素的迭代器

s.insert(q1,q2)          相当于按顺序对[q1,q2)区间内的每个元素分别执行s.insert()

(3)元素的删除

        可以通过erase()函数来删除容器中的元素。顺序容器提供的两种通过迭代器删除指定元素的调用形式对关联容器仍然有效,此外关联容器还允许通过键来删除元素。

s.erare(p1)                     删除p1所指向的元素

s.erase(p1,p2)               删除[p1,p2)区间内的元素

s.erase(k)                      删除所有键为k的元素,返回被删除的元素的个数

(4)基于键的查找和计数

以下函数可以用来查找指定键的元素,或对指定键在容器中出现的次数进行统计。

s.find(k)                         找到任意一个键为k的元素,返回该元素的迭代器,如果s中没有键为k的元素,则返回s.end()

s.lower_bound(k)          得到s中第一个键值不小于k的元素的迭代器

s.upper_bound(k)         得到s中第一个键值不大于k的元素的迭代器

s.equal_range(k)          得到一个用pair<S::iterator,S::iterator>标识的区间,记为[p1,p2)。该区间刚好包含所有键为k的元素,p1 == s.lower_bound(k)和p2 == s.upper_bound(k)一定成立

s.count(k)                     得到s中键为k的元素的个数

关联容器的插入和删除操作不会使任何已有的迭代器、指针或引用失效。

 

集合(set)

集合用来存储一组无重复的元素。与数学上的集合不同的是,此集合不能用来描述数学上的无限集。由于集合内的元素本身是有序的,因此可以高效地查找指定的元素,也可以方便的得到指定大小范围内的元素在容器中所处的区间。

例:输入一串实数,将重复的去掉,取最大和最小的中值,分别输出小于等于此中值和大于等于此中值的实数。

#include <set>
#include <iterator>
#include <utility>
#include <iostream>
using namespace std;
int main()
{
    set<double> s;
    while (true)
    {
	double x;
	cin >> x;
	if (x == 0)			//输入0表示结束
	{
	    break;
	}
	pair<set<double>::iterator, bool> r = s.insert(x);
	if (!r.second)
	{
	    cout << x << "重复出现!" << endl;
	}
    }
    set<double>::iterator iter1 = s.begin();
    set<double>::iterator iter2 = s.end();
    double middle = (*iter1 + *(--iter2)) / 2;
    cout << "中值为:" << middle << endl;
    //输出小于或等于中值的元素
    cout << "小于等于中值的元素有:";
    copy(s.begin(), s.upper_bound(middle), ostream_iterator<double>(cout, " "));
    cout << endl;
    //输出大于或等于中值的元素
    cout << "大于等于中值的元素有:";
    copy(s.lower_bound(middle), s.end(), ostream_iterator<double>(cout, " "));
    cout << endl;
    return 0;
}

       该程序首先定义了一个double型集合s,在while循环中,每次读入一个实数,使用insert()函数将其插入到s中,通过insert()函数的返回值来判断是否插入成功,没插入成功意味着之前已经有相同元素被插入了,这时输出提示信息。集合的第一个元素一定是最小的元素,而集合的最后一个元素一定是最大的。s.upper_bound(middle)指向的是第一个大于middle的元素,s.lower_bound(middle)指向的是第一个不小于middle的元素。

映射(map)

        映射和集合同属于单重关联容器,因此用法上非常相似。它们的主要区别在于,集合的元素类型是键本身,而映射的元素类型是由键和附加数据所构成的二元组。这样,在集合中按照键查找一个元素时,一般只是用来确定这个元素是否存在,而在映射中按照键来查找一个元素时,除了能确定它的存在性外,还可以得到相应的附加数据。因此,映射的一种通常用法是,根据键来查找附加数据。

例:有5门课程,每门课程都有相应学分,从中选择3门,输出学分总和。

#include <map>
#include <string>
#include <utility>
#include <iostream>
using namespace std;
int main()
{
    map<string, int>courses;
    //将课程信息插入到courses映射中
    courses.insert(make_pair("C++", 2));
    courses.insert(make_pair("OS", 3));
    courses.insert(make_pair("Linux", 5));
    courses.insert(make_pair("Internet", 3));
    courses.insert(make_pair("English", 2));
    int n = 3;
    int sum = 0;
    while (n > 0)
    {
	string name;
	cin >> name;
	map<string, int>::iterator iter = courses.find(name);
	if (iter == courses.end())
	{
	    cout << name << "不存在!";
	}
	else
	{
	    sum += iter->second;	//累加学分
	    courses.erase(iter);	//将刚选过的课程从映射中删除
	    n--;
	}
    }
    cout << "总学分为:" << sum << endl;
    return 0;
}

例:统计一句话中每个字母出现的次数。

#include <map>
#include <iostream>
using namespace std;
int main()
{
    map<char, int>s;
    char c;
    do{
	cin >> c;
	if (isalpha(c))
	{
	    c = tolower(c);		
            s[c]++;
	}
    } while (c != '.');
    for (map<char, int>::iterator iter = s.begin(); iter != s.end(); iter++)
    {
	cout << iter->first << ":" << iter->second << endl;
    }
    cout << endl;
    return 0;
}

多重映射(multimap)和多重集合(multiset)

多重集合是允许出现重复的元素,多重映射允许一个键对应多个附加数据。多重集合与集合、多重映射与映射的用法差不多,只在几个函数上有细微差异,其差异主要体现在去除了键必须唯一的限制。对于多重关联容器,一般较少使用find()函数,而更多的使用equal_range()和count()函数。由于一个键可能对应多个元素,因此使用find()函数得到的迭代器所指向的位置具有不确定性,一般只在确定一个键在容器中是否存在时才使用find()函数。如果需要访问一个键所对应的每一个元素,可以使用equal_range()函数,如果需要得到一个键所对应的元素的个数,可以使用count()函数。

例:课表查询

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
    multimap<string, string>courses;
    //将课表信息插入到多重映射courses中
    courses.insert(make_pair("C++", "10:00"));
    courses.insert(make_pair("C++", "12:00"));
    courses.insert(make_pair("C++", "14:00"));
    courses.insert(make_pair("Linux", "10:00"));
    courses.insert(make_pair("English", "10:00"));
    courses.insert(make_pair("English", "12:00"));
    courses.insert(make_pair("OS", "10:00"));
    courses.insert(make_pair("OS", "14:00"));
    courses.insert(make_pair("OS", "16:00"));
    //输入课程名,查找上课时间
    string name;
    int count;
    do{
	cin >> name;
	count = courses.count(name);
	if (count == 0)
	{
	    cout << "该课程不存在!" << endl;
	}
    } while (count == 0);
    cout << count << " lessons" << endl;
    pair<multimap<string, string>::iterator, multimap<string, string>::iterator>range = courses.equal_range(name);
    for (multimap<string, string>::iterator iter = range.first; iter != range.second; iter++)
    {
	cout << iter->second << " ";
    }
    cout << endl;
    return 0;
}

map和set的区别:

map和set都是C++的关联容器,其底层都是用红黑树实现的。由于map和set所开放的各种操作接口红黑树都提供了,所以几乎所有的map和set的操作行为,都只是转调红黑树的操作行为。其区别如下:

(1)map中的元素是key-value(键-值)对:关键字起到索引的作用,值则表示与索引相关联的数据;set与之相对,只不过set中每个元素只包含一个关键字。

(2)set的迭代器是const的,不允许修改元素的值;map允许修改value,但不允许修改key。其原因是因为map和set是根据关键字排序来保证其有序性的,如果允许修改key的话,那么首先需要删除该键,然后调节平衡,再插入修改后的键值,载调平衡,如此一来,严重破坏了map和set的结构,导致迭代器iterator失效,不知道该指向改变前的位置还是指向指向改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值;而map的迭代器则不允许修改key值,允许修改value值。

(3)map支持下标操作,set不支持下标操作。map可以用key做下标,map的下标运算符[]将关键码作为下标去执行查找,如果关键码不存在,则插入一个具有该关键码和mapped_type类型默认值的元素至map中,因此下标运算符[]在map中需慎用,const_map不能用,只希望确定某一个关键值是否存在而不希望插入元素时也不应该使用,mapped_type类型没有默认值也不应该使用。如果find()函数能解决需要,尽可能用find()。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值