算法·图论

对于需要枚举路径上两点的,可以考虑建反图,双向来考虑,只要枚举中间的分割点即可。如《NOIP2009最优贸易》。

默认

1,图为一张 n n n个点、 m m m条边的有向图(无向图两次加边即可)。

存边

邻接矩阵

空间复杂度: O ( n 2 ) \Omicron(n^2) O(n2)

链式前向星

用head[i]保存i节点最后一条边的编号。

nex[x]表示与x这条边共起点的上一条边。(next是关键字所以使用nex)

ver[x]表示x这条边的终点。

当你遍历一个节点的所有边,就从head开始一个个找,直到指向为空。

for(int i = head[now]; i; i = nex[i])//nex的初始值为0

加边:

记录这条边的终点,把它的nex(上一条边)这个指针指向原来的最后一条边,再把它登记为最后一条边。

void add(int u,int v)
{
    ver[++tot] = v,nex[tot] = head[u],head[u] = tot;
}

空间复杂度: O ( n + m ) \Omicron(n+m) O(n+m)

单源最短路

贪心

即Dijkstra。


适用场合

1,所有边长非负。


流程

yes
令d[起点]=0,别的置正无穷
找出y,满足vis[y]=0,d[y]最小,令vis[y]=1,直到所有结点都被访问
遍历y的所有出边(y,t,z)
d[t]>d[y]+z?
用d[y]+z更新d[t]

原理

边权为正→找到的y的d已经最小


普通版

void dij(int x)//Dijkstra算法。 
{
	fill(d+1,d+n+1,INT_MAX);//将初始距离设为无穷大。
	fill(vis+1,vis+n+1,false);//将初始标记设为未访问。
	d[x] = 0;//起点距离为0。
	u(i,1,n)//一共只需要更新n-1次,因为同一个点不会被重复更新。
	{
		int now = 0;
		u(j,1,n+1)//找当前距离最小的点。
		{
			if(!vis[j] && (d[now] > d[j] || now == 0))
				now = j;
		}
		vis[now] = true;
		for(int y = head[now]; y; y = nex[y])
		{
			int des = ver[y];//目的地destination。
			if(d[des] > d[now] + edge[y])
				d[des] = d[now] + edge[y];//更新距离。
		}
	}
}

这个时间复杂度 O ( n 2 + m ) \Omicron(n^2+m) O(n2+m)

堆优化版

时间复杂度应该是 O ( ( m + n ) log ⁡ n ) \Omicron((m+n)\log n) O((m+n)logn)

class node
{
	public:
		int num;
		int dis; 
		node(int _num,int _dis)
		{
			num = _num;
			dis = _dis;
		}
		bool operator < (node y) const
		{
			//return d[num] > d[y.num]; 
			//一定不要贪图省空间写成这样!会错而且会变慢。可能是因为这样的话改变了d中的内容就会影响q的堆性质。 
			//因为默认大顶堆,所以这里要反过来。
			return dis > y.dis; 
		}
};

priority_queue<node> q;

void dij(int x)//Dijkstra算法。
{
	fill(d+1,d+n+1,INT_MAX);//将初始距离设为无穷大。
	fill(vis+1,vis+n+1,false);//将初始标记设为未访问。
	d[x] = 0;//起点距离为0。
	q.push(node(x,0));
	while(!q.empty())
	{
		node now = q.top();//利用堆在log时间内维护当前距离最小的点。
		q.pop();
		if(vis[now.num]) continue;//这句不可省略,因为可能压入了多个相同的节点。 
		vis[now.num] = true;
		for(int y = head[now.num]; y; y = nex[y])
		{
			int des = ver[y];//目的地destination。
			if(d[des] > d[now.num] + edge[y])
			{
				d[des] = d[now.num] + edge[y];//更新距离。
				if(!vis[des]) q.push(node(des,d[des]));
			}
		}
	}
}

注意到在 m ≫ n m\gg n mn时,不用堆优化会更快。

有向图最小环

枚举起点,用堆优化的dijkstra,扫描完所有出边后将起点的 d = ∞ d=\infin d=,起点第二次被取出时就是经过此起点的最小环长度。

迭代

普通版

即Bellman-Ford。


适用场合

1,可以找环。

2,可以处理负边权,但是有负边权的时候复杂度会增加。有SLF LLL DFS优化等。

SLF(Small Label First) 用双端队列,每次更新d[y]后与队头比较,若小,从队头入队。


流程

yes
令d[起点]=0,别的置正无穷
遍历所有边(y,t,z),直到没有更新发生
d[t]>d[y]+z?
用d[y]+z更新d[t]

原理

若对所有边 ( x , y , z ) (x,y,z) (x,y,z)都满足三角不等式 d [ y ] ⩽ d [ x ] + z d[y]\leqslant d[x]+z d[y]d[x]+z,则 d d d数组就是所要的最短路。


复杂度

时间: O ( n × m ) . \Omicron(n\times m). O(n×m).

队列优化版

即SPFA。


适用场合

同普通版。


原理

避免了普通版对不需要扩展节点的冗余扫描。


复杂度

时间: O ( k m ) , k \Omicron(km),k O(km),k 是一个小常数,但是你知道大家都能把它卡回 O ( n m ) \Omicron(nm) O(nm)


代码

void spfa()
{
	queue<int> q;
	memset(dis,0x3f,sizeof(dis));
	dis[1] = 0, v[1] = 1;//v记录是否在队列中。
	q.push(1);
	while(!q.empty())
	{
		tem = q.front();
		q.pop();
		v[tem] = 0;
		for(int i = head[tem]; i; i = nex[i])
		{
			if(dis[ver[i]] > dis[tem] + edg[i])
			{
				cnt[ver[i]]++;
				if(cnt[ver[i]] >= n)//就算每个点都更新它,也就n-1回。多了就是有环。
				{
					printf("有环");
					exit(0);
				}
				dis[ver[i]] = dis[tem] + edg[i];
				if(!v[ver[i]]) v[ver[i]] = true, q.push(ver[i]);
			}
		}
	}
}

任意两点最短/长路

一般用Floyd。


原理

动态规划,用 d [ k , i , j ] d[k,i,j] d[k,i,j]表示经过若干个编号不超过 k k k的结点 i i i j j j的最短路。

状态转移方程:
d [ k , i , j ] = min ⁡ ( d [ k − 1 , i , j ] , d [ k − 1 , i , k ] + d [ k − 1 , k , j ] ) . d[k,i,j]=\min(d[k-1,i,j],d[k-1,i,k]+d[k-1,k,j]). d[k,i,j]=min(d[k1,i,j],d[k1,i,k]+d[k1,k,j]).
与背包问题同理,最外一维可以省略。
d [ i , j ] = min ⁡ ( d [ i , j ] , d [ i , k ] + d [ k , j ] ) . d[i,j]=\min(d[i,j],d[i,k]+d[k,j]). d[i,j]=min(d[i,j],d[i,k]+d[k,j]).


复杂度

空间: O ( n 2 ) \Omicron(n^2) O(n2)

时间: O ( n 3 ) \Omicron(n^3) O(n3)

传递闭包

给定若干个元素和若干对二元关系,且关系具有传递性,通过传递性推导出尽量多元素之间的关系的问题被称为传递闭包

通过连边构建关系,Floyd算法可以解决传递闭包问题。

如POJ1094

代码:

#define u(i,a,b) for(int i = a; i < b; i++)
#define d(i,a,b) for(int i = a; i >= b; i--)
#define sci(a) scanf("%d",&a)
#define LL long long
using namespace std;

char gc()
{
	char c = getchar();
	while(c == '\n' || c == ' ') c = getchar();
	return c;
}

int big_letter_to_number(char x)
{
	return x - 'A';
}

int d[30][30],road[30][30];
string rout[30][30],ori_rout[30][30];

void solve(int n, int m)
{
	string tem;
	int left = 0;
	if(m == 0)
	{
		if(n == 1)
		{
			printf("Sorted sequence determined after 0 relations: A.\n");
			return;
		}
		else
		{
			printf("Sorted sequence cannot be determined.\n");
			return;
		}
	}
	u(t,0,m)
	{
		cin>>tem;
		road[big_letter_to_number(tem[0])][big_letter_to_number(tem[2])] = 1;
		ori_rout[big_letter_to_number(tem[0])][big_letter_to_number(tem[2])] = tem;
		ori_rout[big_letter_to_number(tem[0])][big_letter_to_number(tem[2])].erase(1,1);
		u(i,0,n)
		{
			u(j,0,n)
			{
				d[i][j] = road[i][j];
				rout[i][j] = ori_rout[i][j];
			}
		}
		u(k,0,n)
		{
			u(i,0,n)
			{
				u(j,0,n)
				{
					if(d[i][k] != 0 && d[k][j] != 0)
					{
						if(d[i][k] + d[k][j] > d[i][j])
						{
							d[i][j] = d[i][k] + d[k][j];
							rout[i][j] = rout[i][k] + rout[k][j];
							rout[i][j].erase(rout[i][k].length(),1);
						}
					}
					if(j == i && d[i][j] != 0)
					{
						printf("Inconsistency found after %d relations.\n",t+1);
						left = m-t;
						goto end;
					}
					if(d[i][j] == n-1)
					{
						printf("Sorted sequence determined after %d relations: ",t+1);
						cout<<rout[i][j]<<"."<<endl;
						left = m-t;
						goto end;
					}
				}
			}
		}
	}
	printf("Sorted sequence cannot be determined.\n");
end:
	u(i,0,left-1)
	cin>>tem;
	return;
}

void init(int n)
{
	u(i,0,n)
	{
		u(j,0,n)
		{
			road[i][j] = 0;
			ori_rout[i][j] = "";
		}
	}
}

int main()
{
	int n,m;
	sci(n);
	sci(m);
	while(n != 0)
	{
		init(n);
		solve(n,m);
		sci(n);
		sci(m);
	}
}

另外此题还要记录路径,在更新长度的同时记录路径即可。( O ( n 3 ) \Omicron(n^3) O(n3)能维护的就是多!)

无向图最小环

考虑Floyd算法的过程当,外层循环 k k k刚开始时的 d [ i , j ] d[i,j] d[i,j]保存着经过编号不超过 k − 1 k-1 k1的结点 i i i j j j的最短路长度。
min ⁡ 1 ⩽ i < j < k { d [ i , j ] + a [ j , k ] + a [ j , i ] } \min_{1\leqslant i<j<k}\{d[i,j] + a[j,k]+a[j,i]\} 1i<j<kmin{d[i,j]+a[j,k]+a[j,i]}
表示了节点编号最大为 k k k最小环长度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值