算法竞赛中涉及的数据结构模板

本文主要总结了适用于算法竞赛的数据结构模板,包括链表、栈、队列、树和图、并查集、Trie 树和堆等。在竞赛中,为了追求速度,通常使用数组来模拟数据结构,避免动态内存申请,并且不进行非法数据的合法性检查。文章还强调了链表的单调栈和队列,以及如何在图上进行DFS操作。
摘要由CSDN通过智能技术生成

前言

这里总结的模板主要是用于算法竞赛,而不是用于面试或者实际工作。
我们需要明白,算法竞赛中追求的是时间,也就是在最快的时间内完成特定的任务。
而实际工作中,讲求的是稳定性,通用性。
这是两个完全不同的领域。最大的一个区别有两个。

区别一

在实际代码中,我们经常看到动态内存申请。比如对二叉树的定义,实际项目中使用如下

//定义如下
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);
        }
    }
}

并查集

Trie 树

线段树

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

努力的老周

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

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

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

打赏作者

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

抵扣说明:

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

余额充值