C++ map,set内部数据结构

  1)Set是一种关联容器,它用于存储数据,而且能从一个数据集合中取出数据。它的每一个元素的值必须惟一,并且系统会根据该值来自动将数据排序。每一个元素的值不能直接被改变。【重点】内部结构采用红黑树的平衡二叉树。multiset 跟set 相似,惟一的区别是容许键值重复!!!linux

如: 为什么map和set的插入删除效率比用其余序列容器高?ios

       为什么每次insert以后,之前保存的iterator不会失效?程序员

       为什么map和set不能像vector同样有个reserve函数来预分配数据?算法

        当数据元素增多时(10000到20000个比较),map和set的插入和搜索速度变化如何?数组

或许有得人能回答出来大概缘由,但要完全明白,还须要了解STL的底层数据结构。 C++ STL 之因此获得普遍的赞誉,也被不少人使用,不仅是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量经常使用数据结构操做。vector封装数组,list封装了链表,map和 set封装了二叉树,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的经常使用操做,如:插入、排序、删除、查找等。让用户在 STL使用过程当中,并不会感到陌生。 C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种很是高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于通常的平衡二叉树(有些书籍根据做者姓名,Adelson-Velskii和Landis,将其称为AVL-树),因此被STL选择做为了关联容器的内部结构。本文并不会介绍详细AVL树和RB树的实现以及他们的优劣,关于RB树的详细实现参看红黑树: 理论与实现(理论篇)。本文针对开始提出的几个问题的回答,来向你们简单介绍map和set的底层数据结构。数据结构

为什么map和set的插入删除效率比用其余序列容器高? 大部分人说,很简单,由于对于关联容器来讲,不须要作内存拷贝和内存移动。说对了,确实如此。map和set容器内全部元素都是以节点的方式来存储,其节点结构和链表差很少,指向父节点和子节点。less

结构图可能以下:函数

     A性能

    /  /学习

  B    C

 / /   / /

D  E F  G

所以插入的时候只须要稍作变换,把节点的指针指向新的节点就能够了。删除的时候相似,稍作变换后把指向删除节点的指针指向其余节点就OK了。这里的一切操做就是指针换来换去,和内存移动没有关系。 为什么每次insert以后,之前保存的iterator不会失效? 看见了上面答案的解释,你应该已经能够很容易解释这个问题。iterator这里就至关于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(固然 被删除的那个元素自己已经失效了)。相对于vector来讲,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。由于为了 保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程当中可能已经被其余内存覆盖或者内存已经被释放了。即便时push_back的时 候,容器内部空间可能不够,须要一块新的更大的内存,只有把之前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把须要插入的元素放 到最后,那么之前的内存指针天然就不可用了。特别时在和find等算法在一块儿使用的时候,牢记这个原则:不要使用过时的iterator。 为什么map和set不能像vector同样有个reserve函数来预分配数据? 我之前也这么问,究其原理来讲时,引发它的缘由在于在map和set内部存储的已经不是元素自己了,而是包含元素的节点。也就是说map内部使用的Alloc并非map声明的时候从参数中传入的Alloc。例如: map, Alloc > intmap; 这时候在intmap中使用的allocator并非Alloc, 而是经过了转换的Alloc,具体转换的方法时在内部经过Alloc::rebind从新定义了新的节点分配器,详细的实现参看完全学习STL中的Allocator。其实你就记住一点,在map和set内面的分配器已经发生了变化,reserve方法你就不要奢望了。 当数据元素增多时(10000和20000个比较),map和set的插入和搜索速度变化如何? 若是你知道log2的关系你应该就完全了解这个答案在map和set中查找是使用二分查找,也就是说,若是有16个元素,最多须要比较4次就能找到结 果,有32个元素,最多比较5次。那么有10000个呢?最多比较的次数为log10000,最多为14次,若是是20000个元素呢?最多不过15次。 看见了吧,当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/14的搜索时间而已。你明白这个道理后,就能够安心往里面放入元素了。 最后,对于map和set Winter还要提的就是它们和一个c语言包装库的效率比较。在许多unix和linux平台下,都有一个库叫isc,里面就提供相似于如下声明的函数: void tree_init(void **tree); void *tree_srch(void **tree, int (*compare)(), void *data); void tree_add(void **tree, int (*compare)(), void *data, void (*del_uar)()); int tree_delete(void **tree, int (*compare)(), void *data,void (*del_uar)()); int tree_trav(void **tree, int (*trav_uar)()); void tree_mung(void **tree, void (*del_uar)()); 许多人认为直接使用这些函数会比STL map速度快,由于STL map中使用了许多模板什么的。其实否则,它们的区别并不在于算法,而在于内存碎片。若是直接使用这些函数,你须要本身去new一些节点,当节点特别多, 并且进行频繁的删除和插入的时候,内存碎片就会存在,而STL采用本身的Allocator分配内存,之内存池的方式来管理这些内存,会大大减小内存碎 片,从而会提高系统的总体性能。Winter在本身的系统中作过测试,把之前全部直接用isc函数的代码替换成map,程序速度基本一致。当时间运行很长 时间后(例如后台服务程序),map的优点就会体现出来。从另一个方面讲,使用map会大大下降你的编码难度,同时增长程序的可读性。何乐而不为?

/*
set/multiset会根据待定的排序准则,自动将元素排序。二者不一样在于前者不容许元素重复,然后者容许。
1) 不能直接改变元素值,由于那样会打乱本来正确的顺序,要改变元素值必须先删除旧元素,则插入新元素
2) 不提供直接存取元素的任何操做函数,只能经过迭代器进行间接存取,并且从迭代器角度来看,元素值是常数
3) 元素比较动做只能用于型别相同的容器(即元素和排序准则必须相同)
set模板原型://Key为元素(键值)类型
template <class Key, class Compare=less<Key>, class Alloc=STL_DEFAULT_ALLOCATOR(Key) >
从原型能够看出,能够看出比较函数对象及内存分配器采用的是默认参数,所以若是未指定,它们将采用系统默认方式,
另外,利用原型,能够有效地辅助分析建立对象的几种方式
*/
#include <iostream>
#include <string>
#include <set>

using namespace std;

struct strLess
{
   bool operator() (const char *s1, const char *s2) const
   {
    return strcmp(s1, s2) < 0;
   }
};

void printSet(set<int> s)
{
copy(s.begin(), s.end(), ostream_iterator<int>(cout, ", ") );

// set<int>::iterator iter;
// for (iter = s.begin(); iter != s.end(); iter++)
//    //cout<<"set["<<iter-s.begin()<<"]="<<*iter<<", "; //Error
//    cout<<*iter<<", ";
cout<<endl;
}

void main()
{
//建立set对象,共5种方式,提示若是比较函数对象及内存分配器未出现,即表示采用的是系统默认方式
//建立空的set对象,元素类型为int,
set<int> s1; 
//建立空的set对象,元素类型char*,比较函数对象(即排序准则)为自定义strLess
set<const char*, strLess> s2( strLess); 
//利用set对象s1,拷贝生成set对象s2
set<int> s3(s1); 
//用迭代区间[&first, &last)所指的元素,建立一个set对象
int iArray[] = {13, 32, 19};
set<int> s4(iArray, iArray + 3);
//用迭代区间[&first, &last)所指的元素,及比较函数对象strLess,建立一个set对象
const char* szArray[] = {"hello", "dog", "bird" };
set<const char*, strLess> s5(szArray, szArray + 3, strLess() );

//元素插入:
//1,插入value,返回pair配对对象,能够根据.second判断是否插入成功。(提示:value不能与set容器内元素重复)
//pair<iterator, bool> insert(value)
//2,在pos位置以前插入value,返回新元素位置,但不必定能插入成功
//iterator insert(&pos, value)
//3,将迭代区间[&first, &last)内全部的元素,插入到set容器
//void insert[&first, &last)
cout<<"s1.insert() : "<<endl;
for (int i = 0; i <5 ; i++)
    s1.insert(i*10);
printSet(s1);

cout<<"s1.insert(20).second = "<<endl;;
if (s1.insert(20).second)
    cout<<"Insert OK!"<<endl;
else
    cout<<"Insert Failed!"<<endl;

cout<<"s1.insert(50).second = "<<endl;
if (s1.insert(50).second)
{cout<<"Insert OK!"<<endl; printSet(s1);}
else
    cout<<"Insert Failed!"<<endl;

cout<<"pair<set<int>::iterator::iterator, bool> p;\np = s1.insert(60);\nif (p.second):"<<endl;
pair<set<int>::iterator::iterator, bool> p;
p = s1.insert(60);
if (p.second)
{cout<<"Insert OK!"<<endl; printSet(s1);}
else
   cout<<"Insert Failed!"<<endl;

//元素删除
//1,size_type erase(value) 移除set容器内元素值为value的全部元素,返回移除的元素个数
//2,void erase(&pos) 移除pos位置上的元素,无返回值
//3,void erase(&first, &last) 移除迭代区间[&first, &last)内的元素,无返回值
//4,void clear(), 移除set容器内全部元素

cout<<"\ns1.erase(70) = "<<endl;
s1.erase(70);
printSet(s1);
cout<<"s1.erase(60) = "<<endl;
s1.erase(60);
printSet(s1);

cout<<"set<int>::iterator iter = s1.begin();\ns1.erase(iter) = "<<endl;
set<int>::iterator iter = s1.begin();
s1.erase(iter);
printSet(s1);

//元素查找
//count(value)返回set对象内元素值为value的元素个数
//iterator find(value)返回value所在位置,找不到value将返回end()
//lower_bound(value),upper_bound(value), equal_range(value) 略
cout<<"\ns1.count(10) = "<<s1.count(10)<<", s1.count(80) = "<<s1.count(80)<<endl;
cout<<"s1.find(10) : ";
if (s1.find(10) != s1.end()) 
    cout<<"OK!"<<endl;
else
    cout<<"not found!"<<endl;

cout<<"s1.find(80) : ";
if (s1.find(80) != s1.end()) 
    cout<<"OK!"<<endl;
else
    cout<<"not found!"<<endl;

//其它经常使用函数
cout<<"\ns1.empty()="<<s1.empty()<<", s1.size()="<<s1.size()<<endl;
set<int> s9;
s9.insert(100);
cout<<"s1.swap(s9) :"<<endl;
s1.swap(s9);
cout<<"s1: "<<endl;
printSet(s1);
cout<<"s9: "<<endl;
printSet(s9);
//lower_bound,upper_bound,equal_range(略)
}


///////////////i测试结果/////////////////////////
s1.insert() :
0, 10, 20, 30, 40,
s1.insert(20).second =
Insert Failed!
s1.insert(50).second =
Insert OK!
0, 10, 20, 30, 40, 50,
pair<set<int>::iterator::iterator, bool> p;
p = s1.insert(60);
if (p.second):
Insert OK!
0, 10, 20, 30, 40, 50, 60,

s1.erase(70) =
0, 10, 20, 30, 40, 50, 60,
s1.erase(60) =
0, 10, 20, 30, 40, 50,
set<int>::iterator iter = s1.begin();
s1.erase(iter) =
10, 20, 30, 40, 50,

s1.count(10) = 1, s1.count(80) = 0
s1.find(10) : OK!
s1.find(80) : not found!

s1.empty()=0, s1.size()=5
s1.swap(s9) :
s1:
100,
s9:
10, 20, 30, 40, 50,
相关文章
相关标签/搜索