23. C++ 关联容器

23.1 关联容器介绍

关联容器内部的元素都是排好序的,有以下四种。

  • set:排好序的集合,不允许有相同元素。
  • multiset:排好序的集合,允许有相同元素。
  • map:每个元素都分为关键字和值两部分,容器中的元素是按关键字排序的。不允许有多个元素的关键字相同。
  • multimap:和 map 类似,差别在于元素的关键字可以相同。

关联容器使用注意事项:
1、不能修改 set 或 multiset 容器中元素的值(也不能修改 map 和 multimap 容器中元素的关键字)。因为元素被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。
2、如果要修改 set 或 multiset 容器中某个元素的值,正确的做法是先删除该元素,再插入新元素。
3、关联容器内部的元素或关键字之间比较大小可以用<运算符,也可以用自定义的比较器。因为有序,所以在关联容器上进行查找的速度较快。
4、使用关联容器的目的也就在于快速查找。当一个元素被插入关联容器时,该元素会和已有的元素进行比较,最终被插入一个合适的位置。
5、在关联容器中查找元素和插入元素的时间复杂度都是 O(log(n))。从 begin() 到 end() 遍历整个关联容器,就是从小到大遍历整个容器。
6、关联容器一般是用平衡二叉树实现的。

除了所有容器共有的成员函数外,关联容器还具有以下成员函数:

  • find:查找某个值。
  • lower_bound:查找某个下界。
  • upper_bound:查找某个上界。
  • equal_range:同时查找上界和下界。
  • count:计算等于某个值的元素个数。
  • insert:插人一个元素或一个区间。

23.2 STL multiset

multiset 是关联容器的一种,是排序好的集合(元素已经进行了排序),并且允许有相同的元素。

不能直接修改 multiset 容器中元素的值。因为元素被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。因此,如果要修改 multiset 容器中某个元素的值,正确的做法是先删除该元素,再插入新元素。

使用 multiset 必须包含头文件 。multiset 类模板的定义如下:

template , class B = allocator > class multiset {
    ...
};

该模板有三个类型参数:Key、Pred 和 B。类型参数可以有默认值,默认值就是某种类型。例如,Pred 类型参数的默认值就是 less 类型,B 的默认值就是 allocator 类型。第三个类型参数极少用到,一般都用默认值,因此这里不做介绍。

第一个类型参数说明 multiset 容器中的每个元素都是 Key 类型的。第二个类型参数 Pred 用于指明容器中元素的排序规则,在被实例化后,Pred 可以是函数对象类,也可以是函数指针类型。

multiset 内部在排序时定义了一个变量Pred op,根据表达式op(x, y)来比较两个元素 x、y 的大小。该表达式的值为 true,则说明 x 比 y 小。Pred 的默认值是 less,less 是 STL 中的函数对象类模板,其定义如下:

template 
struct less
{
    bool operator() (const _Tp &__x, const _Tp &__y) const
    { return __x < __y; }
};

这说明,在默认情况下,multiset 容器中的元素是用<运算符比较大小的。例如,假设 A 是一个类的名字,可以定义一个如下的容器对象:

multiset  s;

由于 multiset 的类型参数可以使用默认值,因此上面的语句等价于:

multiset < int, less, allocator > s;

模板类 multiset < A, less, allocator > 的 insert 成员函数可以用来插入一个元素。 插入过程中需要进行元素之间的比较,可以认为 insert 成员函数中定义了一个变量 less op,用 op(x, y) 来比较元素 x、y 的大小。归根到底,还是用<运算符比较 x、y 的大小。 因此,<运算符必须经过适当重载,才可以向 multiset 容器中插人元素。

下面的程序 会编译出错:

#include 
using namespace std;
class A{};
int main(){
    multiset  a;
    a.insert( A() ); //编译出错,因为不能用“<”运算符比较两个A对象
}

multiset 常用的成员函数如表 1 所示。有的成员函数有不止一个版本,这里不一一 列出。


表 1 .png

multiset 及 set 中的 find 和 count 并不是用==运算符比较元素是否和待查找的值相等的。它们进行比较的原则是:如果x比y小和y比x小同时为假,就认为 x 和 y 相等。

下面通过一个例子说明 multiset 的用法。

#include 
#include 

using namespace std;

template 
void Print(T first, T last)
{
    for (; first != last; ++first)
        cout << *first << " ";
    cout << endl;
}

class A
{
private:
    int n;
public:
    A(int n_) { n = n_; }
    friend bool operator < (const A & a1, const A & a2)
    { return a1.n < a2.n; }
    friend ostream & operator << (ostream & o, const A & a2)
    { o << a2.n; return o; }
    friend class MyLess;
};

class MyLess
{
public:
    bool operator() (const A & a1, const A & a2)  //按个位数比较大小
    { return (a1.n % 10) < (a2.n % 10); }
};

typedef multiset  MSET1;  //MSET1 用“<”运算符比较大小
typedef multiset  MSET2;  //MSET2 用 MyLess::operator() 比较大小

int main()
{
    const int SIZE = 6;
    A a[SIZE] = { 4, 22, 19, 8, 33, 40 };

    MSET1 m1;
    m1.insert(a, a + SIZE);
    m1.insert(22);
    cout <<  m1.count(22) << endl;  //输出 2
    Print(m1.begin(), m1.end());  //输出 4 8 19 22 22 33 40

    MSET1::iterator pp = m1.find(19);
    if (pp != m1.end()){  //条件为真说明找到
        cout << "found" << endl;  //本行会被执行,输出 found
    }
    cout << *m1.lower_bound(22) << "," << *m1.upper_bound(22) << endl; //输出 22,33

    pp = m1.erase(m1.lower_bound(22), m1.upper_bound(22));
    //pp指向被删元素的下一个元素
    Print(m1.begin(), m1.end());  //输出 4 8 19 33 40
    cout << *pp << endl;  //输出 33

    MSET2 m2;  //m2中的元素按n的个位数从小到大排序
    m2.insert(a, a + SIZE);
    Print(m2.begin(), m2.end());  //输出 40 22 33 4 8 19

    return 0;
}

image.png

注意:
用 erase 成员函数删除迭代器 i 指向的元素后,迭代器 i 即告失效, 此时不能指望 ++i 后 i 会指向被删除元素的下一个元素;相反,++i 可能立即导致出错。如果想要得到被删除元素后面那个元素的迭代器,可以在删除前获取其迭代器并保存起来(这同样适用于 set、map、multimap 的 erase 成员函数)。事实上,如果得到了某关联容器的迭代器,则该迭代器并不会因为容器中元素的插入以及其他元素的删除而失效。只要该迭代器指向的元素没有被删除,就可以一直使用它。


23.3 C++ STL set

set 是关联容器的一种,是排序好的集合(元素已经进行了排序)。set 和 multiset 类似,它和 multiset 的差别在于 set 中不能有重复的元素。multiset 的成员函数 set 中也都有。

不能直接修改 set 容器中元素的值。因为元素被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。因此,如果要修改 set 容器中某个元素的值,正确的做法是先删除该元素,再插入新元素。

使用 set 必须包含头文件 。set 的定义如下:

template < class Key, class Pred = less, class A = allocator > class set {...}

由于不能有重复元素,所以 set 中插入单个元素的 insert 成员函数与 multiset 中的有所不同,其原型如下:

pair insert(const T & val);

如果 set 的 insert 成员函数的返回值是 pair 模板类对象 x,如果 x.second 为 true,则说明插入成功,此时 x.first 就是指向被插入元素的迭代器;如果 x.second 为 false,则说明要插入的元素已在容器中,此时 x.first 就是指向原有那个元素的迭代器。

关联容器的 equal_range 成员函数的返回值也是 pair 模板类对象,其原型如下:

pair equal_range(const T & val);

返回值对象中的 first 就是 lower_bound 的值,second 就是 upper_bound 的值。

下面的程序演示了 set 的用法。

#include 
#include 

using namespace std;

int main()
{
    typedef set::iterator IT;
    int a[5] = { 3,4,6,1,2 };
    set st(a,a+5);    // st里是 1 2 3 4 6
    pair< IT,bool> result;
    result = st.insert(5); // st变成  1 2 3 4 5 6
    if(result.second){//插入成功则输出被插入元素
        cout << * result.first  << " inserted" << endl; //输出: 5 inserted
    }
    if(st.insert(5).second){
        cout << * result.first  << endl;
    }
    else{
        cout << * result.first << " already exists" << endl;
    }
    //输出 5 already exists
    pair bounds = st.equal_range(4);
    cout << * bounds.first << "," << * bounds.second ;  //输出:4,5

    return 0;
}

image.png

23.4 C++ STL multimap和map

multimap 是关联容器的一种,multimap 的每个元素都分为关键字和值两部分,容器中的元素是按关键字排序的,并且允许有多个元素的关键字相同。

注意,不能直接修改 multimap 容器中的关键字。因为 multimap 中的元素是按照关键字排序的,当关键字被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。 multimap 中的元素都是 pair 模板类的对象。

map 是关联容器的一种,map 的每个元素都分为关键字和值两部分,容器中的元素是按关键字排序的,并且不允许有多个元素的关键字相同。

注意,不能直接修改 map 容器中的关键字。因为 map 中的元素是按照关键字排序的,当关键字被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。

这里我们统一介绍下map,基本理解了map,multimap也就可以很好的使用了。

要使用 map,必须包含头文件 。map 的定义如下:

template < class Key, class T, class Pred = less, class A = allocator >
class map{
    ...
    typedef pair< const Key, T > value_type;
    ...
};

map 和 multimap 十分类似,区别在于 map 容器中元素的关键字不能重复。multimap 有的成员函数,map 都有。此外,map 还有成员函数 operator[]:

T & operator[] (Key k);

该成员函数返回 first 值为 k 的元素的 second 部分的引用。如果容器中没有元素的 first 值等于 k,则自动添加一个 first 值为 k 的元素。如果该元素的 second 成员变量是一个对象,则用无参构造函数对其初始化。

下面的程序演示了 map 的用法。

#include 
#include 

using namespace std;

template 
ostream & operator <<(ostream & o,const pair & p)
{ //将pair对象输出为 (first,second)形式
    o << "(" << p.first  << "," << p.second << ")";
    return o;
}

template
void Print(T first,T last)
{//打印区间[first,last)
    for( ; first != last; ++ first){
        cout <<  * first << " ";
    }
    cout << endl;
}

typedef map > MYMAP; //此容器关键字是整型,元素按关键字从大到小排序

int main()
{
    MYMAP mp;
    mp.insert(MYMAP::value_type(15,2.7));
    pair p = mp.insert(make_pair(15,99.3));
    if(!p.second){
        cout << * (p.first) << " already exists" << endl; //会输出
    }
    cout << mp.count(15) << endl; //输出 1
    mp.insert(make_pair(20,9.3));
    cout << mp[40] << endl;//如果没有关键字为40的元素,则插入一个

    Print(mp.begin(),mp.end());//输出:(40,0)(20,9.3)(15,2.7)
    mp[15] = 6.28; //把关键字为15的元素值改成6.28
    mp[17] = 3.14; //插入关键字为17的元素,并将其值设为3.14
    Print(mp.begin(),mp.end());

    return 0;
}

image.png

map应用例程:
1.问题描述:
输入一些单词(以“#”为结束标志),找出所有满足如下条件的单词:该单词不能通过字母的重排,得到输入文本中的另一个单词。在判断是否满足条件是不分大小写,但是在输出时应保留输入时的大小写,按字典序进行排列(所有大写字母在所有小写字母前面)。

样例输入:
ladder came tape soon leader acme RIDE lone Dreis peat
ScAlE orb eye Rides dealer NotE derail LaCeS drIed
noel dire Disk mace Rob dries

样例输出:
Disk
NotE
derail
drIed
eye
ladder
soon

思路:
1.如何判断单词是否能通过重排得到另一个单词?->只要两单词长度一样,字母出现次数一样(不区分大小写)即可->怎么判断字母出现次数一样?->将所有单词自己按照字典序排序字母,如果有两个strcmp一样即为可以通过重排
2.将每个单词标记为x,y->x为其标准化后的字符串(全部变为小写且排序完成),y为其标准化单词出现次数。如果y出现大于一次·,则不存入容器,最后输出容器即可!
3.利用map的映射关系以及set自动排序型以及忽略重复

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;


map cnt;
vector word;


string fmt(string &s)//标准化函数
{
    string st = s;
    for(int i=0; i> s)
    {
        if(s[0] == '#')break;
        word.push_back(s);
        string r = fmt(s);
        if(cnt.count(r)==0) cnt[r]=0;//没出现过初始化map键对应值为0
        cnt[r]++;
    }
    vector it;
    for(int i=0; i

你可能感兴趣的:(23. C++ 关联容器)