stl的map,stl的map底层实现

  stl的map,stl的map底层实现

  Std map是STL的一个关联容器,提供了一对一的数据处理能力(第一个可以称为关键字,每个关键字在map中只能出现一次,第二个可以称为关键字的值)。由于这个特性,当我们处理一对一的数据时,可以在编程中提供一个快速通道。在这里,我们来谈谈std map中数据的组织。std map中构建了一棵红黑树(一种非正式意义上的平衡二叉树)。这个树有自动排序数据的功能,所以std图中的所有数据都是有序的。我们以后会看到秩序的好处。

  这里有一个关于什么是一对一数据映射的例子。比如在一个班级里,每个学生的学号和他的名字是一一对应的映射关系。这个模型可以很容易地用地图来描述。很明显,学号是用int描述的,名字是用string描述的(在本文中,不是用char *来描述string,而是用STL中的string来描述)。以下是地图描述代码:

  Map int,string mapStudent

  1.地图的构造者

  Map提供了六个构造函数。这个问题涉及内存分配器,所以我们将跳过这个表。下面,我们将会谈到一些地图的构造方法。这里想说的是,我们通常用以下方法构造地图:

  Map int,string mapStudent

  2.数据插入

  构建地图容器后,我们可以向其中插入数据。以下是三种插入数据的方法:

  第一种:用insert函数插入pair数据,下面是一个例子(下面的代码虽然写的很随便,但是应该是在VC和GCC下编译通过的,可以运行一下看看会有什么效果。请在VC下添加此语句以阻止4786警告# #pragma警告(disable:4786))

  #包含地图

  #包含字符串

  #包括iostream

  使用命名空间std

  Int main()

  {

  Map int,string mapStudent

  mapStudent.insert(pair int,string (1," student _ one "));

  mapStudent.insert(pair int,string (2," student _ two "));

  mapStudent.insert(pair int,string (3," student _ three "));

  map int,string:iterator ITER;

  for(ITER=map student . begin();iter!=map student . end();iter)

  {

  cout ITER-first " " ITER-second end;

  }

  }

  第二种方法是用insert函数插入value_type数据。下面是一个例子。

  #包含地图

  #包含字符串

  #包括iostream

  使用命名空间std

  Int main()

  {

  Map int,string mapStudent

  mapStudent.insert(map int,string :value_type (1," student _ one "));

  mapStudent.insert(map int,string :value_type (2," student _ two "));

  mapStudent.insert(map int,string :value_type (3," student _ three "));

  map int,string:iterator ITER;

  for(ITER=map student . begin();iter!=map student . end();iter)

  {

  cout ITER-first " " ITER-second end;

  }

  }

  第三种方法是在数组中插入数据,如下所示。

  #包含地图

  #包含字符串

  #包括iostream

  使用命名空间std

  Int main()

  {

  Map int,string mapStudent

  map student[1]=" student _ one ";

  map student[2]=" student _ two ";

  map student[3]=" student _ three ";

  map int,string:iterator ITER;

  for(ITER=map student . begin();iter!=map student . end();iter)

  {

  cout ITER-first " " ITER-second end;

  }

  }

  以上三种用法虽然都可以实现数据插入,但是不一样。当然,第一种和第二种用法效果是一样的。用insert函数插入数据涉及到集合唯一性的概念,即当map中有这个关键字时,insert操作不能插入数据,只是数组方法不同。可以覆盖这个关键字对应的前一个值,用程序解释。

  mapStudent.insert(map int,string :value_type (1," student _ one "));

  mapStudent.insert(map int,string :value_type (1," student _ two "));

  上面两条语句执行后,map中关键字1对应的值是“student_one”,第二条语句不生效,所以这就涉及到我们如何知道insert语句是否插入成功的问题。您可以使用pair来获取插入是否成功。该计划如下

  Pair map int,string :iterator,bool Insert _ Pair

  insert _ Pair=地图学生。insert(map int,string :value_type (1," student _ one "));

  我们通过一副的第二个变量来知道是否插入成功,它的第一个变量返回的是一个地图的迭代器,如果插入成功的话插入_配对。秒应该是真实的的,否则为错误。

  下面给出完成代码,演示插入成功与否问题

  #包含地图

  #包含字符串

  #包括输入输出流

  使用命名空间标准

  Int main()

  {

  Map int,string mapStudent

  对映射int,string :iterator,bool Insert _ Pair

  insert _ Pair=地图学生。insert(Pair int,string (1,“student _ one”);

  If(Insert_Pair.second==true)

  {

  Cout "插入成功“恩德尔

  }

  其他

  {

  Cout "插入失败“恩德尔

  }

  insert _ Pair=地图学生。insert(Pair int,string (1,“student _ two”);

  If(Insert_Pair.second==true)

  {

  Cout "插入成功“恩德尔

  }

  其他

  {

  Cout "插入失败“恩德尔

  }

  map int,string:迭代器ITER;

  对于(ITER=地图学生。begin();iter!=地图生。end();iter)

  {

  cout ITER-第一”“ITER-第二端;

  }

  }

  大家可以用如下程序,看下用数组插入在数据覆盖上的效果

  #包含地图

  #包含字符串

  #包括输入输出流

  使用命名空间标准

  Int main()

  {

  Map int,string mapStudent

  map student[1]=" student _ one ";

  map student[1]=" student _ two ";

  映射学生[2]="学生_三";

  map int,string:迭代器ITER;

  对于(ITER=地图学生。begin();iter!=地图生。end();iter)

  {

  cout ITER-第一”“ITER-第二端;

  }

  }

  3.地图的大小

  在往地图里面插入了数据,我们怎么知道当前已经插入了多少数据呢,可以用大小函数,用法如下:

  int nSize=地图学生。size();

  4.数据的遍历

  这里也提供三种方法,对地图进行遍历

  第一种:应用前向迭代器,上面举例程序中到处都是了,略过不表

  第二种:应用反相迭代器,下面举例说明,要体会效果,请自个动手运行程序

  #包含地图

  #包含字符串

  #包括输入输出流

  使用命名空间标准

  Int main()

  {

  Map int,string mapStudent

  mapStudent.insert(pair int,string (1," student _ one "));

  mapStudent.insert(pair int,string (2," student _ two "));

  mapStudent.insert(pair int,string (3," student _ three "));

  map int,string:reverse _ iterator ITER;

  对于(ITER=地图学生。Rb egin();iter!=地图生。rend();iter)

  {

  cout ITER-第一”“ITER-第二端;

  }

  }

  第三种:用数组方式,程序说明如下

  #包含地图

  #包含字符串

  #包括输入输出流

  使用命名空间标准

  Int main()

  {

  Map int,string mapStudent

  mapStudent.insert(pair int,string (1," student _ one "));

  mapStudent.insert(pair int,string (2," student _ two "));

  mapStudent.insert(pair int,string (3," student _ three "));

  int nSize=mapStudent.size()

  //此处有误,应该是for(int nIndex=1;nIndex=nSizenIndex)

  //通过雨鱼

  for(int nIndex=0;nIndex nSizenIndex)

  {

  cout map student[nIndex]end;

  }

  }

  5.数据的查找(包括判定这个关键字是否在地图中出现)

  在这里我们将体会,地图在数据插入时保证有序的好处。

  要判定一个数据(关键字)是否在地图中出现的方法比较多,这里标题虽然是数据的查找,在这里将穿插着大量的地图基本用法。

  这里给出三种数据查找方法

  第一种:用数数函数来判定关键字是否出现,其缺点是无法定位数据出现位置,由于地图的特性,一对一的映射关系,就决定了数数函数的返回值只有两个,要么是0,要么是1,出现的情况,当然是返回一了

  第二种:用发现函数来定位数据出现位置,它返回的一个迭代器,当数据出现时,它返回数据所在位置的迭代器,如果地图中没有要查找的数据,它返回的迭代器等于目标函数返回的迭代器,程序说明

  #包含地图

  #包含字符串

  #包括输入输出流

  使用命名空间标准

  Int main()

  {

  Map int,string mapStudent

  mapStudent.insert(pair int,string (1," student _ one "));

  mapStudent.insert(pair int,string (2," student _ two "));

  mapStudent.insert(pair int,string (3," student _ three "));

  map int,string:iterator ITER;

  ITER=map student . find(1);

  如果(iter!=mapStudent.end())

  {

  Cout "Find,值为" ITER-second endl;

  }

  其他

  {

  Cout“不要找”endl

  }

  }

  第三种方法:这种方法用来判断数据是否出现,看起来很蠢,但我打算在这里说明一下。

  Lower_bound函数的用法,用于返回要查找的关键字的下界(它是一个迭代器)。

  Upper_bound函数的用法,用于返回要查找的关键字的上界(是一个迭代器)。

  例如,如果在地图中插入了1,2,3,4,那么lower_bound(2)将返回2,upper-bound(2)将返回3。

  Equal_range函数返回一对。对中的第一个变量是Lower_bound返回的迭代器,对中的第二个迭代器是Upper_bound返回的迭代器。如果这两个迭代器相等,这个关键字就不会出现在映射中。程序描述

  #包含地图

  #包含字符串

  #包括iostream

  使用命名空间std

  Int main()

  {

  Map int,string mapStudent

  map student[1]=" student _ one ";

  map student[3]=" student _ three ";

  map student[5]=" student _ five ";

  map int,string:iterator ITER;

  ITER=map student . lower _ bound(2);

  {

  //返回下界为3的迭代器

  count ITER-second endl;

  }

  ITER=map student . lower _ bound(3);

  {

  //返回下界为3的迭代器

  count ITER-second endl;

  }

  ITER=map student . upper _ bound(2);

  {

  //返回上限3的迭代器

  count ITER-second endl;

  }

  ITER=map student . upper _ bound(3);

  {

  //返回上限5的迭代器

  count ITER-second endl;

  }

  Pair map int,string :iterator,map int,string:iterator map pair;

  map pair=map student . equal _ range(2);

  if(map pair . first==map pair . second)

  {

  cout“不要找”endl

  }

  其他

  {

  Cout“查找”endl

  }

  map pair=map student . equal _ range(3);

  if(map pair . first==map pair . second)

  {

  cout“不要找”endl

  }

  其他

  {

  Cout“查找”endl

  }

  }

  6.清除和判断数据。

  Clear()函数可以用来清除地图中的数据,empty()函数可以用来判断地图中是否有数据。如果返回true,则表示地图是空的。

  7.数据删除

  这里使用了erase函数,它有三个重载函数。下面的例子详细说明了它们的用法。

  #包含地图

  #包含字符串

  #包括iostream

  使用命名空间std

  Int main()

  {

  Map int,string mapStudent

  mapStudent.insert(pair int,string (1," student _ one "));

  mapStudent.insert(pair int,string (2," student _ two "));

  mapStudent.insert(pair int,string (3," student _ three "));

  //如果要演示输出效果,请选择以下其中一个,看到的效果会更好。

  //如果要删除1,用迭代器删除

  map int,string:iterator ITER;

  ITER=map student . find(1);

  map student . erase(ITER);

  //如果要删除1,用关键字删除

  int n=map student . erase(1);//如果删除,则返回1,否则返回0

  //使用迭代器删除片段。

  //用代码清空整个地图。

  map student . earse(map student . begin()、map student . end());

  //分段删除需要注意的是,这也是STL的特点,删除区间是一个前闭后开的集合。

  //自己添加遍历代码,打印出来。

  }

  8.一些其他功能的使用

  这里有swap,key _ comp,value _ comp,get _ allocator等函数。感觉编程用不到这些函数,有兴趣可以跳过表自己研究。

  9.分类

  这里要讲的是稍微高级一点的用法。排序问题,STL默认使用小于号排序。上面的代码在排序上没有问题,因为上面的关键字是int类型,它支持小于号操作。在一些特殊情况下,比如keyword是一个结构,在排序的时候就会出现问题,因为没有小于号操作,在编译的时候insert之类的函数就过不去。这里有两种方法可以解决这个问题。

  第一种:小于号重载,有程序例子。

  #包含地图

  #包含字符串

  使用命名空间std

  Typedef结构tagStudentInfo

  {

  Int nID

  字符串strName

  }StudentInfo,* PStudentInfo//学生信息

  Int main()

  {

  int nSize

  //用学生信息映射分数

  map StudentInfo,int mapStudent

  map StudentInfo,int:iterator ITER;

  StudentInfo studentInfo

  studentinfo . NID=1;

  student info . strname=" student _ one ";

  map student . insert(pair student info,int (studentInfo,90));

  studentinfo . NID=2;

  student info . strname=" student _ two ";

  map student . insert(pair student info,int (studentInfo,80));

  for(ITER=map student . begin();iter!=map student . end();iter)

  cout ITER-first . NID endl ITER-first . strname endl ITER-second endl;

  }

  上面的程序编译不了,只要重载小于数就OK,如下:

  Typedef结构tagStudentInfo

  {

  Int nID

  字符串strName

  布尔运算符(tagStudentInfo const _A) const

  {

  //此函数指定排序策略,按nID排序,如果nID相等,则按strName排序

  If(nID _A.nID)返回true

  If(nID==_A.nID)返回strName.compare(_A.strName)

  返回false

  }

  }StudentInfo,* PStudentInfo//学生信息

  第二:模仿功能的应用。此时,在结构和程序描述中没有小于号的直接重载。

  #包含地图

  #包含字符串

  使用命名空间std

  Typedef结构tagStudentInfo

  {

  Int nID

  字符串strName

  }StudentInfo,* PStudentInfo//学生信息

  类别排序

  {

  公共:

  Bool运算符()(学生信息常量_A,学生信息常量_B)常量

  {

  If(_A.nID _B.nID)返回true

  if(_ a . NID==_ b . NID)return _ a . strname . compare(_ b . strname)

  返回false

  }

  };

  Int main()

  {

  //用学生信息映射分数

  Map StudentInfo,int,sort mapStudent

  StudentInfo studentInfo

  studentinfo . NID=1;

  student info . strname=" student _ one ";

  map student . insert(pair student info,int (studentInfo,90));

  studentinfo . NID=2;

  student info . strname=" student _ two ";

  map student . insert(pair student info,int (studentInfo,80));

  }

  10.另外

  由于STL是一个统一的整体,所以在STL中,地图的许多用法都是与其他事物结合在一起的。比如排序,这里默认用的是less sign,也就是less。如果要从大到小排序的话,这里涉及的东西很多,这里就不一一说明了。

  还需要注意的是,由于其内部的顺序和红黑树的保证,map中很多函数的时间复杂度都是log2N。如果功能可以用map函数实现,STL算法也可以完成这个功能,建议用map的内置函数,效率更高。

  先说地图的空间特征。不然估计你用的时候有时候会很郁闷。因为map的每个数据都对应红黑树中的一个节点,所以这个节点在你不保存数据的时候占用了16个字节。有一个父节点指针,左右子指针,还有一个枚举值(用红色和黑色标记,相当于平衡二叉树中的平衡因子)。我想你应该知道这些地方需要很大的内存。

stl的map,stl的map底层实现