C++ STL

STL容器(常用)

题记:
这应该算是我写博客以来写的最长的一篇了hhh

1. #include < vector >

定义:

vector是变长数组(用多少开多少),是一个可以自动改变长度的数组,支持随机访问,不支持在任意位置O(1)插入。为了保证效率,元素的增删一般应该在末尾进行。

声明:
#include <vector>//头文件

vector<int> a;  //定义一个int类型的vector(相当于一个长度动态变化的int数组)

vector<int> b[233];//定义了233个vector,可以看成是个二维数组,第一维是静态,第二维是动态(相当于第一维长233,第二维长度动态变化的int数组)

struct rec

{

int x,y;//定义结构体

};

vector<rec> c;//自定义的结构体类型保存在vector中
关于vector的函数:
1. size/empty

size函数返回vector的实际长度(包含的元素个数)

empty函数返回一个bool类型,表明vector是否为空

二者的时间复杂度都是O(1)

(所有的STL容器都支持这两个方法,含义也都相同)

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> a;
	vector<int> b[233];
	

	a.size();//表示数组的长度
	a.empty();//表示数组是否为空(它返回的是bool值,若数组为空,返回true;若数组不是空的,则返回false) 
	
	return 0;

}
2. clear

clear函数把vector清空

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> a;
	vector<int> b[233];
	
	a.clear();//表示把当前数组清空(例:当前数组里有一些元素,若想删去则用clear )
	return 0;
}

注:除了队列、优先队列、栈之外,其他所有的STL容器都是有clear函数的

3.迭代器:

*迭代器就像STL容器的“指针”,可以用星号“ * ”操作符解除引用

一个保存int的vector的迭代器声明方法为:

vector<int>: :iterator it;

vector的迭代器是“随机访问迭代器”,可以把vector的迭代器与一个整数相加减,其行为和指针的移动类似,可以把vector的两个迭代器相减,其结果也和指针相减类似,得到两个迭代器对应下标之间的距离

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> a;
	vector<int> b[233];

	vector<int>::iterator it=a.begin();//begin是a的一个起始迭代器
	
	//迭代器是一个随机访问的迭代器,支持相加减 
	it+2;//访问的就是a[2]
	it;//访问的就是a[0] 
    *it;//取得迭代器的值
	return 0;

}
4. begin/end

begin函数返回指向vector中第一个元素的迭代器

例如a是一个非空的vector,则*a.begin()a[0]的作用相同

所有的容器都可以视作一个“前闭后开”的结构,end函数返回vector的尾部,即第n个元素再往后的“边界”

*a.end()a[n]都是越界访问,其中n=a.size()

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> a;
	vector<int> b[233];
    
	vector<int>::iterator it=a.begin();//begin是a的第一个元素的迭代器,也就是a的第一个元素的地址
	a.end();//end是最后一个位置的下一个位置 
	
    *a.begin();//效果和a[0]一样(因为begin是第一个迭代器,*就是取第一个迭代器里的值)
	return 0;

}
关于a.begin()a[0]效果一样的代码证明举例:

在这里插入图片描述

由此可证:*a.begin()效果和a[0]一样

关于遍历vector<int> a并输出它的所有元素的方法:

第一种:

代码:(像遍历数组一样)

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> a({1,2,3});//初始化一个vector,这个vector里包含三个值1、2、3
	
	for(int i=0;i<a.size();i++)
	{
		cout<<a[i]<<' ';
	}
	cout<<endl;
	 
	return 0;
}

结果:

在这里插入图片描述

第二种:

代码:(使用迭代器来遍历,就是从begin到end遍历)

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> a({1,2,3});//初始化一个vector,这个vector里包含三个值1、2、3
	
	for(vector<int>::iterator i=a.begin();i!=a.end();i++)
	{//这里vector<int>::iterator可以用auto来替代
		cout<<*i<<' ';
	}
	cout<<endl;

	return 0;
}

结果:

在这里插入图片描述

第三种:

代码:(范围遍历)

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> a({1,2,3});//初始化一个vector,这个vector里包含三个值1、2、3
	
    for(int x:a)
    {
    	cout<<x<<' ';
    }
	cout<<endl;

	return 0;
}

结果:

在这里插入图片描述

5.front/back

front函数返回vector的第一个元素,等价于*a.begin()a[0]

back函数返回vector的最后一个元素,等价于a[a.size()-1]

再次强调,*a.end()指的是vector的最后一位的后一位

第一条观点的证明:

在这里插入图片描述

第二条观点的证明:

在这里插入图片描述

6.push_back()和pop_back()

a.push_back(x)把元素x插入到vector a的尾部

a.pop_back()删除vector a的最后一个元素

第一条观点的证明:(时间复杂度O(1))

在这里插入图片描述

第二条观点的证明:

在这里插入图片描述

拓展:

vector如何实现动态增长空间???

(基于倍增的思想)

举例:

vector首先开一个长度是10的数组,然后每次往里面加元素,当我们想加第11个元素的时候,就会发现这个数组不够用了,那么我们就再开一个长度为20的数组,再把刚刚的前面10个数复制到现在的数组里,这样的话,新开的数组里就会多出10个位置,然后我们再继续往后写。写到20的时候就会发现又不够了,那么我们就又要开一个长度是40的数组,然后再把刚刚的20个复制到我们新开的这个数组里,然后…综上,虽然上面会涉及到一些关于数组的拷贝,但平均来看,如果我们插入n个数的话,那么我们会拷贝 n 2 \frac{n}{2} 2n+ n 4 \frac{n}{4} 4n+ n 8 \frac{n}{8} 8n+…(每次拷贝的数量是越往上越除以二),所以总共拷贝的数量是 n( 1 2 \frac{1}{2} 21+ 1 4 \frac{1}{4} 41+ 1 8 \frac{1}{8} 81+…) < n,这样看,虽然我们每次都会拷贝一遍,但是平均来看,我们拷贝数组的次数是小于n的。所以vector如果单次去private的话,可能某一次会比较慢,但是总共来看,它平均每次是O(1)的,所以很快

2.#include < queue >

头文件queue主要包括循环队列queue和优先队列priority_queue(实现返回所有队列里的最大值)两个容器

队列声明:
queue<int> q;//队列的定义

queue<double> a;//也可以换成double类型的队列

struct rec

{

       int a,x,y;//定义一个结构体

};

queue<rec> b;//定义这个结构体的队列

注:队列有一个“先进先出”的性质

——————

3 2 1

——————

这两根横线就相当于一个管道,(左边是入口,右边是出口)我们从左边开始先插入1,再插入2,再插入3,那么当我们往外弹的时候,就会先弹1,再弹2,再弹3

优先队列:

(插入时的顺序无所谓,往外弹的时候会先弹所有数的最大值)

例如插入1、2、3,那么它会优先往外弹3,然后再往外弹2,最后才是1

声明:
queue<int> q;//队列

priority_queue<int> a;//默认是大根堆

priority_queue<int,vector<int>,greater<int>>b;//小根堆

//优先队列里面也是可以换成其他类型的,如下:

//priority_queue<pair<int,int>>c;//pair是一个二元组

struct rec

{

   int a,b;//定义的结构体

   bool operator < (const rec& t) const

{

   return a < t.a;

}

};
priority_queue<rec> d;//若想定义一个结构体类型的优先队列的话,一定要重载"<"(因为是大根堆)(具体见上面bool开头的那段代码)

综上:如果是重载小根堆的话,就要用">";

如果是重载大根堆的话,就要用"<".

循环队列 queue

(队列长度与队列中现存元素的数量相关,与插入次数无关)

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int main()
{
   queue<int> q;

   q.push(1);//在队头插入一个元素

   q.pop();//弹出队尾元素

   q.front();//返回队头

   q.back();//返回队尾


}

优先队列 priority_queue
priority_queue<int> a;//大根堆

a.push(1);//插入一个数
a.top();//取最大值
a.pop();//删除最大值


清空队列
q=queue<int> ();//就是重新初始化一下

因为队列、优先队列、栈,都是没有clear函数的

3.#include < stack >

(栈和队列类似,但它是”先进后出“,意思是弹出的元素是最后插入的元素,其余类似)

声明:
#include <iostream>
#include <stack>

using namespace std;

int main()
{
	stack<int> stk;//栈的定义
	stk.push(1);//插入一个函数
	stk.top();//返回栈顶元素
	stk.pop();//删除栈顶元素,就是删除最后一个元素
	
	return 0; 
}

4.#include < deque >

定义:

队列:队头弹出,队尾插入

栈:队尾插入,队尾弹出

双端队列:没有限制

双端队列deque是一个支持在两端高效插入或删除元素的连续线性存储空间。它就像是vector和queue的结合。与vector相比,deque在头部增删元素仅需要O(1)的时间;与queue相比,deque像数组一样支持随机访问

声明:
#include <iostream>
#include <deque>

using namespace std;

int main()
{
	deque<int> a;//定义
	a.begin();//返回deque的头迭代器 
	a.end();//返回deque的尾迭代器 
	a.front();//队头元素 
	a.back(); //队尾元素
	
	a.push_back(1);//在队尾插入一个元素
	a.push_front(2);//在队头插入一个元素
	
	a[0];//随机访问一个元素
	
	a.pop_back();//弹出最后一个元素
	a.pop_front();//弹出第一个元素
	
	a.clear();//清空一个deque
	
	return 0; 
}

5.#include < set >

定义:

头文件set主要包括set和multiset两个容器,分别是”有序集合“和”有序多重集合“,即前者的元素不能重复,而后者可以包含若干个相等的元素。set和multiset的内部实现是一棵红黑树,它们支持的函数基本相同

声明:
#include <iostream>
#include <set>

using namespace std;

int main()
{
	set<int> a;//元素不能重复
	multiset<int> b;//元素可以重复
	
	struct rec
	{
		int x,y;//定义一个结构体 
		bool operator < (const rec& t) const//需要重载"<" 
		{
			return x < t.x;
		}
	}; 
	set<rec> c;
    multiset<double> s;
}
1.size/empty/clear

与vector类似

2.迭代器

set和multiset的迭代器称为“双向访问迭代器”,不支持“随机访问”,支持星号()解除引用,仅支持"++“和”–"两个与算术相关的操作

设it是一个迭代器,例如set<int>::iterator it;

若把it++,则it会指向“下一个”元素。这里的“下一个”元素是指在元素从小到大排序结果中,排在it下一名的元素。同理,若把it–,则it将会指向排在“上一个”的元素。

3.begin/end

返回集合的首、尾迭代器,时间复杂度均为O(1)。

s.begin() 是指向集合中最小元素的迭代器。

s.end() 是指向集合中最大元素的下一个位置的迭代器。换言之,就像vector一样,是一个“前闭后开”的形式。因此–s.end()是指向集合中最大元素的迭代器。

4.insert

s.insert(x)把一个元素x插入到集合s中,时间复杂度为O(logn)。

在set中,若元素已存在,则不会重复插入该元素,对集合的状态无影响。

5.find

s.find(x) 在集合s中查找等于x的元素,并返回指向该元素的迭代器。若不存在,则返回s.end()。时间复杂度为O(logn)。

6.lower_bound/upper_bound

这两个函数的用法与find类似,但查找的条件略有不同,时间复杂度为 O(logn)。

s.lower_bound(x) 查找大于等于x的元素中最小的一个,并返回指向该元素的迭代器。

s.upper_bound(x) 查找大于x的元素中最小的一个,并返回指向该元素的迭代器。

7.erase

设it是一个迭代器,s.erase(it) 从s中删除迭代器it指向的元素,时间复杂度为O(logn)

设x是一个元素,s.erase(x) 从s中删除所有等于x的元素,时间复杂度为O(k+logn),其中k是被删除的元素个数。

8.count

s.count(x) 返回集合s中等于x的元素个数,时间复杂度为 O(k +logn),其中k为元素x的个数。

#include <iostream>
#include <set>

using namespace std;

int main()
{
	set<int> a;//元素不能重复
	multiset<int> b;//元素可以重复
	
	set<int>::iterator it=a.begin();
	a.begin();//表示集合中最小元素的迭代器,时间复杂度为O(1) 
	a.end();// 表示最大元素的后一个位置的迭代器,时间复杂度为O(1) 
	
	a.insert(x);//插入一个x,时间复杂度为O(logn) 
	a.find(x);//查找等于x的元素,并返回指向该元素的迭代器;若不存在,则返回a.end(),就相当于它此时的值等于a.end(),时间复杂度为O(logn)
	
	if(a.find(x)==a.end())//判断x在a中是否存在
	
	a.lower_bound(x);//找到大于等于x的最小的元素的迭代器,时间复杂度O(logn) 
	a.upper_bound(x);//找到大于x的最小的元素的迭代器,时间复杂度O(logn) 
	
	a.erase(x);//设x是一个元素,把a中所有等于x的元素删掉,时间复杂度O(k+logn) ,k为被删除的元素的个数 
	a.erase(it);// 设it是一个迭代器,把a中迭代器it指向的元素全部删掉,时间复杂度O(logn) 
	 
	a.count(x);//表示x在a里面的个数,由于a是一个set,里面不存在重复元素,所以如果存在x的话会返回1;如果不存在x的话,会返回0,时间复杂度O(k+logn),k为元素x的个数 
	 
}

6.#include < map >

定义:

map容器是一个键值对key-value的映射,其内部实现是一棵以key为关键码的红黑树。map的key和value可以是任意类型,其中key必须定义小于号运算符

声明:
#include <iostream>
#include <map>

using namespace std;

int main()
{
	map<int,int> a;//定义
	
	a[1]=2;//插入了一个2
	
	a[100000000]=3;
	
	cout<<a[100000000]<<endl;
	//map定义完之后的用法和数组差不多 
	
	return 0;
}

map<int,int> a;//前后两个结构体可以自己定义的

证明1:

在这里插入图片描述

证明2:

在这里插入图片描述

证明3:

在这里插入图片描述

声明(补充)

map<key_type, value_type> name;

例如:

map<long, long, bool> vis;

map<string, int> hash;

map<pair<int, int>, vector> test;

1.size/empty/clear/begin/end均与set类似。
2.Insert/erase

与set类似,但其参数均是pair<key_type, value_type>。

也可以以下方式插入

#include <iostream>
#include <map>
#include <vector>

using namespace std;

int main()
{
	map<string,vector<int>> a;//定义
	
	a.insert({"a",{}});//插入一个二元组,第一个元组是string类型,第二个元组是第二个类型
	 
	
	return 0;
}
3.find

h.find(x) 在变量名为h的map中查找key为x的二元组。

map<string,vector<int>> a;
cout<<(a.find("qf")==a.end())<<endl;
4.[]操作符

h[key] 返回key映射的value的引用,时间复杂度为O(logn)。

[]操作符是map最吸引人的地方。我们可以很方便地通过h[key]来得到key对应的value,还可以对h[key]进行赋值操作,改变key对应的value。

7.#include < unordered_set >

定义:

无序的set

和set的用法完全一样

但没有lower_bound和upper_bound这两个函数

比set的效率高,但它不支持二分

#include <iostream>
#include <unordered_set>

using namespace std;

int main()
{
	unordered_set<int> s;//哈希表,不能存储重复元素 
	unordered_multiset<int> b;//哈希表,可以存储重复元素
	return 0;
}

8.#include < unordered_map >

和map的用法一样

比map效率高,但不支持二分

#include <iostream>
#include <unordered_map>

using namespace std;

int main()
{
	unordered_map<int,int> a;//哈希表 
	
	return 0;
}

9.#include < bitset >

定义一个很长很长的二进制的01串

用法1:

在这里插入图片描述

用法2:

在这里插入图片描述

用法3:
#include <iostream>
#include <bitset>

using namespace std;

int main()
{
	bitset<1000> a,b;//中括号里面写长度 ,定义了一个长度为1000的01串 
	a[0]=1;//像用数组一样使用 
	a[1]=1;
	
	a.set(3);//把第三位设成1
	a.reset(3);//把第3位设成0 
	cout<<a[3]<<endl; 
	
	return 0;
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值