疯子的算法总结(三) STL Ⅱ迭代器(iterator) + 容器
一、迭代器(Iterator)
背景:指针可以用来遍历存储空间连续的数据结构,但是对于存储空间费连续的,就需要寻找一个行为类似指针的类,来对非数组的数据结构进行遍历。
定义:迭代器是一种检查容器内元素并遍历元素的数据类型。
迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。
迭代器(Iterator)是指针(pointer)的泛化,它允许程序员用相同的方式处理不同的数据结构(容器)。
(1)迭代器类似于C语言里面的指针类型,它提供了对对象的间接访问。
(2)指针是C语言中的知识点,迭代器是C++中的知识点。指针较灵活,迭代器功能较丰富。
(3)迭代器提供一个对容器对象或者string对象的访问方法,并定义了容器范围。
迭代器和指针的区别:
容器和string有迭代器类型同时拥有返回迭代器的成员。如:容器有成员begin和end,其中begin成员复制返回指向第一个元素的迭代器,而end成员返回指向容器尾元素的下一个位置的迭代器,也就是说end指示的是第一个不合法地址,所以end返回的是尾后迭代器。
容器迭代器的使用
每种容器类型都定义了自己的迭代器类型,如vector:vector< int>:: iterator iter;//定义一个名为iter的变量,数据类型是由vector< int>定义的iterator 类型。简单说就是容器类定义了自己的iterator类型,用于访问容器内的元素。每个容器定义了一种名为iterator的类型,这种类型支持迭代器的各种行为。
我么们先讲一下各种迭代器的类型,在讲容器所用的迭代器类型,就可以明白怎么操作。
常见迭代器类型如下:
|--|--|
所有迭代器| 操作
p++ | 后置自增迭代器
++p | 前置自增迭代器
输入迭代器|操作介绍
*p| 复引用迭代器,作为右值
p=p1 | 将一个迭代器赋给另一个迭代器(迭代器指向地址值)
p==p1| 比较迭代器的相等性(比较地址)
p!=p1 |比较迭代器的不等性
输出迭代器|操作
*p|复引用迭代器,作为左值
|p=p1|将一个迭代器赋给另一个迭代器|
|正向迭代器| 提供输入输出迭代器的所有功能|
|双向迭代器|操作|
|--p|前置自减迭代器
|p--|后置自减迭代器
|随机迭代器| |
|p+=i| 将迭代器递增i位|
|p-=i|将迭代器递减i位|
|p+i|在p位加i位后的迭代器|
p-i|在p位减i位后的迭代器|
p[i]|返回p位元素偏离i位的元素引用|
p<p1|如果迭代器p的位置在p1前,返回true,否则返回false|
p<=p1|p的位置在p1的前面或同一位置时返回true,否则返回false|
p>p1|如果迭代器p的位置在p1后,返回true,否则返回false|
p>=p1|p的位置在p1的后面或同一位置时返回true,否则返回false|
只有顺序容器和关联容器支持迭代器遍历,各容器支持的迭代器的类别如下:
容器 | 支持的迭代器类别 | 说明 |
---|---|---|
vector | 随机访问 | 一种随机访问的数组类型,提供了对数组元素进行快速随机访问以及在序列尾部进行快速的插入和删除操作的功能。可以再需要的时候修改其自身的大小 |
deque | 随机访问 | 一种随机访问的数组类型,提供了序列两端快速进行插入和删除操作的功能。可以再需要的时候修改其自身的大小 |
list | 双向 | 一种不支持随机访问的数组类型,插入和删除所花费的时间是固定的,与位置无关。 |
set | 双向 | 一种随机存取的容器,其关键字和数据元素是同一个值。所有元素都必须具有惟一值。 |
multiset | 双向 | 一种随机存取的容器,其关键字和数据元素是同一个值。可以包含重复的元素。 |
map | 双向 | 一种包含成对数值的容器,一个值是实际数据值,另一个是用来寻找数据的关键字。一个特定的关键字只能与一个元素关联。 |
multimap | 双向 | 一种包含成对数值的容器,一个值是实际数据值,另一个是用来寻找数据的关键字。一个关键字可以与多个数据元素关联。 |
stack | 不支持 | 适配器容器类型,用vector,deque或list对象创建了一个先进后出容器 |
queue | 不支持 | 适配器容器类型,用deque或list对象创建了一个先进先出容器 |
priority_queue | 不支持 | 适配器容器类型,用vector或deque对象创建了一个排序队列 |
二、容器
所有容器都支持自定义数据类型,就是结构体。
(一) vector
使用此容器需在程序前加上头文件#include< vector >。
vector可理解为变长数组,基于倍增思想。当以已申请vector长度为m时,若实际长度n=m,则申请长度为2m的数组,将内容转移至新地址上,并释放旧空间;删除元素时,若n<=m/4,则释放一半空间。
vector容器能像数组一样随机访问第i个数a[i],但不支持随机插入.
#include<vector> //头文件
vector<int> a; 定义了一个int类型的vector容器a
vector<int> b[100];定义了一个int类型的vector容器b组
struct rec{···};
vector<rec> c; /定义了一个rec类型的vector容器c
vector<int>::iterator it; //vector的迭代器,与指针类似
具体操作如下:
a.size() //返回实际长度(元素个数),O(1)复杂度
a.empty() //容器为空返回1,否则返回0,O(1)复杂度
a.clear() //把vector清空
a.begin() //返回指向第一个元素的迭代器,*a.begin()与a[0]作用相同
a.end() //越界访问,指向vector尾部,指向第n个元素再往后的边界
a.front() //返回第一个元素的值,等价于*a.begin和a[0]
a.back() //返回最后一个元素的值,等价于*--a.end()和a[size()-1]
a.push_back(x) //把元素x插入vector尾部
a.pop_back() //删除vector中最后一个元素
迭代器使用与指针类似,可如下遍历整个容器
for ( vector<int>::iterator it=a.begin() ; it!=a.end() ; it++ )
queue
循环队列queue需使用头文件< queue >
queue<int> q; //定义了一个int类型的队列容器q
struct rec{···};queue<rec> q; //定义了一个rec类型的队列容器q
q.push(x); //从队尾使元素x入队,O(1)
q.pop(x); //使队首元素出队,O(1)
int x=q.front(); //询问队首元素的值,O(1)
int y=q.back(); //询问队尾元素的值,O(1)
priority_queue
优先队列priority_queue可理解为一个大根二叉堆,必须定义“小于号”,而int,string本身就能比较。同样需要头文件< queue >。
其定义方式与queue相似。
priority_queue<int> q; 由大到小
priority_queue<pair<int,int>> q; //pair是一个数对,由first和scond两个元素构成,按照第一个排序
priority_queue<int, vector<int>, greater<int> >qi2; //由小到大,小根堆,vector<int>是适配器,不用知道很详细,记住就行
struct rec //举个栗子
{
int a,b,c;
bool operator<(const rec&w)
{
if(a==w.a) return b==w.b?c<w.c:b<w.b;
return a<w.a;
}
};
priority_queue<rec> q; 如果不写重载函数,会出错,他不知道怎么排序
q.push(x); //插入 O(log n)
q.pop(); //删除堆顶元素 O(log n)
q.top(); //查询堆顶元素 O(1)
可通过插入元素的相反数取出时再取反,或重载“小于号”的方式实现小根堆,通过懒惰删除法实现随机删除操作。
deque
双端队列,是一个支持在两端高效插入或删除元素的连续线性存储空间,可像数组一样随机访问,使用前加头文件< deque >。
q.begin()/q.end() //头/尾迭代器,与vector类似
q.front()/q.back() //头/尾元素,与queue类似
q.push_back(x)/q.push_front(x) //从队尾/队头入队
q.pop_back(x)/q.pop_front(x) //从队尾/队头出队
q.clear() //清空队列
定义方式
deque<类型> 名称
ps:clear复杂度为O(n),其余为O(1)。
set/multiset
两容器相似,但set为有序集合,元素不能重复,multiset为有序多重集合,可包含若干相等的元素,内部通过红黑树实现,支持的函数基本相同,同样必须定义“小于号”运算符,头文件为< set >。
其迭代器不支持随机访问,支持星号(*)结束引用,仅支持 ++ 、-- 两个与算术有关的操作。迭代器it++,则指向从小到大排序的结果中排在it下一名的元素,两操作时间复杂度均为O(log n)。
q.size() //返回容器内元素个数
q.empty() //判断容器是否为空
q.clear() //清空容器
q.begin()/q.end() //作用与上文几种容器类似
q.insert(x) //将元素x插入集合中,O(log n)
q.find(x) //查找等于x的元素,返回其迭代器,无则返回q.end(),O(log n)
q.lower_bound(x) //查找>=x的元素中最小的一个,返回指向该元素的迭代器
q.upper_bound(x) //查找>x的元素中最小的一个,返回指向该元素的迭代器
q.erase(it) //删除迭代器it指向的元素,O(log n)
q.erase(x) //删除所有等于x的元素,复杂度为O(k+log n),k为被删除的元素个数
q.count(x) //返回等于x的元素个数,O(k+log n),k为元素x的个数
定义方式
set<int> demo 定义一个类型为int的set容器
struct rec
{
int a,b,c;
bool operator<(const rec&w)
{
if(a==w.a) return b==w.b?c<w.c:b<w.b;
return a<w.a;
}
};
set<rec> ob; 一样所有排序的容器不重载就出错
map/multimap
map/multimap映射容器的元素数据是由一个Key和一个Value成的,key与映照value之间具有一一映照的关系。
map/multimap容器的数据结构也采用红黑树来实现的,map插入元素的键值不允许重复,类似multiset,multimap的key可以重复。比较函数只对元素的key进行比较,元素的各项数据只能通过key检索出来。虽然map与set采用的都是红黑树的结构,但跟set的区别主要是set的一个键值和一个映射数据相等,Key=Value。
map<first,second> a;
//map,会按照first(键值)排序(查找也是);
map/multimap用法
头文件
#include< map >
map成员函数
begin() //返回指向 map 头部的迭代器
clear() // 删除所有元素
count() //返回指定元素出现的次数
empty() // 如果 map 为空则返回 true
end() //返回指向 map 末尾的迭代器
erase() // 删除一个元素
find() // 查找一个元素
insert() //插入元素
key_comp() //返回比较元素 key 的函数
lower_bound() //返回键值>=给定元素的第一个位置
max_size() //返回可以容纳的最大元素个数
rbegin() //返回一个指向 map 尾部的逆向迭代器
rend() //返回一个指向 map 头部的逆向迭代器
size() //返回 map 中元素的个数
swap() //交换两个 map
创建map对象
#include<iostream>
#include<map>
using namespace std;
map<int,char>mp;//定义map容器
创建结构体map对象
struct student{
int birth;
string name;
};
int id;
typedef map<int,student> Student;// 这里相当于给map<int,student> 起了个别名Student,后续代码均可以用student代替map<int,student> 使用。
插入结构体对象
接上文代码
Stduent a;
cin>>id>>student.birth>>student.name;
a.insert(make_pair(id,student));
栈(stack)
1.定义:
栈是一种只能在某一端插入和删除数据的特殊线性表。他按照先进先出的原则存储数据,先进的数据被压入栈底,最后进入的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后被压入栈的,最先弹出)。因此栈也称先进后出表。
允许进行插入删除操作的一端称为栈顶,另一端称为栈底。栈底固定,栈顶浮动。插入元素称为进栈,删除一个元素称为进栈,栈内元素为零称为空栈。
2.stack成员函数
bool empty ( ) ————>栈为空返回true,否则返回false;
void pop ( ) ————>删除栈顶元素,出栈;
void push(const TYPE&value)————> 插入新元素value,放置在栈顶进栈;TYPE:类型int,char…;
size_type size ( ) ————> 返回堆栈中元素个数;(注意注意!!!!切不可赋值给int ,很容易超过int的范围
TYPE&top()————> 查看当前栈顶元素;
List
定义:List
作用:
泛型最常见的用途是泛型集合
我们在创建列表类时,列表项的数据类型可能是int,string或其它类型,如果对列表类的处理方法相同,
就没有必要事先指定数据类型,留待列表类实例化时再指定。相当于把数据类型当成参数,这样可以最
大限度地重用代码,保护类型的安全以及提高性能。
定义 list<类型> 名称
成员函数
l.begin() 将迭代器返回到开头(Return iterator to beginning)
l.end() 将迭代器返回到最后(Return iterator to end)
l.rbegin() Return reverse iterator to reverse beginning
l.rend() Return reverse iterator to reverse end
l.l.empty() 检查容器是否为空
l.size() 返回当前容器内元素个数
l.max_size() 返回当前容器能容纳的最大元素数量
l.front() 访问第一个元素
l.back() 访问最后一个元素
l.push_front() 将元素插入到开头
l.pop_front() 删除第一个元素
l.push_back() 将元素插入到最后
l.pop_back() 删除最后一个元素
l.insert() 插入元素
l.erase() 删除元素
l.swap() 交换两个 list 内容
l.resize ()改变容器大小
l.clear() 删除容器所有内容
bitset
bitset可看作一个多位二进制数,每8位占用1个字节,相当于采用了状态压缩的二进制数组,并支持基本的位运算。一般以32位整数的运算次数为基准估算运行时间,n位bitset执行一次的位运算复杂度可视为n/32,效率较高。头文件< bitset >。
同样具有~,&,|,^,<<,>>操作符,==,!=可比较二进制数是否相等
bitset<10000> q; //声明一个10000位的二进制数
q[k] //表示q的第k位,可取值,赋值,最低位为q[0]
q.count() //返回有多少位1
q.none() //所有位都为0则返回true,至少1位为1则返回false
q.any() //所有位都为0则返回false,至少1位为1则返回true,与函数none相反
q.set() //把所有位变为1
q.set(k,v) //把第k位变为v,即q[k]=v
q.reset() //把所有位变为0
q.reset(k) //把第k位变为0,即q[k]=0
q.flip() //把所有位取反,即s=~s
q.flip(k) //把第k位取反,即q[k]^=1