前言
这里总结的模板主要是用于算法竞赛,而不是用于面试或者实际工作。
我们需要明白,算法竞赛中追求的是时间,也就是在最快的时间内完成特定的任务。
而实际工作中,讲求的是稳定性,通用性。
这是两个完全不同的领域。最大的一个区别有两个。
区别一
在实际代码中,我们经常看到动态内存申请。比如对二叉树的定义,实际项目中使用如下
//定义如下
struct BTREE {
t_type vale;
BTREE *lchild, *rchild;
};
//使用如下
BTREE *root=new BTREE;
...
而在竞赛中,为了追求速度,我们都是牺牲内存,使用数组来模拟一切数据结构。只要不爆内存(MLE),我们都是尽量开大数组。如果会出现爆内存,就需要使用滚动数组等技巧。
区别二
合法性判断。在工程代码中,程序的稳定性和鲁棒性是很重要的。
而竞赛代码是从来不判断非法数据。因为数据已经保证了合法性,除非题目特别说明。因此使用 STL 来表示数据结构,要特别注意 STL 操作的合法性,否则会莫名其妙的得到 WA。
珍惜生命远离 STL。
总体思路
根据数据结构的性质,使用数组来模拟对应的数据结构。这样既可以获得数组的优势,也具有对应数据结构的性质。
链表
单向链表
/*数据结构部分*/
const int MAXN=1E6+10;
int head=-1;//头节点
int e[MAXN];//数据节点
int ne[MAXN];//关系节点
int idx=0;//当前节点索引
/*函数部分*/
void add_head(int x) {
//头节点插入 x
e[idx]=x;
ne[idx]=head;
head=idx;
idx++;
}
void add(int k, int x) {
//在第 k 个位置后面插入数据 x
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx;
idx++;
}
void remove(int k) {
//删除第 k 个位置
ne[k]=ne[ne[k]];
}
//遍历
for (int i=head; i; i=ne[i]) {
cout<<e[i];
}
双向链表
const int MAXN=1E6+10;
int e[MAXN];//数据
int l[MAXN];//左边
int r[MAXN];//右边
int idx=1;
void init() {
//初始化
idx=2;//已经使用了2个节点
r[0]=1;//第一个节点0的右边是1
l[1]=0;//第二个节点1的左边是0
}
void add(int k, int x) {
//在节点k的右边插入x
e[idx]=x;
l[idx]=k;
r[idx]=r[k];
l[r[k]]=idx;
r[k]=idx;
idx++;
}
void remove(int k) {
//删除第k个节点
l[r[k]]=l[k];
r[l[k]]=r[k];
}
//从左到右遍历
for (int i=r[0]; i!=1; i=r[i]) {
cout<<e[i]<<" ";
}
//从右到左遍历
for (int i=l[1]; i!=0; i=l[i]) {
cout<<e[i]<<" ";
}
栈
普通栈
const int MAXN=1E6+10;
int st[MAXN];//数据
int tt=-1;//栈顶指针
bool empty() {
return tt<0;
}
//获取栈顶
int top() {
//栈空判断
return st[tt];
}
//从栈顶弹出一个数。我们并没有真的弹,只是移动了指针。注意要满足 tt>0
void pop() {
tt--;
}
//数据 x 入栈
void push(int x) {
tt++;
st[tt]=x;
}
单调栈
单调栈就是在普通栈的基础上保持数据的单调性。
void push(int x) {
//单调性
while (tt>0 && x<st[tt]) {
tt--;
}
tt++;
st[tt]=x;
}
队列
单向队列
const int MAXN=1E6+10;
int que[MAXN];//值
int hh=0//队首
int tt=-1;//队尾
bool empty() {
return hh>tt;
}
int size() {
if (hh>tt) {
return 0;
}
return hh-tt+1;
}
void push(int x) {
tt++;
que[tt]=x;
}
void pop() {
hh++;
}
int front() {
return que[hh];
}
双向队列
const int MAXN=1e5+10;
int que[MAXN];
int hh=MAXN/2+5;
int tt=MAXN/2+5;
bool empty() {
return hh>=tt;
}
int size() {
if (hh>=tt) {
return 0;
}
return tt-hh;
}
void push_front(int x) {
que[hh--]=x;
}
void push_back(int x) {
que[++tt]=x;
}
void pop_front() {
hh++;
}
void pop_back() {
tt--;
}
int front() {
return que[hh+1];
}
int back() {
return que[tt];
}
单调队列
在单向队列的基础上,维护数据单调性。
void push(int x) {
while (hh<=tt && que[tt]<x) {
tt--;
}
que[++tt]=x;
}
TO be continue
其他部分模板需要继续总结
树和图
无向图
存储无向图
const int MAXN = 1e5 + 10; //节点个数
const int M = 2 * N; //以有向图的格式存储无向图,所以每个节点至多对应2n-2条边
int h[N]; //邻接表存储树,有n个节点,所以需要n个队列头节点
int e[M]; //存储元素
int ne[M]; //存储列表的next值
int idx; //单链表指针
建立无限图
给出一对 ( u , v ) (u, v) (u,v) 表示节点 u u u 到节点 v v v 之间有一条边。
e[idx] = v;
ne[idx] = h[u];
h[u] = idx++;
图上 DFS
从节点 u u u 出发开始遍历图。
bool st[MAXN];
void dfs(int u){
st[u]=true; // 标记一下,记录为已经被搜索过了,下面进行搜索过程
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]) {
dfs(j);
}
}
}