【算法基础】第二章:数据结构

Chapter 2 数据结构

1:链表与邻接表

单链表:用于构造邻接表,存储图和树

双链表:用于优化某些问题

数组模拟链表

e[N] 表示当前位置的值

ne[N] 表示指向的下一个位置的编号

head -> value0(0) -> value1(1) -> end(-1)

题目:单链表

# include <iostream>
using namespace std;

const int N = 1e6+10;
int head, e[N], ne[N], idx;
// head头指针
// e数组 数值
// ne数组 下一个指针编号
// idx 当前已经用到哪个点 

// 初始化 
void init(){
	head = -1;
	idx = 0;
}

// head插入
void add_to_head(int x){
	ne[idx] = head; // 当前点的next,更新为head指向的起点 
	head = idx;		// head指向当前点 
	e[idx] = x;		// 存储value 
	idx++;
} 

// 将x插入下标是k的点后面 
void add(int k, int x){
	e[idx] = x;
	ne[idx] = ne[k];
	ne[k] = idx;
	idx++;
} 

// 将下标是k的点后面的点删除 
void remove(int k){
	ne[k] = ne[ne[k]];
} 

int main(){
	int m;
	cin>>m;
	
	init();
	
	while(m--){
		int k,x;
		char op;
		cin>>op;
		
		if(op=='H'){
			cin>>x;
			add_to_head(x);
		}
		else if(op=='D'){
			cin>>k;
			if(!k)	head = ne[head]; // delete head
			remove(k-1);	// 第k个插入的点,其下标是k-1 
		}
		else{
			cin>>k>>x;
			add(k-1,x);
		}
	}
	for(int i=head;i!=-1;i=ne[i]){
		cout<<e[i]<<" ";
	}
	cout<<endl;
	return 0;
}
/*
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6

6 4 6 5
*/

题目:双链表

# include <iostream>
using namespace std;

const int N = 1e6+10;
int m;
int e[N], l[N], r[N], idx;

void init(){
	// 0是左端点,1是右端点 
	// 一开始的状态是 point0 <=> point1
	r[0] = 1; // point0右边是point1 
	l[1] = 0; // point1左边是point0 
	idx = 2; 
}

// 第k个点右侧插入 
void add(int k, int x){
	e[idx] = x;
	// define x pointer
	r[idx] = r[k];	//idx的right,为k点右侧指向 
	l[idx] = k;		//idx的left,为k本身 
	
	// update previous pointer
	l[r[k]] = idx; 	//k原来的右侧指向,其left更改为idx
	r[k] = idx;
	idx ++;
	// 左侧插入,在k-1点右侧插入
	// add(l[k], x)
}

// 删除第k个点 
void remove(int k){
	int pre = l[k];
	int next = r[k];
	r[pre] = next;
	l[next] = pre;
}

int main(){
	// 输入和输出操作
    init();
    int m, k, x;
    cin >> m;
    while (m--) {
        string op;
        cin >> op;
        if (op == "L") {
            cin >> x;
            // 最左端插入数x
            // 最左端就是表示在0的右侧插入一个数据
            add(0, x);
        } else if (op == "R") {
            cin >> x;
            // 最右端插入数x
            add(l[1], x); // 1号端点的左侧,就是最右端
        } else if (op == "D") {
            cin >> k;
            remove(k + 1); // idx从2开始,所以下标需要k+1传入才对
        } else if (op == "IL") {
            cin >> k >> x;
            add(l[k + 1], x); // 在这个k的左侧那个元素的右侧插入数据x
        } else {
            cin >> k >> x;
            add(k + 1, x); // 正常调用
        }
    }
    // 注意双链表的输出方法,0和1是一头一尾,idx从2开始,换言之,
    //  从r[0]就是开始进行遍历,出现1就结尾了
    for (int i = r[0]; r[i]!=NULL ; i = r[i]) printf("%d ", e[i]);
	return 0;
}
/*
10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2

8 7 7 3 2 9
*/

邻接表:多个单链表组成

2:栈与队列

栈:先进后出

// tt表示栈顶
int stk[N], tt = 0;

// 向栈顶插入一个数
stk[ ++ tt] = x;

// 从栈顶弹出一个数
tt -- ;

// 栈顶的值
stk[tt];

// 判断栈是否为空,如果 tt > 0,则表示不为空
if (tt > 0)
{

}

队列:先进先出

// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;

// 向队尾插入一个数
q[ ++ tt] = x;

// 从队头弹出一个数
hh ++ ;

// 队头的值
q[hh];

// 判断队列是否为空,如果 hh <= tt,则表示不为空
if (hh <= tt)
{

}

单调栈:

作用:求数组中距离当前数最近的符合某个特征的数值

题目:输出每个数左边第一个比它小的数

# include <iostream>
using namespace std;

const int N = 1e6+10;

int stk[N], tt;
int n;

int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		int x;
		cin>>x;
		// tt: stack is not empty
		// stk[tt]>=x: stack top >= current
        // 需要改变的是stk[tt] >= x,即check()函数
		while(tt && stk[tt] >= x) tt--;
        
		// i左边离i最近且比i小的数 
		if(tt) cout<<stk[tt]<<" ";
		else cout<<-1<<" ";
		
		stk[++tt] = x;
	}
	return 0;
}
/*
5
3 4 2 7 5


-1 3 -1 2 2
*/

cin.tie(0) 可以加快cin的读入速度

单调队列:

作用:求滑动窗口的最大值和最小值

题目:滑动窗口

# include <iostream>
using namespace std;

const int N = 1e6+10;

int a[N], q[N]; // q是单调队列
int n, k;
// k是窗口大小
// n是数组长度

int main(){
	cin>>n>>k;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	
	int hh = 0, tt = -1;
	// hh头, tt尾 
	for(int i=0;i<n;i++){
		// 判断队头是否离开滑动窗口,不确定移动多少次时使用while,这里的if只有一次判断
		if(hh <= tt && i-k+1 > q[hh]){
			hh++; // 离开
		}
        //q[tt]是窗口末尾数,如果a[末尾]超过了当前a[i],则说明队列q不是单调递增了,应该剔除a[末尾]
		while(hh <= tt && a[q[tt]]>=a[i]){
			tt--;
		}
		q[++tt] = i; //将当前位置i插入队尾
		if(i>=k-1){
            // i走到第一次窗口的最右侧时,才开始输出
			cout<<a[q[hh]]<<" ";
		}
	}
	cout<<endl;
	
	hh = 0, tt = -1;
	// hh头, tt尾 
	for(int i=0;i<n;i++){
		// 判断队头是否离开滑动窗口 
		if(hh <= tt && i-k+1 > q[hh]){
			hh++;
		}
		while(hh <= tt && a[q[tt]]<=a[i]){
			tt--;
		}
		q[++tt] = i;
		if(i>=k-1){
			cout<<a[q[hh]]<<" ";
		}
	}
	cout<<endl;
	return 0;
}
/*
8 3
1 3 -1 -3 5 3 6 7

-1 -3 -3 -3 3 3
3 3 5 5 6 7
*/

3:kmp

核心:前缀和后缀相同的最长字符串

next[]的含义:当子串j+1点和父串i点不相同时,j点应该更新的下一个点

# include <iostream>
using namespace std;

const int N = 1e6+10, M = 1e6+10;

int ne[N];
char p[N], s[M];	// 模式串和主串 
int n, m;

int main(){
	// 主串是1开始编号,模式串是0开始编号 
	cin>>n>>p+1>>m>>s+1;
	
	// 求next,则是模式串和模式串本身求kmp
    //i从2开始,因为ne[1]=0
	for(int i=2,j=0; i<=n; i++){ 
		while(j && p[i]!=p[j+1]){
			j = ne[j]; // 退回
		}
		if(p[i] == p[j+1]){
			j++; // 匹配成功
		}
		ne[i] = j; // 更新
	} 
	
	// kmp 匹配过程
	for(int i=1,j=0; i<=m; i++){
		// 没退回到模式串的起点  且  下一个字符总是不匹配 
		while(j && s[i]!=p[j+1]){
			j = ne[j];	// 退回 
		}
		// 下一个字符,退回到,匹配成功了 
		if(s[i] == p[j+1]){
			j++; 	// 继续匹配下下一个字符
		}
		// 整个模式串匹配成功 
		if(j == n){
			cout<<i-n<<" ";
			j = ne[j];
		}
	} 
	return 0;
}
/*
3
aba
5
ababa

0 2
*/

4:Trie树

作用:快速存储和查找字符串集合的数据结构

字典树

题目:字符串统计

l x是插入一个字符串x

Q x是询问字符串x出现了多少次

# include <iostream>
using namespace std;

const int N = 1e6+10;

int son[N][26], cnt[N], idx;
// son数组是每个idx的分叉,小写字母最多26个分叉
// cnt数组记录每个idx的结尾信号数
// 下标是0的点,是根节点或空节点 
char str[N];

void insert(char str[]){
	int p=0; // 从根节点开始
	for(int i=0;str[i];i++){
		int u = str[i]-'a';
		// 如果不存在儿子,则创建,且儿子里存放的是唯一idx序号
		if (!son[p][u]) son[p][u]=++idx;
		p = son[p][u]; // 更新根节点为儿子
	}
	cnt[p]++;	// 存好了,给最后的儿子一个结尾信号
}

int query(char str[]){
	int p=0;
	for(int i=0;str[i];i++){
		int u = str[i]-'a';
		// 如果不存在儿子,则不存在单词 
		if (!son[p][u]) return 0; 
		p = son[p][u]; 
	}
	return cnt[p];
}

int main(){
	int n;
	cin>>n;
	while(n--){
		char op[2];
		cin>>op>>str;
		if(op[0]=='I') insert(str);
		else cout<<query(str)<<endl;
	}
	return 0;
}
/*
5
I abc
Q abc
Q ab
I ab
Q ab

1
0
1
*/

5:并查集

作用:

【1】将两个集合合并

【2】询问两个元素是否在一个集合当中

基本原理:

以树的形式维护每一个集合,根节点的编号是当前集合的编号,每个节点存储其父节点,p[x]表示x的父节点。

【1】判断树根:if(p[x] == x)

【2】求x的集合编号:while(p[x] != x) x = p[x],从当前节点x向上递归,寻找根节点,返回这个集合的编号。

【3】合并两个集合:px是x的编号,py是y的集合编号,则p[x]=y使得集合x的父节点变成集合y的根节点

优化:路径压缩——向上搜索过的结点,全部连接到根节点,不再经过中间结点

其他优化:按秩合并——把高度较小的树的根节点,接到高度较大的树的根节点上(几乎不用)

题目:合并集合

# include <iostream>
using namespace std;

const int N = 1e6+10;

int p[N];

// 返回x的祖宗结点 + 路径压缩
int find(int x){
	if(p[x] != x){
		p[x] = find(p[x]);
	}
	return p[x];
} 

int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		p[i]=i;
	}
	
	while(m--){
		char op[2];
		int a,b;
		cin>>op>>a>>b;
		
		if(op[0] == 'M'){
			p[find(a)] = find(b);
			// a的祖宗结点的父亲,等于b
			// 实质上是让a集合的根节点的父亲,变成,b集合的根节点 
		}
		else{
			if(find(a) == find(b)){
				puts("Y");
			}
			else{
				puts("N");
			}
		}
	}
	return 0;
}
/*
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

Y
N
Y
*/

题目:连通块中的数量

# include <iostream>
using namespace std;

const int N = 1e6+10;

int p[N], size[N];
//size是每一个集合中点的数量 
//只用保证根节点的size是有意义的 

// 返回x的祖宗结点 + 路径压缩
int find(int x){
	if(p[x] != x){
		p[x] = find(p[x]);
	}
	return p[x];
} 

int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		p[i]=i;
		size[i]=1; //刚开始只有一个点 
	}
	
	while(m--){
		char op[5];
		int a,b;
		cin>>op;
		
		if(op[0] == 'C'){
			cin>>a>>b;
			if(find(a) == find(b)){
				continue;
				//如果a和b在同一个结合,不需要更新根节点的size
                //需要特判!
			} 
			size[find(b)] += size[find(a)];
			p[find(a)] = find(b);
			// a的祖宗结点的父亲,等于b
			// 实质上是让a集合的根节点的父亲为b集合的根节点 
		}
		else if (op[1] == '1'){
			cin>>a>>b;
			if(find(a) == find(b)){
				puts("Y");
			}
			else{
				puts("N");
			}
		}
		else{
			cin>>a;
			cout<<size[find(a)]<<endl; 
		}
	}
	return 0;
}
/*
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

Yes
2
3
*/

并查集的变形:维护一些额外信息

例如:上题是集合中的个数;集合中到根节点的距离

6:堆

用于Dijkstra,维护一个数据集合

如何手写一个堆?

【1】插入一个数

【2】求集合当中的最小值

【3】删除最小值

【4】删除任意一个元素

【5】修改任意一个元素

STL中可以直接实现【1】到【3】。

无法直接实现【4】【5】

堆是一个完全二叉树

小根堆的性质,每个点都小于等于左右两个儿子,根节点为最小值

按一维数组存储点,x的左儿子是2x,x的右儿子是2x+1

核心操作:

down:结点往下调整(与儿子比大小,比儿子大则下沉)

up:结点往上调整(与父亲比大小,比父亲小则上升)

heap表示堆,size表示当前大小

下标从1开始!

【1】

heap[++size] = x;
up(size);

【2】

heap[1];

【3】

heap[1] = heap[size];	//最后一个点直接覆盖第一个点
size--;					//修改大小(直接删除最小值了相当于)
down(1);				//将第一个点下沉(重新维护堆)

【4】

heap[k] = heap[size];
size--;
down(k);
up(k);
// 只执行down或up

【5】

heap[k] = x;
down(k);
up(k);

题目:堆排序

输入一个长度为n的整数数列,升序输出前m个数

# include <iostream>
using namespace std;

const int N = 1e6+10;

int n, m;
int h[N], size;

void down(int u){
	int min = u;	// 假设父亲最小 
	if(u*2<=size && h[u*2]<h[min]){
		//如果左儿子存在 且 左儿子比父亲小 
		min = u*2; 
	}
	if(u*2+1<=size && h[u*2+1]<h[min]){
		//如果右儿子存在 且 右儿子比父亲小 
		min = u*2+1;
	}
	if(u != min){
		//如果有儿子比父亲小 
		swap(h[u],h[min]);	// 交换儿子和父亲 
		down(min);			// 让新儿子继续下沉 
	}
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>h[i];
	}
	size=n;
	
    // 整理每个结点,使得小根堆成立
	for(int i=n/2;i;i--){
		down(i);
	}
	
	while(m--){
		cout<<h[1]<<" ";
		h[1] = h[size];
		size--;
		down(1);
	}
	return 0;
}
/*
5 3
4 5 1 3 2

1 2 3
*/

题目:模拟堆

实现以下操作:

1:插入一个数

2:输出最小值

3:删除最小值(不唯一时,删除最早插入的)

4:删除第k个插入的

5:修改第k个插入的

# include <iostream>
# include <string.h>
# include <algorithm> 
using namespace std;

const int N = 1e6+10;

int h[N], ph[N], hp[N], size;
//ph[k]存第k个插入点,在堆中的下标
//hp[k]存堆中k下标的点,是第几个插入点
//hp和ph是互为反函数

void heap_swap(int a, int b){
	swap(h[a],h[b]);			
	//交换2个值
	swap(hp[a],hp[b]);			
	//交换hp指针,即更新从点获取的插入编号 
	swap(ph[hp[a]],ph[hp[b]]);	
	//交换ph指针,hp[i]是i点对应的插入编号,即交换插入编号 
}

void down(int u){
	int min = u;	// 假设父亲最小 
	if(u*2<=size && h[u*2]<h[min]){
		//如果左儿子存在 且 左儿子比父亲小 
		min = u*2; 
	}
	if(u*2+1<=size && h[u*2+1]<h[min]){
		//如果右儿子存在 且 右儿子比父亲小 
		min = u*2+1;
	}
	if(u != min){
		//如果有儿子比父亲小 
		heap_swap(u,min);	// 交换儿子和父亲 
		down(min);			// 让新儿子继续下沉 
	}
}

void up(int u){
	// 当父亲存在 且 父亲比儿子大 
	while(u/2 && h[u/2]>h[u]){
		heap_swap(u/2,u);	// 交换父亲和儿子 
		u /= 2;				// 让新父亲继续上升 
	}
} 

int main(){
	int n, idx = 0;
	cin>>n;
	while(n--){
		char op[2];
		int k,x;
		
		cin>>op;
		if(!strcmp(op,"I")){
			cin>>x;
			size++;
			idx++;
			ph[idx] = size;
			hp[size] = idx;
			h[size] = x;
			up(size);
		}
		else if(!strcmp(op,"PM")){
			cout<<h[1]<<endl;
		}
		else if(!strcmp(op,"DM")){
			heap_swap(1,size);
			size--;
			down(1);
		}
		else if(!strcmp(op,"D")){
			cin>>k;
			k = ph[k];
			heap_swap(k,size);
			size--;
			down(k);
			up(k);
		}
		else{
			cin>>k>>x;
			k = ph[k];
			h[k] = x;
			down(k);
			up(k);
		}
		cout<<endl;
		for(int k=1;k<=size;k++){
			cout<<" "<<h[k];
		}
		cout<<endl;
	}
	return 0;
}
/*
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

-10
6
*/

7:Hash表

存储结构:

【1】开放寻址法

【2】拉链法(单数组模拟映射块+数组模拟链表)

取模的数为范围内最大的素数

题目:拉链法模拟hash

每个取模的位置,连接一个链表,放置当前数字x进入链表

—1—2—3—4—5—6—7—

—[]—[]—[]—x1—[]—[]—[]—

—[]—[]—[]—x2—[]—[]—[]—

# include <iostream>
# include <string.h>
# include <algorithm> 
using namespace std;

const int N = 1e5+3;

int h[N], e[N], ne[N], idx;

void insert(int x){
	int k = ((x%N) + N) % N; 	// 让复数取模是正数
	// 把x插入到h[k]上 
	e[idx] = x;					// 数值x存入 
	ne[idx] = h[k];				// 新的点的next指针为 h[k] 
	h[k] = idx;					// h[k]指向新的点 
	idx++; 
}

bool find(int x){
	int k = ((x%N) + N) % N; 
	for(int i=h[k]; i!=-1; i=ne[i]){
		if(e[i] == x){
			return 1;
		}
	}
	return 0;
}

int main(){
	int n;
	cin>>n;
	memset(h, -1, sizeof h);
	
	while(n--){
		char op[2];
		int x;
		cin>>op>>x;
		
		if(*op=='I'){
			insert(x);
		}
		else{
			if(find(x)){
				puts("Yes");
			}
			else{
				puts("No");
			}
		}
	}
	return 0;
}
/*
5
I 1
I 2
I 4
Q 2
Q 5

Yes
No
*/

题目:开放寻址法模拟hash

开一个目标长度2~3倍是数组,按照上厕所找坑位的方法遍历,找到坑位x就插入

# include <iostream>
# include <string.h>
# include <algorithm> 
using namespace std;

const int N = 2e5+3, null = 0x3f3f3f3f;

int h[N];

int find(int x){
	int k = (x%N+N) % N; 
	
	// 当k位置非空,且,k位置的数不是x 
	while(h[k] != null && h[k] != x){
		k++;
		// 查找下一个位置
		 
		if(k == N){
			k = 0; 
			//走到最后一个位置了,应该从头开始遍历 
		}
	}
	return k;
}

int main(){
	int n;
	cin>>n;
	memset(h, 0x3f, sizeof h);
	
	while(n--){
		char op[2];
		int x;
		cin>>op>>x;
		
		int k = find(x);
		if(*op=='I'){
			h[k] = x;
		}
		else{
			if(h[k] != null){
				puts("Yes");
			}
			else{
				puts("No");
			}
		}
	}
	return 0;
}
/*
5
I 1
I 2
I 4
Q 2
Q 5

Yes
No
*/

字符串hash方式

作用:快速判断两个字符串是否相等,从 O(n) 到 O(1)

字符串前缀哈希法(从1开始映射,且假定不存在冲突)

【1】把字符串看成一个p进制的数

【2】将p进制的数转换成10进制的数

【3】将10进制的数对小整数Q取模

P和Q的经验值(这样的话不会出现冲突)

P = 131 或 13331

Q = 2^64

# include <iostream>
# include <string.h>
# include <algorithm> 
using namespace std;

// 0839
typedef unsigned long long ULL;
const int N =1e5+10, P = 131;

int n, m;
char str[N];	// 输入的字符串
ULL h[N], p[N];
// h数组是 某个前缀的 哈希值 
// p数组是 进制移位的乘数 

// 比较两个字符串是否相等 
ULL get(int l, int r){
	return h[r] - h[l-1] * p[r-l+1];
	// 将 h[l-1] 左移,补全位数,然后作差 
}

int main(){
	cin>>n>>m>>str+1;
	p[0]=1;	// 初始化 
	for(int i=1;i<=n;i++){
		p[i] = p[i-1] * P;
		//基数,例如:补全n位需要乘以n个p 
		h[i] = h[i-1] * P + str[i];
		//前缀和 
	}
	
	while(m--){
		int l1, r1, l2, r2;
		cin>>l1>>r1>>l2>>r2;
		
		if(get(l1,r1) == get(l2,r2)) puts("Yes");
		else puts("No");
	}
	return 0;
}
/*
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

Yes
No
Yes
*/

8:STL

【1】vector, pair

变长数组,倍增思想

vector常用函数

//size() 	返回元素个数
//empty() 	返回是否为空
//clear() 	清空
//front()	返回第一个数
//back()	返回最后一个数
//push_back()	末尾插入一个数
//pop_back()	末尾删除一个数 
//begin()	迭代器,第一个数 
//end()		最后一个数的后面一个数

测试用例

# include <iostream>
# include <string.h>
# include <algorithm> 
# include <stdio.h> 
# include <vector>
using namespace std;

int main(){
	int n=10, value=3;
	vector<int> aa(n,value); //长度为n,数值初始化为value 
	for(auto x:aa){
		cout<<x<<" ";
	}
	cout<<endl;
	vector<int> b[10]; //vector数组
	
	/*-----基本函数-----*/
	//size() 	返回元素个数
	//empty() 	返回是否为孔
	//clear() 	清空
	//front()	返回第一个数
	//back()	返回最后一个数
	//push_back()	末尾插入一个数
	//pop_back()	末尾删除一个数 
	//begin()	迭代器,第一个数 
	//end()		最后一个数的后面一个数
	
	/*-----遍历vector-----*/
	vector<int> a;
	for(int i=0;i<10;i++)	a.push_back(i);
	// method 1
	for(int i=0;i<a.size();i++)	cout<<a[i]<<" ";
	cout<<endl;
	// method 2
	for(vector<int>::iterator i=a.begin(); i!=a.end(); i++) cout<<*i<<" ";
	cout<<endl;
	// method 3
	for(auto x:a) cout<<x<<" ";
	cout<<endl;
	
	/*-----比较运算-----*/
	// 按字典序来比较大小
	vector<int> c(4,3), d(3,4);
	if(c < d) puts("c<d");
	else puts("c>=d");
	 
	return 0;
}

pair用法

pair<int, int>
    first, 第一个元素
    second, 第二个元素
    支持比较运算,以first为第一关键字,以second为第二关键字(字典序)

两个不同属性的变量需要存储在一起,通常用于排序

存储3个内容:pair<int, pair<int,int>> p,以此类推

创建一个pair:p = {20, "abc"}p = make_pair{20, "abc"}

作用:直接实现结构体

【2】string

字符串

string常用函数

//size() 	返回元素个数
//length()	同上
//empty() 	返回是否为空
//substr(start,len) 返回从start开始长度为n的子字符串
//c_str() 	返回字符串所在字符数组的起始地址

【3】queue, priority_queue

队列和优先队列

queue常用函数

//size() 	返回元素个数
//empty() 	返回是否为空
//push()  	向队尾插入一个元素
//front()  	返回队头元素
//back()  	返回队尾元素
//pop()  	弹出队头元素

没有clear函数,如果需要清空则需要q = queue<int>()

优先队列默认按大根堆实现

priority_queue常用函数

size()
empty()
push()  插入一个元素
top()  	返回堆顶元素
pop()  	弹出堆顶元素

如果想实现小根堆,则可以使用

priority_queue<int, vector<int>, greater<int>> q;

或直接插入复数

priority_queue<int> heap

heap.push(-x)

【4】stack

size()
empty()
push()  	向栈顶插入一个元素
top()  		返回栈顶元素
pop()  		弹出栈顶元素

【5】deque

双端队列

size()
empty()
clear()
front()/back()				//返回第一个/最后一个元素
push_back()/pop_back()		//队尾插入/弹出一个元素
push_front()/pop_front()	//队首插入/弹出一个元素
begin()/end()				//迭代器
[]							//随机寻址

缺点:速度慢

【6】set, map, multiset, multimap

基于平衡二叉树(红黑树),动态维护有序序列

multi支持重复元素,没有multi就不支持

    size()
    empty()
    clear()
    begin()/end()
    ++, -- 返回前驱和后继,时间复杂度 O(logn)

    set/multiset
        insert()  		插入一个数
        find()  		查找一个数
        count()  		返回某一个数的个数
        erase()
            (1) 输入是一个数x,删除所有x   O(k + logn),k是x的个数
            (2) 输入一个迭代器,删除这个迭代器
        lower_bound()/upper_bound()
            lower_bound(x)  返回大于等于x的最小的数的迭代器
            upper_bound(x)  返回大于x的最小的数的迭代器
    map/multimap
        insert()  	插入的数是一个pair
        erase()  	输入的参数是pair或者迭代器
        find()
        []  		注意multimap不支持此操作。 时间复杂度是 O(logn)
        lower_bound()/upper_bound()

【7】unordered_set, unordered_map, unordered_multiset, unordered_multimap

哈希表

内部无序

    和上面类似,增删改查的时间复杂度是 O(1)
    不支持 lower_bound()/upper_bound(), 迭代器的++,--

【8】bitset

压位

布尔变量压缩,一个bool变量是1B,占8位,压缩之后只占1位了

    bitset<10000> s;
    ~, &, |, ^		取反,与,或,异或
    >>, <<			左移,右移
    ==, !=			等于,不等于
    []				取出某一位

    count() 		返回有多少个1

    any()  			判断是否至少有一个1
    none()  		判断是否全为0

    set()  			把所有位置成1
    set(k, v)  		将第k位变成v
    reset()  		把所有位变成0
    flip() 			把所有位反转,即等价于~
    flip(k) 		把第k位取反
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MorleyOlsen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值