c++ queue
1、定义
2、核心接口
queues主要由成员函数push(),front(),back(),pop()构成。
- push()将元素置入queue中。
- front()会返回queue内的下一个元素(也就是第一个被置入的元素)
- back()会返回queue的最后一个元素(也就是最后一个被插入的元素)
- pop()会从queue中移除一个元素。
- size()返回stack长度。
- empty()返回stack是否为空。
#include<stdio.h> #include<queue> using namespace std; int main(){ queue<int> q; if(q.empty()==true){//输出Empty printf("Empty\n"); }else{ printf("Not Empty\n"); } for(int i = 1; i <= 5; i++){ q.push(i); } for(int i = 1; i <= 3; i++){ q.pop(); } printf("%d\n",q.front()); printf("%ld\n",q.size()); }
root@ubuntu:~/c++# g++ -std=c++11 queue.c -o queue root@ubuntu:~/c++# ./queue Empty 4 2
双端队列deque
deque是一个双端队列,即可以头插和尾插,也可以头删和尾删。它的优点就是结合了vector与list两个的优点,可是实现随机访问和头插头删,在空间不够需要扩容时也不需要像vector那样复杂,只需要在原来空间的基础上加入新的空间即可。
虽然deque具有vector与list的优点,但是由于其复杂的结构,导致其有些操作效率非常低下,譬如排序,直接在deque中排序甚至不如先把数据拷贝到vector中,在vector中排序后在拷贝回deque。
deque的底层空间可以说是连续的,又不是绝对连续的。deque的实现是通过控制多个数组来实现的。内部有一个map表,每一项都是一个只想一段连续物理空间(缓冲区)的指针。数据放在那段连续的空间里。
- 每当需要访问元素时,先通过map找到具体存放在哪一块空间。然后在通过指针进行访问。虽然不是直接通过指针访问,但是比起list来看访问效率也是提高不少。
- 当需要进行头插时,找到map的第一个有效元素,该指针指向的空间若还有空余,则直接插入,没有空间则在动态开辟一块空间,将指针放在map的第一个有效元素之前。然后将元素存放在新开辟的空间的最后一个位置。也不需要移动元素。与list的效率一样高。若map已满,则重新开辟空间并将原来的map拷贝到新的map。
deque实现原理
内存结构
deque使用多段连续内存空间(每段内存空空间大小一致)来存储数据元素,插入元素时可根据需要重新增加一段内存空间。
为了维护多段内存空间,使其在使用上如同一段连续的内存空间,deque设计了一个中控器map(数组),中控器记录着每一段内存空间的地址。其结构如下:
数据访问
deque除了维护了一个中控器map;其还维护着start和finish两个迭代器,分别指向deque的首尾;此外,还必须记录map的大小,一旦map的空间不够,则配置一个更大的map;还记录了指定的内存段的大下。
有了以上这些数据信息,就可以实现对多段内存空间的随机访问了。
插入操作
1、头尾插入
- 如果备用空间足够,就直接push进去
- 如果备用空间不足,就要考虑配置一个新的缓冲区
- 配置新缓冲区的时候, 如果map空间足够,就直接配置一块新的缓冲区,链接到map中
- 如果map空间不足,就需要考虑重新配置一块map
2、中间插入(理解时就把deque多段内存看成一段连续内存空间)
中间插入时其实基本原理和vector一致,都需要进行元素位置的移动;只不过因为deque是双端的,所以会根据插入元素位置前后的元素个数来决定是移动前面的元素还是移动后面的元素。
- 判断插入位置前面元素少还是后面元素少
- 前面元素少,则把插入位置前元素进行拷贝移动
- 后面元素少,则把插入位置后元素进行拷贝移动
迭代器
用户看起来deque使用的是连续空间,实际上是分段连续线性空间。为了管理分段空间deque容器引入了map,称之为中控器,map是一块连续的空间,其中每个元素是指向缓冲区的指针,缓冲区才是deque存储数据的主体。
在上图中,buffer称为缓冲区,显示map size的一段连续空间就是中控器。
中控器包含了map size,指向buffer的指针,deque的开始迭代器与结尾迭代器。
_Tp **_M_map;
size_t _M_map_size;
iterator _M_start;
iterator _M_finish;
由于buffer也是指针,所以_Tp
是指针的指针。
buffer计算是什么实现的呢?
在源码中计算是根据传递进来的类型,如果传递的类型小于512字节,那么buffersize就是512/sizeof(_Tp),超过512,就是1。
typedef _Tp **_Map_pointer; _Tp *_M_cur; _Tp *_M_first; _Tp *_M_last; _Map_pointer _M_node;
这几个究竟是什么呢,根据名字,很容易知道啥意思,对于deque来说,是分段连续空间,迭代器执行操作,上述的_M_cur
指向具体的元素,_M_first
指向这段buffer中的第一个元素,_M_last
指向最后一个元素(不是有效的元素),而_M_node
则是指向中控器。所以它是一个指针的指针。
例如现在迭代器执行++操作,当前buffer不够用了,那么此时需要一个指针能够回到中控器,取下一段buffer,重置_M_first
与_M_last
的指针位置,_M_cur
指向新段buffer中的指定位置。
我们现在回到一开始的图:
最上面的的iterator就是上面几个指针的区块配图。
deque和vector的区别
- 相同之处
1.支持随机访问,迭代器均属于random-access iterator;
2.基于中间位置的元素的移除和插入,速度都比较慢,因为要进行大量元素的移动和复制操作;
3.vector所支持的接口在deque上都能使用,且具有相同的效果。 - 不同之处
1.两端都能够进行快速的插入和移除操作;
2.访问deque时,内部结构会多一个间接过程,因此元素的访问以及迭代器的动作会相比vector较慢;
3.迭代器需要在不同的区块间进行跳转,因此迭代器必须是smart_pointer,不能是寻常pointer;
4.Deque不支持对容量大小的控制,需要特别注意的是,除了首尾两端,在任何地点安插或者删除元素都会导致pointer、reference和iterator的失效;
5.Deque重新分配内存优于vector,因为其内部结构显示,deque重新分配内存的时候,不需要复制所有的元素;
6.Deque会释放不需要的内存块,Deque的大小是可缩减的,但是要不要这么做,如何做,取决于编译器。
总结:显然,deque具有vector的特性,且比vector更强大,但C++之中,更强大的功能往往意味这更大的时空开销,如何在功能和开销上作取舍,取决于具体应用场景
#include <iostream> #include <deque> #include <string> #include <algorithm> #include<iterator> using namespace std; int main() { deque<string> strDeq; strDeq.assign(4,string("CHINA")); strDeq.push_back("first_string"); strDeq.push_front("last_string"); copy(strDeq.begin(),strDeq.end(), ostream_iterator<string>(cout," ")); cout << endl; cout << endl; for(int i = 0;i < strDeq.size();++i) cout << "strDeq[" << i << "] : " << strDeq[i] << endl; cout << endl; for(int i = 0;i < strDeq.size();++i) cout << "strDeq.at(" << i << ") : " << strDeq.at(i) << endl; cout << endl; strDeq.pop_front(); strDeq.pop_back(); copy(strDeq.begin(),strDeq.end(), ostream_iterator<string>(cout," ")); cout << endl; cout << endl; for(int i = 1;i < strDeq.size();++i) strDeq[i] = "pre" + strDeq[i]; copy(strDeq.begin(),strDeq.end(), ostream_iterator<string>(cout," ")); cout << endl; cout << endl; strDeq.resize(4,"resized string"); copy(strDeq.begin(),strDeq.end(), ostream_iterator<string>(cout," ")); cout << endl; cout << endl; }
last_string CHINA CHINA CHINA CHINA first_string strDeq[0] : last_string strDeq[1] : CHINA strDeq[2] : CHINA strDeq[3] : CHINA strDeq[4] : CHINA strDeq[5] : first_string strDeq.at(0) : last_string strDeq.at(1) : CHINA strDeq.at(2) : CHINA strDeq.at(3) : CHINA strDeq.at(4) : CHINA strDeq.at(5) : first_string CHINA CHINA CHINA CHINA CHINA preCHINA preCHINA preCHINA CHINA preCHINA preCHINA preCHINA
优先级队列priority_queue
优先级队列就是认为队列中的元素有权值,每当有元素入队时会根据权值为队列元素重新排序,可简单理解为建造堆。
使用优先级队列默认时建造一个大根堆。
#include<queue> #include<iostream> using namespace std; struct fruit{ string name; int price; }f1, f2, f3; struct cmp{ bool operator() (fruit f1, fruit f2){ return f1.price < f2.price; } }; int main(){ priority_queue<fruit, vector<fruit>, cmp> q; f1.name = "peach"; f1.price = 3; f2.name = "pear"; f2.price = 4; f3.name = "apple"; f3.price = 1; q.push(f1); q.push(f2); q.push(f3); cout << q.top().name << " " << q.top().price << endl; return 0; }
root@ubuntu:~/c++# mv nsexec.c prior.c root@ubuntu:~/c++# g++ -std=c++11 prior.c -o prior root@ubuntu:~/c++# ./prior pear 4