ACM程序设计(第2版)
上QQ阅读APP看书,第一时间看更新

2.6 map映照容器

map映照容器的元素数据是由一个键值和一个映照数据组成的,键值与映照数据之间具有一一映照的关系。

map映照容器的数据结构也是采用红黑树来实现的,插入元素的键值不允许重复,比较函数只对元素的键值进行比较,元素的各项数据可通过键值检索出来。由于map与set采用的都是红黑树的数据结构,所以,用法基本相似。图2-5是map映照容器元素的数据构成示意图。

图2-5 map映照容器元素的数据构成示意图

使用map容器需要头文件包含语句“#include <map>”, map文件在C:\Program Files\Microsoft Visual Studio\VC98\Include文件夹内。map文件也包含了对multimap多重映照容器的定义。

2.6.1 map创建、元素插入和遍历访问

创建map对象,键值与映照数据的类型由自己定义。在没有指定比较函数时,元素的插入位置是按键值由小到大插入到黑白树中去的,这点和set一样。下面这个程序详细说明了如何操作map容器。

        #include <map>
        #include <string>
        #include <iostream>
        using namespace std;
        int main(int argc, char * argv[])
        {

            //定义map对象,当前没有任何元素
            map<string, float> m;
            //插入元素,按键值的由小到大放入黑白树中
            m["Jack"]=98.5;
            m["Bomi"]=96.0;
            m["Kate"]=97.5;
            //前向遍历元素
            map<string, float>::iterator it;
            for(it=m.begin(); it! =m.end(); it++)
            {
                //输出键值与映照数据
                cout<<(*it).first<<" : "<<(*it).second<<endl;
            }
            return 0;
          }

运行结果:

        Bomi : 96
        Jack : 98.5
        Kate : 97.5

程序编译时,会产生代号为“warning C4786”的警告,“4786”是标记符超长警告的代号。可以在程序的头文件包含代码的前面使用“#pragma warning(disable:4786)”宏语句,强制编译器忽略该警告。4786号警告对程序的正确性和运行并无影响。

2.6.2 删除元素

与set容器一样,map映照容器的erase()删除元素函数,可以删除某个迭代器位置上的元素、等于某个键值的元素、一个迭代器区间上的所有元素,当然,也可使用clear()方法清空map映照容器。

下面这个程序演示了删除map容器中键值为28的元素:

        #pragma warning(disable:4786)
        #include <map>
        #include <string>
        #include <iostream>
        using namespace std;

       int main(int argc, char * argv[])
        {
          //定义map对象,当前没有任何元素
          map<int, char> m;
          //插入元素,按键值的由小到大放入黑白树中
          m[25]='m';
          m[28]='k';
          m[10]='x';
          m[30]='a';
          //删除键值为28的元素
          m.erase(28);

            //前向遍历元素
            map<int, char>::iterator it;
            for(it=m.begin(); it! =m.end(); it++)
            {
                //输出键值与映照数据
                cout<<(*it).first<<" : "<<(*it).second<<endl;
            }
            return 0;
          }

运行结果:

        10 : x
        25 : m
        30 : a

2.6.3 元素反向遍历

可以使用反向迭代器reverse_iterator反向遍历map照映容器中的数据,它需要rbegin()方法和rend()方法指出反向遍历的起始位置和终止位置。

        #pragma warning(disable:4786)
        #include <map>
        #include <string>
        #include <iostream>
        using namespace std;

       int main(int argc, char * argv[])
        {
          //定义map对象,当前没有任何元素
          map<int, char> m;
          //插入元素,按键值的由小到大放入黑白树中
          m[25]='m';
          m[28]='k';
          m[10]='x';
          m[30]='a';
          //反向遍历元素
          map<int, char>::reverse_iterator rit;
          for(rit=m.rbegin(); rit! =m.rend(); rit++)
          {
              //输出键值与映照数据
              cout<<(*rit).first<<" : "<<(*rit).second<<endl;
          }
          return 0;
        }

运行结果:

        30 : a
        28 : k

        25 : m
        10 : x

2.6.4 元素的搜索

使用find()方法来搜索某个键值,如果搜索到了,则返回该键值所在的迭代器位置,否则,返回end()迭代器位置。由于map采用黑白树数据结构来实现,所以搜索速度是极快的。

下面这个程序搜索键值为28的元素:

        #pragma warning(disable:4786)
        #include <map>
        #include <string>
        #include <iostream>
        using namespace std;

       int main(int argc, char * argv[])
        {
          //定义map对象,当前没有任何元素
          map<int, char> m;
          //插入元素,按键值的由小到大放入黑白树中
          m[25]='m';
          m[28]='k';
          m[10]='x';
          m[30]='a';
          map<int, char>::iterator it;
          it=m.find(28);
          if(it! =m.end())//搜索到该键值
          {
              cout<<(*it).first<<" : "<<(*it).second<<endl;
          }
          else
          {
              cout<<"not found it"<<endl;
          }
          return 0;
        }

运行结果:

        28 : k

2.6.5 自定义比较函数

将元素插入到map中去的时候,map会根据设定的比较函数将该元素放到该放的节点上去。在定义map的时候,如果没有指定比较函数,那么采用默认的比较函数,即按键值由小到大的顺序插入元素。在很多情况下,需要自己编写比较函数。

编写比较函数与set比较函数是一致的,因为它们的内部数据结构都是红黑树。编写方法有两种。

(1)如果元素不是结构体,那么,可以编写比较函数。下面这个程序编写的比较规则是要求按键值由大到小的顺序将元素插入到map中:

        #pragma warning(disable:4786)
        #include <map>
        #include <string>
        #include <iostream>
        using namespace std;

       //自定义比较函数myComp
        struct myComp
        {
          bool operator()(const int &a, const int &b)
          {
              if(a! =b)return a>b;
              else
                    return a>b;
          }
        };
        int main(int argc, char * argv[])
        {
          //定义map对象,当前没有任何元素
          map<int, char, myComp> m;
          //插入元素,按键值的由小到大放入黑白树中
          m[25]='m';
          m[28]='k';
          m[10]='x';
          m[30]='a';
          //使用前向迭代器中序遍历map
          map<int, char, myComp>::iterator it;
          for(it=m.begin(); it! =m.end(); it++)
          {
              cout<<(*it).first<<" : "<<(*it).second<<endl;
          }
          return 0;
        }

运行结果:

        30 : a
        28 : k
        25 : m
        10 : x

(2)如果元素是结构体,那么,可以直接把比较函数写在结构体内。下面的程序详细说明了如何操作:

        #pragma warning(disable:4786)
        #include <map>

        #include <string>
        #include <iostream>
        using namespace std;

       struct Info
        {
          string name;
          float score;
          //重载“<”操作符,自定义排序规则
          bool operator < (const Info &a) const
          {
              //按score由大到小排列。如果要由小到大排列,使用“>”号即可
              return a.score<score;
          }
        };

       int main(int argc, char * argv[])
        {
          //定义map对象,当前没有任何元素
          map<Info, int> m;
          //定义Info结构体变量
          Info info;
          //插入元素,按键值的由小到大放入黑白树中
          info.name="Jack";
          info.score=60;
          m[info]=25;
          info.name="Bomi";
          info.score=80;
          m[info]=10;
          info.name="Peti";
          info.score=66.5;
          m[info]=30;
          //使用前向迭代器中序遍历map
          map<Info, int>::iterator it;
          for(it=m.begin(); it! =m.end(); it++)
          {
              cout<<(*it).second<<" : ";
              cout<<((*it).first).name<<" "<<((*it).first).score<<endl;
          }
          return 0;
        }

运行结果:

        10 : Bomi 80
        30 : Peti 66.5
        25 : Jack 60

2.6.6 用map实现数字分离

对数字的各位进行分离,采用取余等数学方法操作是很耗时的。而把数字当成字符串,使用map的映照功能,很方便地实现了数字分离。下面这个程序将一个字符串中的字符当成数字,并将各位的数值相加,最后输出各位的和。

        #pragma warning(disable:4786)
        #include <string>
        #include <map>
        #include <iostream>
        using namespace std;

       int main(int argc, char * argv[])
        {
          //定义map对象,当前没有任何元素
          map<char, int> m;
          //赋值:字符映射数字
          m['0']=0;
          m['1']=1;
          m['2']=2;
          m['3']=3;
          m['4']=4;
          m['5']=5;
          m['6']=6;
          m['7']=7;
          m['8']=8;
          m['9']=9;
          /*上面的10条赋值语句可采用下面这个循环来简化代码编写
          for(int j=0; j<10; j++)
          {
              m['0'+j]=j;
          }
          */
          string sa, sb;
          sa="6234";
          int i;
          int sum=0;
          for(i=0; i<sa.length(); i++)
          {
              sum+=m[sa[i]];
          }
          cout<<"sum = "<<sum<<endl;
          return 0;
        }

运行结果:

        sum = 15

2.6.7 数字映照字符的map写法

在很多情况下,需要实现将数字映射为相应的字符,看看下面这个程序:

        #pragma warning(disable:4786)

        #include <map>
        #include <string>
        #include <iostream>
        using namespace std;

       int main(int argc, char * argv[])
        {
          //定义map对象,当前没有任何元素
          map<int, char> m;
          //赋值:字符映射数字
          m[0]='0';
          m[1]='1';
          m[2]='2';
          m[3]='3';
          m[4]='4';
          m[5]='5';
          m[6]='6';
          m[7]='7';
          m[8]='8';
          m[9]='9';
          /*上面的10条赋值语句可采用下面这个循环来简化代码编写
          for(int j=0; j<10; j++)
          {
              m[j]='0'+j;
          }
          */
          int n=7;
          string s="The number is ";
          cout<<s + m[n]<<endl;
          return 0;
        }

运行结果:

        The number is 7