STL容器学习总结(二)

来源:互联网 发布:nba历届最佳新秀数据 编辑:程序博客网 时间:2024/06/17 20:50

关联式容器的定义

所谓关联式容器,概念上类似关联式数据库(实际上则简单许多):每项数据(元素)包含一个键值(key)和一个实值(value)。当元素被插入到关联式容器中时,容器内部数据结构(可能是RB-tree,也可能是hash-table)便依照其键值大小,以某种特定规则将这个元素放置于适当位置。关联式容器没有所谓头尾(只有最大元素和最小元素),所以不会有push_back(),push_front(),pop_back(),pop_front(),begin(),end()这样的操作。

一般而言,关联式容器的内部结构是一个balanced binary tree(平衡二叉树),以便获得良好的搜索效率。balancedbinary tree有很多种类型,包括AVL-tree、RB-tree、AA-tree,其中广泛运用于STL的是RB-tree(红黑树)。

标准的STL关联式容器分为set(集合)和map(映射类)两大类,以及这两大类的衍生体multiset(多键集合)和multimap(多键映射表)。这些容器的底层机制均以RB-tree完成(红黑树)。RB-tree也是一个独立容器,但并不开放给外界使用。

此外,SGI STL还提供了一个不在标准规格之列的关联式容器:hash table(散列表,哈希表),以及以此hash table为底层机制而完成的hash_set(散列集合)、hash_map(散列映射表)、hash_multiset(散列多键集合)、hash_multimap(散列多键映射表)。

 

map容器

STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据 处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。map内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的,后边我们会见识到有序的好处。它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。对于迭代器来说,可以修改实值,而不能修改key。key 和 value可以是任意你需要的类型,根据key值快速查找记录,查找的复杂度基本是Log(N)

基本操作:

1.构造函数:

map<int, string> mapStudent;

2.数据的插入:

mapStudent.insert(pair<int,string>(1, "student_one")); //pair数据

mapStudent.insert(map<int,string>::value_type (1, "student_one"));//用insert函数插入value_type数据

mapStudent[1] ="student_one";  //数组插入

注意:第一种和第二种在效果上是完成一样的,涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是插入数据不了的,怎么知道insert语句是否插入成功:

pair<map<int,string>::iterator, bool> Insert_Pair;

Insert_Pair =mapStudent.insert(map<int, string>::value_type (1,"student_one"));

if(Insert_Pair.second== true) 

        cout<<"InsertSuccessfully"<<endl; 

    else 

        cout<<"InsertFailure"<<endl; 

但是用数组方式就不同了,它可以覆盖以前该关键字对应的值

3.map的大小:mapStudent.size();

4.数据的遍历:

map<int,string>::iterator iter;

for(iter =mapStudent.begin(); iter != mapStudent.end(); iter++) 

       cout<<iter->first<<''<<iter->second<<endl;//前向迭代器

map<int,string>::reverse_iterator iter; 

for(iter =mapStudent.rbegin(); iter != mapStudent.rend(); iter++) 

       cout<<iter->first<<" "<<iter->second<<endl;  //反向迭代器

for(int nindex = 1;nindex <= nSize; nindex++) 

       cout<<mapStudent[nindex]<<endl;  //数组

5,查找并获取map中的元素(包括判定这个关键字是否在map中出现)

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

mapStudent.find(1); 

用find函数来定位数据出现位置,它返回的一个迭代器,当数据出现时,它返回数据所在位置的迭代器iterator,如果map中没有要查找的数据,它返回的迭代器等于end函数返回的迭代器。传入的参数是要查找的key,

iter =mapStudent.find(1); 

    if(iter != mapStudent.end()) 

       cout<<"Find, the value is"<<iter->second<<endl; 

    else 

       cout<<"Do notFind"<<endl; 

lower_bound:返回要查找关键字的下界(是一个迭代器)

upper_bound:返回要查找关键字的上界(是一个迭代器)

Equal_range:返回一个pair,第一个变量是Lower_bound返回的迭代器,第二个迭代器是Upper_bound返回的迭代器,如果这两个迭代器相等的话,则说明map中不出现这个关键字,

mappair =mapStudent.equal_range(2); 

    if(mappair.first == mappair.second) 

        cout<<"Do notFind"<<endl; 

    else 

       cout<<"Find"<<endl; 

6,从map中删除元素

iterator erase(iterator it);//通过一个条目对象删除

iterator erase(iterator first,iterator last)//删除一个范围

size_typeerase(const Key&key);//通过关键字删除

clear()就相当于map.erase(map.begin(),map.end());

7. swap:不是一个容器中的元素交换,而是两个容器所有元素的交换

8. 排序 :

map中的元素是自动按Key升序排序,所以不能对map用sort函数;

STL中默认是采用小于号来排序的,在一些特殊情况,比如关键字是一个结构体,涉及到排序就会出现问题,因为它没有小于号操作,insert等函数在编译的时候过不去,下面给出两个方法解决这个问题。

小于号重载

typedef structtagStudentinfo 

       int     niD; 

       string  strName; 

       bool operator < (tagStudentinfoconst& _A) const 

 

       {    //这个函数指定排序策略,按niD排序,如果niD相等的话,按strName排序 

            if(niD < _A.niD) returntrue; 

            if(niD == _A.niD) 

                return strName.compare(_A.strName)< 0; 

        return false; 

       } 

}Studentinfo,*PStudentinfo; //学生信息 

仿函数的应用,这个时候结构体中没有直接的小于号重载

class sort 

public: 

    bool operator() (Studentinfo const &_A,Studentinfo const &_B) const 

   { 

        if(_A.niD < _B.niD) 

            return true; 

        if(_A.niD == _B.niD) 

            return _A.strName.compare(_B.strName)< 0; 

    return false; 

    } 

}; 

 

map<Studentinfo, int, sort>mapStudent; 

 

map中由于它内部有序,由红黑树保证,因此很多函数执行的时间复杂度都是logN的,如果用map函数可以实现的功能,而STL Algorithm也可以完成该功能,建议用map自带函数,效率高一些。由于map的每个数据对应红黑树上的一个节点,这个节点在不保存数据时,是占用16个字节的,一个父节点指针,左右孩子指针,还有一个枚举值(标示红黑的,相当于平衡二叉树中的平衡因子),很费内存。

 

map关联容器

map :关联数组;保存关键字-值对;数据的存放是有序的

multimap:关键字可以重复出现的map

unordered_map:用哈希函数组织的map;容器中的数据存放是无序的

unordered_multimap:哈希组织的map;关键字可以重复出现

类型map和multimap定义在头文件map中,unordered_map定义在头文件unordered_map中。

所有有序关联容器数据是按关键字的字典顺序进行存储存放的。

multimap容器

和map容器类似,区别在于map容器中的关键字必须是唯一的,对于一个给定的关键字,只能有一个元素的关键字等于它。而multimap对此没有限制,允许多个元素具有相同的关键字,map与multimap很多操作都一样.

multimap与map使用不同的地方:

1.添加元素: 由于multimap中容器的关键字不必唯一,可以向multimap中插入多个关键字相同的元素。

authors.insert({"Barth","sot-weedfactor"});

authors.insert({"Barth","Lostin the Funhouse"});

2.下标访问: map是支持下标访问的,但是由于multimap中的一个关键字可能对应多个值,所以multimap并不支持下标操作。

3.查找元素: 如果multimap中有多个元素具有相同的关键字,则这些关键字在容器中会相邻存储,可以通过这一特性,将关键字对应的多个值全部找出来。

intnumbers=authors.count(search_item);

autoit=authors.find(search_item);

while(numbers)

{

   cout<<iter->second<<endl; ++it;numbers--;

}

set容器

set容器中的每个元素只包含一个关键字,其实就是一个集合,用来存储同类型的元素。 
1.set
容器的定义与初始化:

由于set是有序的关联容器,容器内部会按字典顺序自动为元素进行排序,如果我们需要将数据按顺序存储起来,使用set容器是一个非常不错的选择。

set<string>a1={"fengxin","666"};

set<string>a2=a1;

2.添加与删除元素: 同map容器类似。

set<string>a;  //empty set

a.insert("fengxin");  // 插入一个元素

a.emplace("123");   //插入

a.erase("123");    //删除关键字为123的元素

3.遍历元素: 同map容器类似。用迭代器进行遍历set容器。需要注意的是,同不能改变一个map的关键字一样,一个set中的关键字也是const的。所以,我们只能用一个set迭代器读取元素的值,但不能修改。

set<int>iset={0,1,2,3,4,5,6,7,8,9};

auto it=iset.begin();

if(it!=iset.end())

{

     *it=10; //错误:set中关键字是只读的

     cout<<*it<<endl;

}

4.查找元素: 由于set中存储的只有关键字,所以set容器并不支持下标操作。 同样使用find函数进行关键字的查找,函数返回指向关键字的迭代器。

set<int>iset={0,1,2,3,4,5,6,7,8,9};

autoit=iset.find(6);

cout<<*it<endl;

 

set关联容器

set : 关键字即值,即只保存关键字的容器

multiset : 关键字可重复出现的set

unordered_set: 无序的set

unordered_multiset:关键字可以重复出现的无序的set

set和multiset定义在头文件set中,unordered_set定义在头文件unordered_set中。

 

multiset容器: set和multiset容器区别同map与multimap容器

 

容器比较

 

vector

deque

list

set

multiset

map

multimap

名称

向量容器

双向队列容器

列表容器

集合

多重集合

映射

多重映射

内部数

据结构

连续存储的数组形式(一端开口的组)

连续或分段连续存储数组(两端

开口的数组)

双向环状链表

红黑树(平衡检索二叉树)

红黑树

红黑树

红黑树

头文件

#include <vector>

#include <deque>

#include <list>

#include <set>

#include <set>

#include <map>

#include <map>

操作元素的方式

下标运算符:[0](可以用迭代器,但插入删除操作时会失效)

下标运算符或迭代器

只能用迭代器(不断用变量值来递推新值,相当于指针),不支持使用下标运算符

迭代器

迭代器

迭代器

迭代器

插入删除操作迭代器是否失效

插入和删除元素都会使迭代器失效

插入任何元素都会使迭代器失效。删除头和尾元素,指向被删除节点迭代器失效,而删除中间元素会使所有迭代器失效

插入,迭代器不会失效。删除,指向被删除节点迭代器失效

插入,迭代器不会失效。删除,指向被删除节点迭代器失效

插入,迭代器不会失效。删除,指向被删除节点迭代器失效

插入,迭代器不会失效。删除,指向被删除节点迭代器失效

插入,迭代器不会失效。删除,指向被删除节点迭代器失效

 

容器特点比较以及选择

 

vector

deque

list

set

multiset

map

multimap

名称

向量容器

双向队列容器

列表容器

集合

多重集合

映射

多重映射

特点

增加和获取元素效率

很高,插入和删除的

效率很低

 

增加和获取元素效率

较高,插入和删除的

效率较高

 

增加和获取元素效率

很低,插入和删除的

效率很高

1.键(关键字)和值(数据)相等(就是模版只有一个参数,键和值合起来)

2.键唯一

3.元素默认按升序排列

1.键和值相等

2.键可以不唯一

3.元素默认按升序排列

1.键和值分开(模版有两个参数,前面是键后面是值)

2.键唯一

3.元素默认按键的升序排列

1.键和值分开

2.键可以不唯一

3.元素默认按键的升序排列

定义容器

vector<string> book(50);

deque<string> book(50);

list<string> book;

set<string> book;

multiset<string> book;

map<int,string> book;

multimap<int,string> book;

 

无序关联容器

 

unordered_map与unordered_set都是无序的关联容器有序关联容器中的关键字是有序排列的,所以要求关键字可以进行<运算符比较或满足自定义的比较操作。无序关联容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符。

无序容器可以使用上述所有的与有序容器相同的操作,由于无序容器在存储上组织为桶,每个桶保存零个或多个元素,容器的性能依赖于哈希函数的质量和桶的数量和大小,因此无序容器多了一些哈希函数和桶相关的操作。

1.桶接口

m.bucket_count()        正在使用的桶的数目

m.max_bucket_count()    容器能容纳的最多的桶的数量

m.bucket_size(n)        第n个桶中有多少个元素

m.bucket(k)             关键字为k的元素在哪个桶

2.桶迭代

local_iterator            可以用来访问桶中元素的迭代器类型

const_local_iterator      桶迭代器的const版本

m.begin(n)、m.end(n)     桶n的首元素迭代器和尾后迭代器

m.cbegin(n)、m.cend(n)   与前两个函数类似,但返回const_local_iterator

3.哈希策略

//每个桶的平均元素数量,返回float

m.load_factor()

//m试图维护的平均桶大小,返回float值,要求创建的新桶的load_factor<=max_load_factor        

m.max_load_factor()

//重新存储,使得bucket_count>=n,且bucket_count>size/max_load_factor        

m.rehash(n) 

//重新存储,使得m可以保存n个元素且不必rehash

m.reserve(n)