基础数据结构

时时刻刻注意数据结构只是工具罢了,还是需要你思维能力过关,也就是能写出正确的暴力,其中有部分过程可以通过数据结构等若干手法进行优化。

希望大家在学习了一系列数据结构后遇到题不要一开始就想我一定要用某种数据结构来解决这题,而是写完暴力后再想我要用哪个数据结构来优化暴力。

链表

考虑维护一个动态序列,支持 \(m\) 次插入操作,每次在给定位置插入一个数,输出 \(m\) 次操作后的序列。

考虑暴力怎么做?

是不是用数组模拟插入排序的过程,假如插入的位置为 pos,则原先 \([pos,len]\) 的数都向后移一个单位。

但是,考虑怎么确定一个序列。

确定开头,然后确定开头右边的数,再确定这个数的右边,……。

也就是说,是不是我们可以记录每一个数的左边的数和右边的数。

具体的,开一个结构体。

struct node {
	int val,l,r;
}a[N];

val 表示 \(a_i\) 这个数的值,\(l,r\) 分别表示左右,也就是 \(a[a[i].l]\) 就是 \(a_i\) 的左边数字。

对于开头和结尾,我们可以定义最左端为 0,最右端为 \(m+1\) (插入 \(m\) 次后有 \(m\) 个数)

插入操作就很显然了,找到 \(pos\),然后新建一个点,让原先的 \(a[pos].l\) 的右边为新点,让新点的左边为 \(a[pos].l\),让新点的右边为 \(pos\),让 \(pos\) 的左边为新点。

删除同理,假如要删除的是 \(pos\),那么让 \(a[pos].l,a[pos].r\) 成相邻关系即可。

题外话,周杰伦有一首歌叫做《红尘客栈》,虽然与我要讲的没什么关系。

只要知道是先进后出即可。

就像你在叠放东西,一定是慢慢叠高的,那么在下层的是不是先进入?然而拿的时候只能从上层开始拿,所以先进后出。

怎么实现?

考虑我们需要一个数组,来记录栈中的元素,以及一个栈顶变量 \(top\)

加入

void add(int x) {
	st[++top]=x;
}

访问栈顶元素

st[top]

弹出栈顶元素

void pop() {
	--top;
}

判断当前栈是否为空

bool empty() {
	return top==0;
}

需要注意的是,STL 也有栈相关操作,不过我并不推荐使用,因为栈自己写起来比较容易,而且 STL 里的栈常数比较大。不过后面还是会讲使用方法。

队列

如其名。

在食堂排队的时候,是不是先排的就先打到饭,也就是先进先打到饭,即先进先出。但是也会有同学排队,所以我们维护的队列还要支持在最后插入一个数。

考虑使用 2 个变量 \(\text{hd,tl}\),表示 \(\text{head,tail}\),需要注意的是,将英文单词当作变量名的时候建议简写,防冲突。

那么在队尾插入

void push(int x) {
	q[++tl]=x;
}

访问队头

int front() {
	return q[hd];
}

将队头出队

void pop_front() {
	++hd;
}

判断队列是否为空

bool empty() {
	return hd>tl;
}

同理,也可以将队尾出队,访问队尾,等等操作。不过这些操作涉及到双端队列,对于队列,我建议无论普通队列还是双端队列都使用 STL 的队列。

STL(建议大家打万能头)

#include <stack>
stack<int>st;
st.top(); //访问栈顶
st.pop(); // 弹出栈顶
st.size(); //栈内元素数量
st.empty(); //栈为空则返回 1
st.push(x); //将 x 弹入栈内

队列

#include <queue>
queue<int>q;
q.front(); //访问队头
q.empty();
q.pop(); //弹出队尾
q.push(x); //在队尾加入 x
q.size();

双端队列

#include <deque>
deque<int>q;
q.front();
q.back();
q.push_front();
q.push_back();
q.pop_front();
q.pop_back();
q.empty();
q.size();

vector

当成一个不用预先设置好大小的数组使用。
当然,vector 的插入和删除操作经常可以拿来骗分,不过并不是我想讲的。
课后大家可以再上网或者找书学习。

#include <vector>
vector<int>vec;
vec.push(x);
vec.back();
vec.pop_back();
vec.insert(pos,x);
vec.erase(pos);
vec.size();

堆(优先队列

我认为堆在 OI 里是完全可以当黑盒子使用的。

所以大家只需要知道堆的作用(加入一个数,查询当前已经加入的数构成的集合内的元素最大/最小值,STL 里的堆是大根堆,所以就是最大值。

#include <priority_queue>
priority_queue<int>q;
q.top(); // q 内最大元素,因为 STL 里的堆是大根堆
q.push(x);
q.size();
q.empty();

当然,我们也可以重载运算符。在这里我们只需要重载小于号即可,因为考虑大于号就是小于号左右反过来,等于号就是不小于也不大于。

struct node {
	int x;
	bool operator < (const node &rhs) const {
		return x>rhs.x;
	}
}
priority_queue<node>q;

为什么不等号反过来了?

set

set 你可以认为是堆+链表。

支持维护一个集合(序列),序列按重载后的小于号来定义相对顺序来排列。

讲一部分指针内容。

set<int>s;
s.insert(x);
s.find(x);
s.lower_bound(x);
s.upper_bound(x);
s.erase(pos);
s.begin();
s.end();

一道例题,30min 练习

困了,剩下内容能讲多少讲多少

并查集

分块

ST 表

线段树

莫队

posted @ 2022-07-11 23:40  FxorG  阅读(71)  评论(0编辑  收藏  举报