图论题模板总结

图论模板总结

参考:
https://www.zhangshengrong.com/p/YjNKpGrLXW/
相关练习总结参考
https://blog.csdn.net/ghost_him/article/details/124024682

链表存储

用数组模拟链表,用链表存储树和图

首先看如何用数组存储链表和遍历:
定义:
e[N] 每个节点的值
en[N] 节点的下一个节点的下标
idx 存储当前可以放入节点位置的索引,也就是当前用到的点
head 存储头结点

链表的初始化:

void init()
{
    head = -1;
    idx = 0;
}

头插法插入节点:

void add_to_head(int a)
{
    e[idx] = a; 
    ne[idx] = head;
    head = idx ;
    idx++;
}

在位置K之后插入节点:

void add_to_k(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]];
}

遍历链表的值

for(int i=head; i!=-1; i=ne[i]){
	cout<<e[i]<<" ";
}

例题:ACWing 828 单链表

#include<iostream>
using namespace std;

const int N=100010;
int e[N],ne[N],idx,head;
// e[i] 第i个结点的值,ne[i]第i个结点的下一个节点的下标(next指针);
//head 头结点,idx 当前 可以放入节点位置的索引 

//初始化
void init(){
	head = -1;
	idx=0;
} 
//向头结点处插入值 
void add_to_head(int x){
	e[idx] = x;
	ne[idx] = head;
	head = idx;
	idx++; 
}

//向k位置插入值 
void add_to_k(int k,int x){
	e[idx] = x;
	ne[idx] = ne[k];//当前节点指向k的下一个节点 
	ne[k] = idx;//k节点指向当前节点
	idx++; 
} 
//删除k位置下一个的节点 
void remove(int k){
	ne[k] = ne[ne[k]]; 
}
int main(){
	int m;
	cin>>m;
	init();
	while(m--){
		char p;
		int k,x;
		cin>>p;
		if(p=='H'){
			cin>>x;
			add_to_head(x);
		}
		if(p=='D'){
			cin>>k;
			if(k!=0)
				remove(k - 1);
			else head = ne[head];//删除头节点
			
		}
		if(p=='I'){
			cin>>k>>x;
			add_to_k(k-1,x);
		}
	}
	
	for(int i=head; i!=-1; i=ne[i]){
		cout<<e[i]<<" ";
	}
	return 0;
} 

图的存储

树是一种特殊的图,与图的存储方式相同。
对于无向图中的边ab,存储两条有向边a->b, b->a。
因此我们可以只考虑有向图的存储。
(1) 邻接矩阵:g[a][b] 存储边a->b
(2) 邻接表:
定义:
int h[N], e[N], ne[N], idx;
1.用 h 数组保存各个节点能到的第一个节点的编号。开始时,h[i] 全部为 -1。
2.用 e 数组保存节点编号,ne 数组保存 e 数组对应位置的下一个节点所在的索引。
3.用idx保存下一个 e 数组中,可以放入节点位置的索引

//邻接表
const int N = 100010, M = N * 2; //无向图n条边时,最多2n个idx,因为每条边在邻接表中会出现两次

int h[N], e[M], ne[M], idx;//n个链表头,e每一个结点的值,ne每一个结点的next指针
int w[M] //边有权值的情况

//头插法插入 a->b
void add(int a,int b){
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
}
//或者 如果插入带权值的边
void add(int a,int b, int c){
	e[idx] = b;
	w[idx] = c;//加入权值
	ne[idx] = h[a];
	h[a] = idx++;
	
}

//初始化
idx = 0;
memset(h, -1, sizeof(h));

图的遍历

深度优先遍历

时间复杂度 O(n+m), n 表示点数,m 表示边数;

深度优先遍历的步骤

  1. 访问顶点V
  2. 依次从顶点V的未被访问的邻节点出发,进行深度优先搜索,直至和V有路径相通的顶点都被访问到。
  3. 对于连通图进行遍历时,从一个顶点出发即可访问图中所有的顶点。
  4. 对于非连通图进行遍历时,若图中尚有顶点未被访问,则另选一未曾访问的顶点作为起始点,进行深度优先搜索,直至所有顶点都被访问
// 需要标记数组st[N],  遍历节点的每个相邻的点
int dfs(int u){
	st[u] = true; // st[u] 表示点u已经被遍历过
	
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}
广度优先遍历

从顶点V出发广度优先搜索的步骤

  1. 访问顶点V
  2. 依次访问顶点V的各个未被访问的临接点(横向访问)
  3. 从V的这些邻接点出发依次访问他们的邻接点,致使“先被访问的顶点的邻接点先于"后访问的顶点的邻接点"被访问(一般可以借助队列实现),直至图中所有已被访问的顶点的邻接点均被访问。
  4. 对于非连通图进行遍历时,若图中尚有顶点未被访问,则另选一未曾访问的顶点作为起始点,进行广度优先搜索,直至所有顶点都被访问
queue<int> q;
st[1] = true;//表示1号点已经遍历过
q.push(1);

while(q.size()){
 int t = q.front();//队头出队,找该点能到的点
 q.pop();//遍历完就出队列
 for(int i=h[t]; i!=-1; i = ne[i]){ //遍历所有t节点能到的点,i为节点索引
 	int j = e[i]; //通过索引i得到t能到的节点编号
 	if(!st[j]){
 		st[j] = true;//访问标记
 		q.push(j);//节点入队
 	}
 }
}

拓扑排序

BFS 的写法就叫「拓扑排序」

使用一个队列来进行广度优先搜索。开始时,所有入度为 0 的节点都被放入队列中,它们就是可以作为拓扑排序最前面的节点,并且它们之间的相对顺序是无关紧要的。
在广度优先搜索的每一步中,我们取出队首的节点 u
.

我们将 u放入答案中;

我们移除 u 的所有出边,也就是将 u 的所有相邻节点的入度减少 1。如果某个相邻节点 v 的入度变为 0,那么我们就将 v 放入队列中。

在广度优先搜索的过程结束后。如果答案中包含了这 n 个节点,那么我们就找到了一种拓扑排序,否则说明图中存在环,也就不存在拓扑排序了。

int h[N],e[N],ne[N],idx;
int d[N];//记录每个点的入度
int seq[N];//拓扑排序的序列

void add(int a,int b){
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
}

void topsort(){
	queue<int> q;
	for(int i=1;i<=n;i++){
		if(!d[i]){//入度为0
			q.push(i);
		}
	}
	int k=0; 
	while(q.size()){
		int t = q.front();
		q.pop();
		seq[k++] = t;//入队
		for(int i = h[t]; i!=-1; i = ne[i]){
			int j = e[i];
			if(--d[j]==0){//入度-1后如果为0就加入队列
				q.push(j)
			}
		}
	}
}


用数组模拟队列:

bool topsort()
{
    int hh = 0, tt = -1;

    // d[i] 存储点i的入度
    for (int i = 1; i <= n; i ++ )
        if (!d[i])
            q[ ++ tt] = i;

    while (hh <= tt)
    {
        int t = q[hh ++ ];

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (-- d[j] == 0)
                q[ ++ tt] = j;
        }
    }

    // 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
    return tt == n - 1;
}
作者:yxc
链接:https://www.acwing.com/blog/content/405/
来源:AcWing
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值