编程语言C/C++(六)—— STL(一)

标准模板库

C++ STL 的实现:

序列式容器(元素的位置与插入的时机、地点有关)(线性结构)

1.vector 底层数据结构为数组 ,支持快速随机访问

2.list 底层数据结构为双向链表,支持快速增删

3.deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问。deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:
[堆1] --> [堆2] -->[堆3] --> …

每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.

4.stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时

5.queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)

6.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现

关系式容器(元素的位置与插入的时机、地点无关)

7.set 底层数据结构为红黑树,有序,不重复

8.multiset 底层数据结构为红黑树,有序,可重复

9.map 底层数据结构为红黑树,有序,不重复

10.multimap 底层数据结构为红黑树,有序,可重复

11.hash_set 底层数据结构为hash表,无序,不重复

12.hash_multiset 底层数据结构为hash表,无序,可重复

13.hash_map 底层数据结构为hash表,无序,不重复

14.hash_multimap 底层数据结构为hash表,无序,可重复

string

实现:

包装了一个char* ,尽量使用string而不是char*

用法:
	查找(从头到尾方向):
			str.find("abc",3);  //查找"abc"第一次出现的位置,从位置3开始搜索
	
	查找(从尾到头方向):
	    str.rfind("abc",9);  //查找"abc"最后一次出现的位置,从9开始查找str最后一次出现的地方,
	    注意位置是向前移动!
	
	访问:
	    使用[]访问出界会使程序down掉,而使用at成员函数访问出界则是抛异常out_of_range
	
	拼接:
			str += "abc";
			str.append("def");
			
	替换:
			str.replace(0,2,"abcd");  //将0开始的2个字符替换为"abcd"
			
	比较:
			str.compare("abcd");
	
	子串:
			str1 = str.substr(0,5);  //返回由0开始的5个字符组成的字符串
	
	插入:
			str.insert(2,"abc");  //从2位置插入"abc"
	
	删除:
			str.erase(0,5);   //删除从0开始的5个字符

vector(动态数组,单口容器)

  • vector 的数据安排以及操作方式,与 array 非常相似。两者的唯一区别在于空间的运用的灵活性。array 是静态空间,一旦配置了就不能改变,vector 是动态数组。在堆上分配空间。
用法:
    定义:
        vector<T> vec;
        
        int arr[] = {1,2,3,4};
        vector<int> v(arr,arr+sizeof(arr)/sizeof(int)); //原型是vector(v.begin(), v.end())

    插入元素:
        vec.push_back(element);
        vec.insert(iterator, element);

    删除元素:
        vec.pop_back();
        vec.erase(iterator);  //删除操作,返回下一个元素的迭代器

    赋值:
        vec.assign(v1.begin(), v1.end());
        v2 = vec;
        
    改变元素的值(注意,是改变!不是初始化):
    		vec[i] = j;

    遍历容器:
        for(auto it = vec.begin(); it != vec.end(); ++it) {......}
        
    取元素:
					vec.front();
					vec.top();
					vec.back();

    其他:
        vec.empty();    //判断是否空
        vec.size();    // 实际元素
        vec.capacity();    // 容器容量
        vec.begin();    // 获得首迭代器
        vec.end();    // 获得尾迭代器
        vec.clear();    // 清空
        vec.resize(10,0);  //改变动态数组的大小为10个,多出来的用0进行初始化,注意缩小空间时是
        不改变容量的!增加空间时,容量和大小都增大到同一个值
        
        vec.swap(v1);   //将v1、vec的元素交换,注意这里改变的是数据指针的指向
        
        **巧用swap进行内存收缩**
        		
        		vector<int>(n).swap(n);  //这里我们用n初始化一个匿名对象,然后将匿名对象的数据
        		与n的数据进行交换,匿名对象在执行完之后会自动销毁,实现n的内存收缩
        		
        **reserve成员函数(预留空间)**
        		如果你知道容器大概要存储的元素个数,建议使用reserve预留空间,减少数据的拷贝次数
        		
        		- reserver与resized的区别
        				- reserve是容器预留空间,但在空间内不真正创建元素对象(没有初始化、访问
        				不到),所以在没有添加新的对象之前,不能引用容器内的元素
        				
        				- resize是改变容器的大小,且在重建对象,因此在调用这个函数之后就可以
        				引用容器内的元素了
实现原理:
  • 线性表,数组实现,动态数组,单口容器。

  • 支持随机访问。

  • 插入删除操作需要大量移动数据。

  • 需要连续的物理存储空间。

  • 每当大小不够时,重新分配内存(*2)(1~2倍时间复杂度比较低),并复制原内容,事实上,vector的增长倍数是会变化的。

错误避免:
  • 插入元素

    • 尾后插入:size < capacity时,首迭代器不失效尾迭代失效(未重新分配空间),size == capacity时,所有迭代器均失效(需要重新分配空间)。

    • 中间插入:size < capacity时,首迭代器不失效但插入元素之后的所有迭代器失效,size == capacity时,所有迭代器均失效。

  • 删除元素

    • 尾后删除:只有尾迭代失效。

    • 中间删除:删除位置之后的所有迭代失效。

总结:

vector是个动态数组,当空间不足的时候插入新元素,vector会重新申请一块更大的内存空间,将旧空间数据拷贝到新空间,然后释放旧空间。

vector是单口容器,所以在尾端插入和删除元素效率较高,在指定位置插入,势必会引起数据元素移动,效率较低。

vector 常用来保存需要经常进行随机访问的内容,并且不需要经常对中间元素进行添加删除操作。

如果你需要高效的随机存取,而不在乎插入和删除的效率,使用 vector。

deque(双端队列)

实现原理:

deque 是一种双向开口的连续线性空间,元素也是在堆中。所谓双向开口,意思是可以在队尾两端分别做元素的插入和删除操作。deque 和 vector 的最大差异,一在于 deque 允许于常数时间内对起头端进行元素的插入或移除操作,二在于deque没有所谓容量观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接在一起。换句话说,像 vector 那样“因旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在 deque 是不会发生的。它的保存形式如下:
[堆1] --> [堆2] -->[堆3] --> …

deque 是由一段一段的定量连续空间构成。一旦有必要在 deque 的前端或尾端增加新空间,便配置一段定量连续空间,串接在整个 deque 的头端或尾端。deque 的最大任务,便是在这些分段的定量连续空间上,维护其整体连续的假象,并提供随机存取的接口。避开了“重新配置,复制,释放”的轮回,代价则是复杂的迭代器架构。因为有分段连续线性空间,就必须有中央控制器,而为了维持整体连续的假象,数据结构的设计及迭代器前进后退等操作都颇为繁琐。

deque 采用一块所谓的 map 作为主控。这里的 map 是一小块连续空间,其中每个元素都是指针,指向另一段连续线性空间,称为缓冲区。缓冲区才是 deque 的存储空间主体。( 底层数据结构为一个中央控制器和多个缓冲区)SGI STL 允许我们指定缓冲区大小,默认值 0 表示将使用 512 bytes 缓冲区。

用法:
	定义:
        deque<T> d;
        deque d2(d1.begin(), d1.end())
        deque d(10,5);  //初始化为10个5

    插入元素:
        d.push_back(element);
        d.push_front(element);
        d.insert(iterator, element);

    删除元素:
        d.pop_back();
        d.pop_front();

    赋值:
        d.assign(d1.begin(), d1.end());
        d2 = d;
        d.swap(d1);

    遍历容器:
        for(auto it = d.begin(); it != d.end(); ++it) {......}
        
    取元素:
					d.front();
					d.back();
					d.at(0);
					d[0];

    其他:
        d.empty();    //判断是否空
        d.size();    // 实际元素
        d.resize(10,0);    // 重新指定容器的长度为10,若容器变长,则以0填充新位置,如果变短,
        则末尾超出容器长度的元素被删除
        
        d.begin();    // 获得首迭代器
        d.end();    // 获得尾迭代器
        d.clear();    // 清空

			**补充:**
					sort(d.begin(), d.end(), mycompare);  //algorithm下的排序函数,默认
					是从小到大排序,可以提供一个函数(比较规则)
					//这里的sort只能对能够进行随机访问的容器使用,因此list容器是不能使用的,
					list有自己的排序函数
总结:

deque 在开始和最后添加元素都一样快,并提供了随机访问方法,像vector一样使用 [] 访问任意元素,但是随机访问速度比不上vector快,因为它要内部处理堆跳转。

deque 也有保留空间。另外,由于 deque 不要求连续空间,所以可以保存的元素比 vector 更大,这点也要注意一下。还有就是在前面和后面添加元素时都不需要移动其它块的元素,所以性能也很高。

如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque。

stack

实现原理:

stack 是一种先进后出(First In Last Out , FILO)的数据结构。它只有一个出口,stack 允许新增元素,移除元素,取得最顶端元素。但除了最顶端外,没有任何其它方法可以存取stack的其它元素,stack不允许遍历行为,没有迭代器。s.top()、s.push()、s.pop()

以某种容器( 一般用 list 或 deque 实现,封闭头部即可,不用 vector 的原因应该是容量大小有限制,扩容耗时)作为底部结构,将其接口改变,使之符合“先进后出”的特性,形成一个 stack,是很容易做到的。deque 是双向开口的数据结构,若以 deque 为底部结构并封闭其头端开口,便轻而易举地形成了一个stack。因此,SGI STL 便以 deque 作为缺省情况下的 stack 底部结构,由于 stack 系以底部容器完成其所有工作,而具有这种“修改某物接口,形成另一种风貌”之性质者,称为 adapter(配接器),因此,STL stack 往往不被归类为 container(容器),而被归类为 container adapter(容器配接器)。

queue

实现原理:

queue 是一种先进先出(First In First Out,FIFO)的数据结构。它有两个出口,queue 允许新增元素,移除元素,从最底端加入元素,取得最顶端元素。但除了最底端可以加入,最顶端可以取出外,
没有任何其它方法可以存取 queue 的其它元素。它与stack一样不支持遍历行为。q.front()、q.back()、q.push()、q.pop()

以某种容器( 一般用 list 或 deque 实现,封闭头部即可,不用 vector 的原因应该是容量大小有限制,扩容耗时)作为底部结构,将其接口改变,使之符合“先进先出”的特性,形成一个 queue,是很容易做到的。deque 是双向开口的数据结构,若以 deque 为底部结构并封闭其底部的出口和前端的入口,便轻而易举地形成了一个 queue。因此,SGI STL 便以 deque 作为缺省情况下的 queue 底部结构,由于 queue 系以底部容器完成其所有工作,而具有这种“修改某物接口,形成另一种风貌”之性质者,称为 adapter(配接器),因此,STL queue 往往不被归类为container(容器),而被归类为 container adapter。

总结:

stack 和 queue 其实是适配器,而不叫容器,因为是对容器的再封装。

list

实现原理:

相对于 vector 的连续空间,list 就显得复杂许多,它的好处是每次插入或删除一个元素,就配置或释放一个元素空间,元素也是在堆中。因此,list 对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素移除,永远是常数时间。STL 中的list 底层是一个双向链表,而且是一个环状双向链表。这个特点使得它的随即存取变的非常没有效率,因此它没有提供 [] 操作符的重载,即它是不支持随机存取的。

用法:
	大部分函数与之前的一样,这里就列举几个比较特殊的:
	
			删除匹配的元素:
					l.remove(10);  //删除容器中所有与10匹配的元素
					
			list反转:
					l.reverse();  //反转链表,比如l包含1,3,5元素,反转后就变成5,3,1了
			
			list排序:
					l.sort();  //list排序,默认从小到大,一样可以传递函数(排序规则),
					如l.sort(mycompare);
总结:

如果你喜欢经常添加删除大对象的话,那么请使用 list;

要保存的对象不大,构造与析构操作不复杂,那么可以使用 vector 代替。

list<指针> 完全是性能最低的做法,这种情况下还是使用 vector<指针> 好,因为指针没有构造与析构,也不占用很大内存

heap

实现原理:

STL中并没有把heap作为一种容器组件,heap的实现亦需要更低一层的容器组件(诸如list,array,vector)作为其底层机制。Heap是一个类属算法,包含在algorithm头文件中。虽然STL中关于heap默认调整成的是大顶堆,但却可以让用户利用自定义的compare_fuction函数实现大顶堆或小顶堆。heap的低层机制vector本身就是一个类模板,heap基于vector便实现了对各种数据类型(无论基本数据类型还是用户自定义的数据类型)的堆排(前提是用户自定义的数据类型要提供比较机制compare_fuction函数)。

用法:
	STL里面的堆操作一般用到的只有4个。他们就是:

			make_heap();、pop_heap();、push_heap();、sort_heap();他们的头函数是algorithm

			建立堆:void make_heap(first_pointer,end_pointer,compare_function);

					一个参数是数组或向量的头指针,第二个向量是尾指针。第三个参数是比较函数的名字。
					在缺省的时候,默认是大根堆。(下面的参数都一样就不解释了)
					作用:把这一段的数组或向量做成一个堆的结构。范围是(first,last),这里的end是
					最后一个元素的下一个位置。

			弹出元素:void pop_heap(first_pointer,end_pointer,compare_function);

					作用:pop_heap()不是真的把最大(最小)的元素从堆中弹出来。而是重新排序堆。
					它把first和last交换,然后将[first,last-1)的数据再做成一个堆。

			放入元素:void push_heap(first_pointer,end_pointer,compare_function);

					作用:push_heap()假设由[first,last-1)是一个有效的堆,然后,再把堆中的新元
					素加进来,做成一个堆。

			对堆进行排序:void sort_heap(first_pointer,end_pointer,compare_function);

					作用是sort_heap对[first,last)中的序列进行排序。它假设这个序列是有效堆。
					(当然,经过排序之后就不是一个有效堆了)

priority_queue

priority_queue 优先队列,其底层是用堆来实现的。在优先队列中,队首元素一定是当前队列中优先级最高的那一个。在优先队列中,没有 front() 函数与 back() 函数,而只能通过 top() 函数来访问队首元素(也可称为堆顶元素),也就是优先级最高的元素。基本操作有:

			empty() 如果队列为空返回真
			pop() 删除对顶元素
			push() 加入一个元素
			size() 返回优先队列中拥有的元素个数
			top() 返回优先队列对顶元素

priority_queue 默认为大顶堆,即堆顶元素为堆中最大元素(比如:在默认的int型中先出队的为较大的数)。基本数据类型(int,double,char等可以直接使用的数据类型),优先队列对他们的优先级设置一般是数字大的优先级高,因此队首元素就是优先队列内元素最大的那个(如果是 char 型,则是字典序最大的)。

如果我们想要用小顶堆的话需要增加使用两个参数:

			priority_queue< int, vector<int>, greater<int> > q;  // 小顶堆
			priority_queue< int, vector<int>, less<int> > q;     // 大顶堆  从大到小  
			//基本数据类型我们可以直接使用greater和less,自己的一些类型可以传递一个自己的
			比较仿函数进去

	下面两种优先队列的定义是等价的:
			priority_queue< int > q;
			priority_queue< int,vector<int>,less<int> >;

			其中第二个参数( vector ),是来承载底层数据结构堆的容器,第三个参数( less ),则是
			一个比较类,less 表示数字大的优先级高,而 greater 表示数字小的优先级高。

猜你喜欢

转载自blog.csdn.net/weixin_38337616/article/details/88954689
今日推荐