数据结构与算法模板

本文深入解析了多种经典算法模板,包括BFS、DFS、Dijkstra、Prim、Kruskal等,涵盖图论、字符串匹配、树遍历等多个领域,详细讲解了算法原理与实践应用,助力读者快速掌握算法精髓。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

经常打codeforce,比赛,给学生讲算法那的啥啊的都需要模板
那么也把我整理的这些基础数据结构与算法的模板分享给大家
可能有一点点小错误,可以在评论区留言哈~

/*
	名称定义:
	n: 数据规模 m: 表示边数等数据规模 
	v: vertex 图中的顶点 
	fa: father 一般用来指代父亲 
	ret: return 函数返回值 
	res: result 结果值 
	ans: answer 结果值 
	g: graph 存图 
	dis: distance 存最短路径长度 
	sup: support 作为辅助的数据结构
	temp: temporary 临时变量/数组
	visit: 是否访问过
	cnt: count 次数
	d: degree (入)度
	pre: previous 之前的/前缀
	p: position 下标
	w: weight 权重 
	arr: array 数组
	mp: map的名字
	se: set的名字
	op,opt: operation操作符 
*/ 
	 
#define N (int)1e5+10
using namespace std;
/*
	O(n) BFS求无权图最短路径
	虽然dfs和bfs很像 
	在求最短路径方面,dfs可是被bfs
	甩了整整八条街哦! 
*/
int dis[N];
vector<int> g[N];
void bfs(int start)
{
	memset(dis, 0x3f, sizeof(dis));
	dis[start] = 0;
	queue<int> sup;
	sup.push(start);
	while (!sup.empty())
	{
		int front = sup.front();
		sup.pop();
		for (int i = 0; i < g[front].size(); i++)
		{
			if (dis[front] + 1 < dis[g[front][i]])
			{
				dis[g[front][i]] = dis[front] + 1;
				sup.push(g[front][i]);
			}
		}
	}
} 



/*
	O(n^2)暴力dj算法,pre数组存具体路径 
*/
int g[N][N]; 
bool visit[N];
int dis[N], pre[N];
void dijkstra(int start)
{
	memset(dis, 0x3f, sizeof(dis));
	visit[start] = true; dis[start] = 0; pre[start] = start;
	int i, j;
	int tempv = start;
	for (i = 1; i < n; i++)
	{
		for(j = 1; j <= n; j++)
		{
			if (!visit[j] && dis[tempv]+g[tempv][j] < dis[j])
			{
				dis[j] = dis[tempv]+g[tempv][j];
				pre[j] = tempv;
			}
		}
		int temp = 0x7fffffff;
		for (i = 1; i <= n; i++)
		{
			if (!visit[i])
			{
				if (dis[i] < temp)
				{
					temp = dis[j];
					tempv = i;
				}
			}
		}
		visit[tempv] = true;
	}
}




/*
	O(eloge)堆优化dj算法,在n的数量级>=1e5时必须采用这种堆优化+邻接表方式 
*/
struct node{
	int p, w;
	node(int a, int b):p(a), w(b){}
	bool operator< (const node& b) const
	{
		return w > b.w;
	}
};
vector<node> g[N];
priority_queue<node> sup;
void dijkstra(int start)
{
	memset(dis, 0x3f, sizeof(dis));
	dis[start] = 0; pre[start] = start;
	sup.push(node(start, 0));
	while (!sup.empty())
	{
		node front = sup.top();
		sup.pop();	int tempv = front.p;
		if (visit[tempv]) continue;
		visit[tempv] = true;
		for (int i = 0; i < g[tempv].size(); i++)
		{
			int p = g[tempv][i].p;
			if (!visit[p] && dis[tempv]+g[tempv][i].w < dis[p])
			{
				dis[p] = dis[tempv]+g[tempv][i].w;
				pre[p] = tempv;
				sup.push(node(p, dis[p]));
			}
		}
	}

}



/*
	O(n^2) 暴力Prim最小生成树算法 
*/
int g[N][N];
int dis[N];
bool visit[N];
int prim(int start)/*起点可以任意选,因为最小生成树一定包括每一个点*/ 
{
	int ret = 0;
	memset(dis, 0x3f, sizeof(dis));
	int i, j, tempv = start;
	visit[tempv] = true; dis[start] = 0;
	for (i = 1; i < n; i++)
	{
		for (j = 1; j <= n; j++)
		{
			if (!visit[j] && g[tempv][j] < dis[j])
			{
				dis[j] = g[tempv][j];
			}
		}
		int temp = 0x3fffffff;
		for (j = 1; j <= n; j++)
		{
			if (!visit[j] && dis[j] < temp)
			{
				temp = dis[j];
				tempv = j;
			}
		}
		visit[tempv] = true, ret += dis[tempv];
	}
	return ret;
} 

/*
	O(eloge) 堆优化Prim最小生成树算法 
*/
vector<int> g[N];
struct node{
	int p, w;
	node(int a, int b):p(a), w(b){}
	bool operator< (const node &b) const
	{
		return w > b.w;
	}
};
int prim(int start)
{
	int ret = 0;
	memset(dis, 0x3f, sizeof(dis));
	priority_queue<node> sup;
	sup.push(node(start, 0));
	while (!sup.empty())
	{
		node front = sup.front();
		sup.pop();	int tempv = front.p;
		if (visit[tempv]) continue;
		visit[tempv] = true; ret += dis[tempv];
		for (int i = 0; i < g[tempv].size(); i++)
		{
			int p = g[tempv][i].p;
			if (!visit[p] && g[tempv][i].w < dis[p])
			{
				dis[p] = g[tempv][i].w;
				sup.push(node(p, dis[p]));
			}
		}
	}
	return ret;
}
/*
	其实从代码来看,dj算法和prim算法几乎如出一辙。
	说白了呢,这两种算法都只是BFS+贪心算法而已
	每次选择最优的基点然后再更新相邻点,n-1次循环后
	就可以跑完整张图(起点那次不用跑,所以n-1次)
	
	除此以外需要注意的一点是切勿盲目堆优化
	一般来说ACM中的n和e是同一数量级(基本都是1e5)
	所以eloge的时间复杂度远远优于n^2
	但是如果是稠密图,即e = O(n^2)时
	eloge = 2n^2logn > n^2
	这是选择暴力的dj算法会更好一点
	使用stl中的堆优化,时间复杂度eloge
	自己手写二叉堆(吃饱了撑的)的话,时间复杂度是elogn
	因为不用像stl中的堆一样,每次遇到一个小于当前dis的点就无脑入堆
	而是通过一个id->堆中位置的一个映射表调整位置
	这样就能保持堆中元素的数量始终小于n,e次跑图之后时间复杂度即为
	elogn+nlogn = elogn
	使用斐波那契堆时,时间复杂度为nlogn+e 
	但是太难写了,而且常数大,故比赛时不会用到 
	希望你能够抱着一颗探索知识的心,真正的吃透和弄懂这些基础算法
	并对它们的时空复杂度有着明确的认识
	
	就我个人观点来看,kruscal算法更易理解,更容易写代码和做理论题
	并且还可以较容易直观地判断图的连通性
	但它的时间复杂度只能是eloge
	当稠密图时,用非堆优化的prim算法O(n^2)时间复杂度则更为优秀 
*/

/*
	O(eloge) kruskal最小生成树算法 
*/
int fa[N];
struct node{
	int u, v, w;
	node(){}/*注意不要忘了写上无参构造函数哦,因为如果定义了其他构造函数
			  系统就不会自己加默认构造函数,arr数组就无法构造出来!*/ 
	node(int a, int b, int c):u(a), v(b), w(c){}
	bool operator< (const node &b) const
	{
		return w < b.w;
	}
}arr[N];
int jtfind(int x)/*并查集----路径压缩*/
{
	return fa[x] == x ? x : fa[x] = jtfind(fa[x]);
}
int main(void)
{
	int n, m;
	scanf("%d%d", &n, &m);
	int i, j;
	for (i = 1; i <= n; i++) fa[i] = i;
	for (i = 1; i <= m; i++)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		arr[i] = node(a, b, c);
	}
	sort(arr+1, arr+1+m);
	int cnt = 0, res = 0;
	for (i = 1; i <= m; i++)
	{
		if (jtfind(arr[i].u) != jtfind(arr[i].v))
		{
			fa[jtfind(arr[i].u)] = jtfind(arr[i].v);
			res += arr[i].w;
			if (++cnt == n-1) break;
		}
		else continue;
	}
	if (cnt == n-1)
	{
		cout << "最小生成树的权重是" << res;	
	}
	else
	{
		cout << "此图不连通呀!" << endl;
	}
}

/*
	O(e^2)破圈法求最小生成树
*/

struct node{
	int u, v, w;
	node(){}
	node(int a, int b, int c):u(a), v(b), w(c){}
	bool operator< (const node &b) const
	{
		return w > b.w;
	}
}arr[N];
vector<int> g[N];
bool visit[N];
bool dfs(int start, int end)
{
	if (start == end) return true;
	visit[start] = true;
	bool ret = false;
	for (int i = 0; i < g[start].size(); i++)
	{
		if (!visit[g[start][i]])
			ret = ret || dfs(g[start][i], end);
	}
	return ret;
}
int main(void)
{
	int n, m;
	scanf("%d%d", &n, &m);
	int i, j;
	int res = 0;
	for (i = 1; i <= m; i++)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		arr[i] = node(a, b, c);
		res += c;
		g[a].push_back(b);
		g[b].push_back(a);
	}
	sort(arr+1, arr+1+n);
	int cnt = m - (n - 1);/*要去除的边数*/
	for (i = 1; i <= m; i++)
	{
		memset(visit, false, sizeof(visit));
		int u = arr[i].u, v = arr[i].v;
		g[u].erase(find(g[u].begin(), g[u].end(), v));
		g[v].erase(find(g[v].begin(), g[v].end(), u));
		if (dfs(u, v))
		{
			res -= arr[i].w;
			if (--cnt == 0) break;
		}
		else
		{
			g[u].push_back(v);
			g[v].push_back(u);
		}
	} 
	cout << res;
}
/*
	O(e)求拓扑序,有环的有向图是没有拓扑序的!
	如果题目中点的下标不是1--n,可以用map或者二分去离散化 
*/
vector<int> g[N];
int d[N];
vector<int> res;
queue<int> sup;
int main(void)
{
	int n, m;
	scanf("%d%d", &n, &m);
	int i;
	for (i = 1; i <= m; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		g[a].push_back(b);
		d[b]++;
	}
	for (i = 1; i <= n; i++)
	{
		if (!d[i]) sup.push(i);
	}
	while (!sup.empty())
	{
		int front = sup.front();
		sup.pop();	res.push_back(front);
		for (i = 0; i < g[front].size(); i++)
		{
			if (--d[g[front][i]] == 0) sup.push(g[front][i]);
		}
	}
	if (res.size() != n)
	{
		pritnf("这个图有环环哦~"); 
	}
	else
	{
		for (i = 0; i < res.size(); i++)
		{
			printf("%d ", res[i]);
		}
		printf("%d", res[i]);/*为了行末没有空格!*/ 
	}
}


/*
	O(n+m) KMP算法----求模式串在主串中的出现次数
	非优化版本(反正优化了也只是个常数,一般没卵用) 
*/
char s[N], p[N];/*主串和模式串*/ 
int next[N];
void getnext()
{
	int i = 0, j = -1;
	next[0] = -1;
	int len = strlen(p);
	while (i < len)
	{
		if (j == -1 || p[i] == p[j])
		{
			next[++i] = ++j;
		}
		else
		{
			j = next[j];
		}
	}
}
int cal()
{
	int ret = 0;
	int len1 = strlen(s), len2 = strlen(p);
	int i = 0, j = 0;
	while (i < len1)
	{
		if (j == -1 || s[i] == p[j])
		{
			i++, j++;
		}
		else
		{
			j = next[j];
		}
		if (j == len2)
		{
			ret++;
			i--, j = next[j-1];
		}
	}
	return ret;
}
int main(void)
{
	scanf("%s%s", s, p);
	getnext();
	int res = cal();
}
/*
	O(n)非递归二叉树前中后序遍历
*/ 
class tree{
private:
	int data;
	tree* left, *right;
public:
	void pretravel(){
		stack<tree*> s;
		tree* p = this;
		while(p || !s.empty()){
			while(p){
				cout << p->data << " ";
				s.push(p);
				p = p->left;
			}
			if(!s.empty()){
				p = s.top();
				s.pop();
				p = p->right;
			}
		}
	}
	void intravel(){
		tree* a[10005];
		int top = -1;
		tree* p = this;
		while(p || top != -1){
			while(p){
				a[++top] = p;
				p = p->left;
			}
			if(top != -1){
				p = a[top--];
				cout << p>data << " ";
				p = p->right;
			}
		}
	}
	void posttravel(){
		tree* p = this;
		stack<tree*> s;
		tree *pre = NULL, *temp = NULL;
		while(p || !s.empty()){
			while(p){
				s.push(p);
				p = p->left;
			}
			if(!s.empty()){
				temp = s.top();
				if(!temp->right || temp->right == pre){
					cout << temp->data << " ";
					pre = temp;
					s.pop();
				}
				else
					p = temp->right;
			}
		}
	}
};

/*
	O(n+e)求关键路径critical path
*/
struct node{
	int v, w;
	node(int a, int b)
	{
		v = a, w = b;
	}
};
vector<node> g[N];
int d[N];
vector<int> tp;
queue<int> sup;
int et[N], lt[N];
int main(void)
{
	int n, m;
	scanf("%d%d", &n, &m);
	int i, j;
	for (i = 1; i <= m; i++)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		g[a].push_back(node(b, c));
		d[b]++;
	}
	for (i = 1; i <= n; i++)
	{
		if (!d[i]) sup.push(i);
	}
	while (!sup.empty())
	{
		int front = sup.front();
		sup.pop();	tp.push_back(front);
		for (i = 0; i < g[front].size(); i++)
		{
			if (--d[g[front][i].v] == 0) sup.push(g[front][i].v);
		}
	}
	if (tp.size() != n)
	{
		printf("这个图有环环哦~");
		return 0;
	}

	for (i = 0; i < tp.size(); i++)
	{
		int u = tp[i];
		for (j = 0; j < g[u].size(); j++)
		{
			int v = g[u][j].v, w = g[u][j].w;
			et[v] = max(et[v], et[u] + w);
		}
	}
	for (i = 1; i <= n; i++) lt[i] = et[tp[n-1]];
	for (i = tp.size()-1; i >= 0; i--)
	{
		int u = tp[i];
		for (j = 0; j < g[u].size(); j++)
		{
			int v = g[u][j].v, w = g[u][j].w;
			lt[u] = min(lt[u], lt[v] - w);
		}
	}
	
	for (i = 1; i <= n; i++)
	{
		for (j = 0; j < g[i].size(); j++)
		{
			int u = i, v = g[i][j].v, w = g[i][j].w;
			if (lt[v] - et[u] == w)
			{
				printf("%d->%d\n", u, v);
			}
		}
	}
}















/************************华丽的分割线************************/

/*以下为写题目专用的高级数据结构与算法*/



/*
	O(nlogn)离散化模板 
*/ 
void init(void)
{
	int i, temp[N];
	for (i = 1; i <= n; i++)
	{
		temp[i] = arr[i];
	}
	sort(temp+1, temp+1+n);
	int m = unique(temp+1, temp+1+n) - temp;
	for (i = 1; i <= n; i++)
	{
		arr[i] = lower_bound(temp+1, temp+m, arr[i]) - temp;
	}
} 

/*
	树状数组 https://www.cnblogs.com/hsd-/p/6139376.html(讲的比较好) 
	适用范围:单点更新,区间求和(也可以区间求最值和区间修改,但是不太容易理解) 
*/
inline int lowbit(int t)
{
	return t&(-t);
}
int query(int x)
{
	int ret=0;
	for(int i = x; i; i -= lowbit(i)) ans += c[i];
	return ret;
}
void update(int x, int change)
{
	for(int i = x; i <= n; i += lowbit(i)) c[i] += change;
}

/*
	线段树
	适用范围:单点/区间修改,区间赋值,区间查询 
	相较于BIT,常数较大,代码量较长,适用范围更广 
*/ 
#define lc root<<1
#define rc root<<1|1
struct seg{/*这里的val代表求和,要检索的如果是区间最大值需要相应修改代码*/ 
	int l, r;
	ll val, lazy, tag;/*lazy用于区间修改。tag用于区间赋值,tag等于-1时表示没有赋值操作*/ 
	                 /*多数时候用不到lazy和tag,下面的pushdown,assignment函数就不用写了*/ 
}t[4*N];
void build(int root, int l, int r)
{
	t[root].l = l, t[root].r = r;
	t[root].lazy = 0;
	t[root].tag = -1;
	if (l == r)
	{
		scanf("%lld", &t[root].val);
		return;
	}
	int m = (l + r) >> 1;
	build(lc, l, m);
	build(rc, m+1, r);
	t[root].val = t[lc].val + t[rc].val;
}
inline void imptag(int root, ll change)
{
	t[root].tag = change;
	t[root].val = (t[root].r - t[root].l + 1) * change;
	t[root].lazy = 0;
}
inline void implazy(int root, ll change)
{
	t[root].lazy += change;
	t[root].val += (t[root].r - t[root].l + 1) * change;
}
inline void pushdown(int root)
{
	ll temp1 = t[root].tag;
	if (temp1 != -1)
	{
		imptag(lc, temp1);
		imptag(rc, temp1);
		t[root].tag = -1;
	}
	ll temp2 = t[root].lazy;
	if (temp2)
	{
		implazy(lc, temp2);
		implazy(rc, temp2);
		t[root].lazy = 0;
	}
}
void assignment(int root, int l, int r, ll change)/*基本和update一样,复制一下改下名字就行*/ 
{
	if (r < t[root].l || l > t[root].r) return;
	if (l <= t[root].l && t[root].r <= r)
	{
		imptag(root, change);
		return;
	}
	pushdown(root);
	assignment(rc, l, r, change);
	assignment(lc, l, r, change);
	t[root].val = t[lc].val + t[rc].val;
}
void update(int root, int l, int r, ll change)
{
	if (r < t[root].l || l > t[root].r) return;
	if (l <= t[root].l && t[root].r <= r)
	{
		implazy(root, change);
		return;
	}
	pushdown(root);
	update(rc, l, r, change);
	update(lc, l, r, change);
	t[root].val = t[lc].val + t[rc].val;
}
ll query(int root, int l, int r)
{
	if (r < t[root].l || l > t[root].r) return 0;
	if (l <= t[root].l && t[root].r <= r)
	{
		return t[root].val;
	}
	pushdown(root);
	return query(rc, l, r) + query(lc, l, r);
}

/*
	O(n+e) tarjan算法
	求图中所有的强联通分量
*/ 
int dfn[N], low[N], col[N], s[N]; 
bool visit[N];
int cnt, top, idx;
void tarjan(int now)
{
	dfn[now] = low[now] = ++cnt;
	visit[now] = true;//visit表示该点是否在栈中,而非是否没访问 
	s[++top] = now;
	int i;
	for (i = 0; i < g[now].size(); i++)
	{
		int temp = g[now][i];
		if (!dfn[temp])
		{
			tarjan(temp);
			low[now] = min(low[now], low[temp]);
		}
		else if (visit[temp])
		{
			low[now] = min(low[now], dfn[temp]);
		}
	}
	if (low[now] == dfn[now])
	{
		idx++;
		while (s[top] != now)
		{
			visit[s[top]] = false;
			col[s[top]] = idx;
			top--; 
		}
		visit[now] = false;
		col[now] = idx;
		top--;
	} 
}

/*
	O(n)马拉车算法求最长回文子串
	一般来说可以用dp或是枚举中心点法做到O(n^2)就已经够了 
*/
string malache(string& s)
{
    vector<int> t(2); t[0] = -2; t[1] = -1;
    for (int i = 0; i < s.size(); ++i) 
	{
        t.push_back(s[i]);
        t.push_back(-1);
    }
    vector<int> p(t.size(), 0);
    int mx = 0, id = 0, resLen = 0, resCenter = 0;
    for (int i = 1; i < t.size(); ++i) 
	{
        p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
        while (t[i + p[i]] == t[i - p[i]]) ++p[i];
        if (mx < i + p[i]) 
		{
            mx = i + p[i];
            id = i;
        }
        if (resLen < p[i]) 
		{
            resLen = p[i];
            resCenter = i;
        }
    }
    return s.substr((resCenter - resLen) / 2, resLen - 1);
}

/*
	O(n)算法求lca
	p和q在调用函数之前已经检验过是否为NULL 
*/ 
tree* lca(tree *root, tree *p, tree* q)
{
	if (root)
	{
		if (p == root || q == root) return root;
	
		tree *left = lca(root->left, p, q); 
		tree *right = lca(root->right, p, q);
		
		if (left && right)
		{
			return root;
		}
		if (left) return left;
		else return right;
	}
	
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值