10.2 图的存储
1.邻接矩阵
邻接矩阵适用于顶点数目不太大的题目(一般不超过1000)
2.邻接表
可以使用链表来实现邻接表,也可以使用vector来实现邻接表
vector<int> adj[n];
这样每个顶点adj[i]都是一个变长数组。
struct Node{
int v, w;
}
Node temp;
temp.v=3;temp.w=4;
Adj[1].push_back(temp);
//或者采用下面这种方式
struct Node{
int v, w;
Node(int_v, int_w):v(-v), w(_w) {}
}
Adj[1].push_back(Node(3, 4));
在顶点数目较大时都需要采用邻接表来存储图
10.3图的遍历
1. DFS
1.连通分量
无向图中,如果两个顶点可以相互到达,则称这两个顶点连通,若图中任意两个顶点都连通,则称图为连通图,否则为非连通图,且其中的极大连通子图为连通分量;
2.强连通分量
有向图中,如果两个顶点可以各自通过一条有向路径到达另一个顶点,就称这两个顶点强连通,如果图中任意两个顶点都强连通,则称该图为强连通图。
如果已知一个图为连通图,那么一次深度优先搜索就可以遍历全部结点
//DFS的伪代码
DFS(u) {
vis[u]=true;
for(从u出发能到达的所有顶点v) {
if(vis[v]==false) {
DFS(v);
}
}
}
DFS_Trave(G) {
for(G的所有顶点u) {
if(vis[u]==false) {
DFS(u);
}
}
}
//邻接矩阵版
#include <climits>
const int maxv=1000;
const int inf=INT_MAX;
int n, G[maxv][maxv];
bool vis[maxv]={false};
void DFS(int u, int depth) {
vis[u]=true;
for(int v=0; v<n; v++) {
if(vis[v]==false && G[u][v]!=INF) {
DFS(v, depth+1);
}
}
}
void DFSTrave() {
for(int u=0; u<n; u++) {
if(vis[u]==false) {
DFS(u, 1);
}
}
}
//邻接表
vector<int> Adj[maxn];
int n;
bool vis[maxv]={false};
void DFS(int u, int depth)
vis[u]=true;
for(int i=0; i<Adj[u].size(); i++) {
int v=Adj[u][i];
if(vis[v]==false) {
DFS(v, depth+1);
}
}
void DFSTrave() {
for(int u=0; u<n; u++) {
if(vis[u]==false){
DFS(u, 1);
}
}
}
BFS
//邻接矩阵版
int n, G[maxv[maxv];
bool inq[maxv]={false};
void BFS(int u) {
queue<int> q
q.push(u);
inq[u]=true;
while(!q.empty()) {
int x=q.front();
q.pop();
for(int i=0; i<n; i++) {
if(inq[i]==false && G[x][i]!=inf) {
q.push(i);
inq[i]=true;
}
}
}
}
void BFSTrave() {
for(int i=0; i<n; i++) {
if(inq[i]==false) {
BFS(i);
}
}
}
//邻接表版
vector<int> Adj[maxv];
int n;
bool inq[maxv]={false};
void BFS(int u) {
queue<int> q;
q.push(u);
inq[u]=true;
while(!q.empty()) {
int x=q.front();
q.pop();
for(int i=0; i<Adj[x].size(); i++) {
int v=Adj[u][i];
if(inq[v]==false) {
q.push(v);
inq[v]=true;
}
}
}
}
void BFSTrave() {
for(int i=0; i<n; i++) {
if(inq[i]==false) {
BFS(i);
}
}
}
//若要计算其层次号
struct Node{
int v;
int layer;
}
vector<Node> Adj[maxv];
void BFS(int u) {
queue<Node> q;
Node start;
start.v=u;
start.layer=0;
q.push(start);
inq[start.v]=true;
while(!q.empty()) {
Node now=q.front();
q.pop();
int u=now.v;
for(int i=0; i<Adj[u].size(); i++) {
Node next=Adj[u][i];
next.layer=now.layer+1;
if(inq[next.v]==false) {
q.push(next);
inq[next.v]=true;
}
}
}
}
10.4最短路径
1.Dijkstra算法–单源最短路径问题
总思路;设置一个集合S来存放已访问过的结点,然后执行n次:
1)每次从V-S中选择距离起点最短的一个结点,然后将该结点加入S,
2)以该结点为中介点,优化起点与所有能从该结点能到的结点的距离。
//伪代码
Dijkstra(G, d[], s) {
初始化;
for(循环n次) {
u=使d[u]最小且还没有访问过的结点的编号;
记u被访问过;
for(从u出发能到达的所有顶点v) {
if(v未被访问 && 以u为中介点使s到v的最短距离d[v]更优) {
优化d[v];
令v的前驱为u;
}
}
}
}
//邻接矩阵版
#include <iostream>
const int maxv=1010;
const int inf=1e9;
int n, G[maxv][maxv], d[maxv];
bool vis[maxv]={false};
void Dijkstra(int s) {
fill(d, d+maxv, inf);
d[s]=0;
for(int i=0; i<n; i++) {
int u=-1, mins=inf;
for(int j=0; j<n; j++) { //找最小值
if(vis[j]==false && d[j]<mins) {
u=j;
mins=d[j];
}
}
if(u==-1) return; //找不到说明剩下的图不连通
vis[u]=true;
for(int v=0; v<n; v++) { //对distance数组进行优化
if(vis[v]==false && G[u][v]!=inf && d[u]+G[u][v]<d[v]) {
d[v]=d[u]+G[u][v];
}
}
}
}
//邻接表版
#include <iostream>
#include <vector>
const int maxv=1010;
const int inf=1e9;
struct node{
int v, dis;
};
vector<node> Adj[maxv];
int n, d[maxv];
bool vis[maxv]={false};
void Dijkstra(int s) {
fill(d, d+maxv, inf);
d[s]=0;
for(int i=0; i<n; i++) {
int u=-1, mins=inf;
for(int j=0; j<n; j++) {
if(vis[j]==false && d[j]<mins) {
u=j;
mins=d[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int j=0; j<Adj[u].size(); j++) {
int v=Adj[u][j].v;
if(vis[v]==false && d[u]+Adj[u][j].dis<d[v]) {
d[v]=d[u]+Adj[u][j];
}
}
}
}
//利用递归来得到最终的路径!!
void DFS(int s, int v) {
if(v==s) {
printf("%d\n", s);
}
DFS(s, pre[v]);
printf("%d\n", v);
}
三种问法:除了最短距离外,再增加一个尺度:
1)每条边增加边权;
2)每个点增加点权;
3)直接问有多少条路径;
//这三种都只需要增加一个数组来保存新增的边权或点权或最短路径条数
//这三种问题都只需要在更新dis的时候对数组进行修改即可,其他地方不用动
//1)--新增cost[u][v], c[]---注意初始化的问题
for(int i=0; i<n; i++) {
if(vis[v]==false && G[u][v]!=inf) {
if(dis[u]+G[u][v]<dis[v]) {
dis[v]=dis[u]+G[u][v];
c[v]=c[u]+cost[u][v];
} else if(d[u]+G[u][v]==d[v] && c[u]+cost[u][v]<c[v]) {
c[v]=c[u]+cost[u][v];
}
}
}
//2)---weight[u], w[u], 注意初始化的问题
for(int v=0; v<n; v++) {
if(vis[v]==false && G[u][v]!=inf) {
if(dis[u]+G[u][v]<dis[v]) {
dis[v]=dis[u]+G[u][v];
w[v]=w[u]+weight[v];
} else if(dis[u]+G[u][v]==dis[v] && w[u]+weight[v]>w[v]) {
w[v]=w[u]+weight[v];
}
}
}
//3)--增加一个数组num[]
for(int v=0; v<n; v++) {
if(vis[v]==false && G[u][v]!=inf) {
if(dis[u]+G[u][v]<dis[v]) {
dis[v]=dis[u]+G[u][v];
num[v]=num[u];
} else if(dis[u]+G[u][v]==dis[v]) {
num[v]+=num[u];
}
}
}
Dijkstra+DFS:
思路:先在Dijkstra算法中记录下所有最短路径(只考虑距离),然后从这些最短路径中选出一条第二标尺最优的路径。
1)记录最短路径:
将原先的pre[maxv]写成vector< int > pre[maxv]:
之前的pre[maxv]初始化为pre[i]=i,在这里,pre数组一开始不需要赋初值(是因为每次找到更优的前驱都会清空pre[v])
if(d[u]+G[u][v]<d[v]) {
d[v]=d[u]+G[u][v];
pre[v].clear();
pre[v].push_back(u);
} else if(d[u]+G[u][v]==d[v]) {
pre[v].push_back(u);
}
//具体实现
vector<int> pre[maxv];
void Dijkstra(int s) {
fill(dis, dis+maxv, inf);
dis[s]=0;
for(int i=0; i<n; i++) {
int u=-1, mins=inf;
for(int j=0; j<n; j++) {
if(vis[j]=false && dis[j]<mins) {
u=j;
mins=dis[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<n; v++) {
if(vis[v]==false && G[u][v]!=inf) {
if(dis[u]+G[u][v]<dis[v]) {
dis[v]=dis[u]+G[u][v];
pre[v].clear();
pre[v].push_back(u);
} else if(dis[u]+G[u][v]==dis[v]) {
pre[v].push_back(u);
}
}
}
}
}
2)遍历最短路径,找到一条使第二标尺最优的路径。
本质上就是对一棵树进行遍历,计算根结点到每个叶子结点的第二标尺的值并与最优值比较
int optvalue;
vector<int> pre[maxv];
vector<int> path, tempPath;
void DFS(int v) {
if(v==st) { //出口
tempPath.push_back(v);
int value; //存放tempPath的第二标尺的值
计算tempPath路径上的value值;
if(value 优于optvalue) {
optvalue=value;
path=tempPath;
}
tempPath.pop_back();
return;
}
tempPath.push_back(v);
for(int i=0; i<pre[v].size(); i++) {
DFS(pre[v][i]);
}
tempPath.pop_back();
}
//边权求和
intn value=0;
for(int i=tempPath.size()-1; i>=0; i--) {
int id=tempPath[i], idnext=tempPath[i-1];
value+=V[id][idnext];
}
//点权求和
int value=0;
for(int i=tempPath.size(); i>=0; i--) {
int id=tempPath[i];
value+=w[id];
}
Dev C++调试:
Bellman-Ford算法和SPFA算法
1.Bellman-Ford算法
前面的Dijkstra算法只能解决无负边的问题,而这里的Bellman-Ford算法可以解决有负权边的最短路径问题
图的存储方式选择邻接表会减少算法的时间复杂度
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct node{
int v, dis;
};
const int maxv=1010;
const int inf=1e9;
vector<node> Adj[maxv];
int n, dis[maxv];
bool Bellman(int s) {
fill(dis, dis+maxv, inf);
dis[s]=0;
for(int i=0; i<n-1; i++) {
for(int u=0; u<n; u++) {
for(int j=0; j<Adj[u].size(); j++) {
int v=Adj[u][j].v;
int dis=Adj[u][j].dis;
if(d[u]+dis<d[v]){
d[v]=d[u]+dis;
}
}
}
}
for(int u=0; u<n; u++) {
for(int j=0; j<Adj[u].size(); j++) {
int v=Adj[u][j].v, dis=Adj[u][j].dis;
if(d[u]+dis<d[v]) {
return false;
}
}
}
return true;
}
由于B-F算法要对所有边进行遍历,影响了算法的性能,因此可以考虑对算法进行优化
也叫SPFA算法Shortest Path Faster Algorithm
思路:只有当某个顶点u的d[u]值发生改变时,从u出发的边的邻接点v的d[v]才会有可能发生改变。
queue<int> q;
源点s入队
while(!q.empty()) {
取队首元素u;
for(u的所有邻接边u->v) {
if(d[u]+dis<d[v]) {
d[v]=dis+d[u];
if(v当前不在队列中) {
v入队;
if(v入队次数大于n-1) {
说明有负环,return;
}
}
}
}
}
//邻接表实现
#include <isotream>
#include <vector>
#include <algorithm>
using namespace std;
const int maxv=1010;
struct node{
int v, dis;
};
vector<node> Adj[maxv];
int n, dis[maxv], num[maxv]; //num用于记录顶点的入队次数
bool inq[maxv]={false}; //顶点是否在队列中
bool SPFA(int s) {
fill(num, num+maxv, 0);
fill(dis, dis+maxv, inf);
queue<int> q;
q.push(s);
inq[s]=true;
num[s]++;
d[s]=0;
while(!q.empty()) {
int u=q.front();
q.pop();
inq[u]=false;
for(int j=0; j<Adj[u].size(); j++) {
int v=Adj[u][j].v;
int dis=Adj[u][j].dis;
if(dis[u]+dis<dis[v]) {
dis[v]=dis[u]+dis;
if(!inq[v]) {
q.push(v);
inq[v]=true;
num[v]++;
if(num[v]>=n) {
return false; //有可达负环
}
}
}
}
}
return true;
}
Floyd算法–全源最短路径
时间复杂度为O(n3 )—决定了顶点n的限制约在200以内----使用邻接矩阵会很方便
//伪代码
枚举顶点k(在[1,n]中);
以顶点k作为中介点,枚举所有顶点对i,j;
如果dis[i][k]+dis[k][j]<dis[i][j]成立
赋值dis[i][j]=dis[i][k]+dis[k][j];
//具体实现
#include <iostream>
#include <algorithm>
using namespace std;
const int inf=1e9;
const int maxv=200;
int n, m; //n为顶点数,m为边数
int dis[maxv][maxv];
void floyd() {
for(int k=0; k<n; k++) {
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
if(dis[i][k]!=inf && dis[k][j]!=inf && dis[i][k]+dis[k][j]<dis[i][j]) {
dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
}
}
int main() {
int u, v, w;
fill(dis[0],dis[0]+maxv*maxv, inf);
scanf("%d%d", &n, &m);
for(int i=0; i<n; i++) {
dis[i][i]=0;
}
for(int i=0; i<m; i++) {
scanf("%d%d%d", &u, &v, &w);
dis[u][v]=w;
}
floyd();
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
printf("%d ", dis[i][j]);
}
printf("\n");
}
return 0;
}
10.5 最小生成树-MST
1.定义:在给定的无向图中求一棵树T,使这棵树有图G中的所有顶点,且所有边都是来自图G中的边,并满足整棵树的边权之和最小。
于是有了两种生成最小生成树的算法:基于贪心策略的Prim算法和Kruskal算法
Prim算法-----注意与Dijkstra算法进行区分
对于最小生成树问题,如果是求最小边权之和,则在Prim算法中可以随意指定一个点作为初始点。
//伪代码
Prim(G, d[]) {
初始化;
for(循环n次) {
u=使d[u]最小的还未被访问的顶点的编号;
记录u被访问过;
for(从u出发能到达的所有顶点v) {
if(v未被访问过 && 以u为中介点使得v与集合s的最短距离d[v]更优) {
将G[u][v]赋值给v和集合s的最短距离d[v];
}
}
}
}
//Dijkstra算法与Prim算法实际上思路是一致的,只是数组d[]的含义不同!!!!
#include <iostream>
#include <algorithm>
using namespace std;
const int maxv=1010;
const int inf=1e9;
//邻接矩阵版
int G[maxv][maxv], n;
int d[maxv];
bool vis[maxv]={false};
int prim() { //默认0号为初始点,返回边权之和
fill(d, d+maxv, inf);
d[0]=0;
int ans=0; //保存边权之和
for(int i=0; i<n; i++) {
int u=-1, mins=inf;
for(int j=0; j<n; j++) {
if(vis[j]==false && d[j]<mins) {
u=j;
mins=d[j];
}
}
if(u==-1) return -1;
vis[u]=true;
ans +=d[u];
for(int v=0; v<n; v++) {
if(vis[v]==false && G[u][v]!=inf && G[u][v]<d[v]) {
d[v]=G{u}[v];
}
}
}
return ans;
}
//邻接表版
struct node{
int v, dis;
};
vector<node> Adj[maxv];
int n;
int d[maxv];
bool vis[maxv]={false};
int prim() {
fill(d, d+maxv, inf);
d[0]=0;
int ans=0;
for(int i=0; i<n; i++) {
int u=-1, mins=inf;
for(int j=0; j<n; j++) {
if(vis[j]==false && d[j]<mins) {
u=j;
mins=d[j];
}
}
if(u==-1) return -1;
vis[u]=true;
ans +=d[u];
for(int j=0; j<Adj[u].size(); j++) {
int v=Adj[u][j].v;
int dis=Adj[u][j].dis;
if(vis[v]==false && dis<d[v]) {
d[v]=dis;
}
}
}
return ans;
}
#include <iostream>
#include <algorithm>
using namespace std;
const int maxv=1010;
const int inf=1e9;
int n, m, G[maxv][maxv];
int d[maxv];
bool vis[maxv]={false};
int prim() {
fill(d, d+maxv, inf);
d[0]=0;
int ans=0;
for(int i=0; i<n; i++) {
int u=-1, mins=inf;
for(int j=0; j<n; j++) {
if(vis[j]==false && d[j]<mins) {
u=j;
mins=d[j];
}
}
if(u==-1) return -1;
vis[u]=true;
ans+=d[u];
for(int v=0; v<n; v++) {
if(vis[v]==false && G[u][v]!=inf && G[u][v]<d[v]) {
d[v]=G[u][v];
}
}
}
return ans;
}
int main() {
int u, v, w;
scanf("%d%d", &n, &m);
fill(G[0], G[0]+maxv*maxv, inf);
for(int i=0; i<m; i++) {
scanf("%d%d%d", &u, &v, &w);
G[u][v]=G[v][u]=w;
}
int ans=prim();
printf("%d\n", ans);
return 0;
}
//测试数据
//6 10
//0 1 4
//0 4 1
//0 5 2
//1 2 6
//1 5 3
//2 3 6
//2 5 5
//3 4 4
//3 5 5
//4 5 3
Kruskal算法
思路:对所有边按边权从小到大进行排序;按边权从小到大测试所有边,如果当前测试边所连接的两个顶点不在同一连通块中,则把在这条边加入到生成树中,否则被遗弃;执行上述步骤直到边的条数为顶点个数减一或者是测试完结束,如果最小生成树的边数少于顶点数减一,说明该图不连通。
struct edge{
int u, v;
int cost;
}E[maxe];
bool cmp(edge a, edge b) {
return a.cost<b.cost;
}
//伪代码
int kruskal() {
令最小生成树的边权之和为ans,最小生成树的当前边数num_edge;
将所有边按边权从小到大进行排序;
for(从小到大枚举所有边) {
if(当前测试边的两个端点不在同一个连通块中) {
将该测试边加入到最小生成树中;
ans += 测试边的边权;
最小生成树的当前边数num_edge加1;
当边数num_edge等于顶点数减一的时候结束循环;
}
}
return ans;
}
#include <iostream>
#include <algorithm>
using namespace std;
int father[n];
//int findfather(int x) {
// int a=x;
// while(x!=father[x]) {
// x=father[x];
// }
// while(a!=father[a]) {
// int z=a;
// a=father[a];
// father[z]=x;
// }
// return x;
//}
//或者递归写法
int findfather(int v) {
if(v==father[v]) return v;
else {
int f=findfather(father[v]);//从最深层返回的是v的根结点
father[v]=f;
return f; //函数返回,根结点
}
}
int kruskal(int n, int m) {
int ans=0, num_edge=0;
for(int i=1; i<=n; i++) {
father[i]=i;
}
sort(E, E+m, cmp);
for(int i=0; i<m; i++) {
int faU=findfather(E[i].u);
int faV=findfather(E[i].v);
if(faU!=faV) {
father[faU]=faV;
ans+=E[i].cost;
num_edge++;
if(num_edge==n-1) break;
}
}
if(num_edge!=n-1) return -1;
else return ans;
}
总而言之,边数较多的时候采用prim算法,边数较少的时候用kruskal算法
10.6拓扑排序
有向无环图-DAG
步骤:1.定义一个队列Q;并把所有入度为0的结点入队
2.取队首结点,输出,并把与该结点相连的边删除,同时把与其相连的顶点的入度减一,并把入度为零的点入队;
3.重复上述操作,直到队空,如果队空的时候入过队的结点数目恰为N,说明拓扑排序成功,否则失败,说明有环
换句话说,拓扑排序可以用来判断一个有向图是否有环
可使用邻接表来实现拓扑排序
vector<int> G[maxv];
int n, m, inDegree[maxv];
bool topologicalSort() {
int num=0;
queue<int> q; //也可以使用priority_queue
for(int i=0; i<n; i++) { //将入度为零的顶点入队
if(inDegree[i]==0) {
q.push(i);
}
}
while(!q.empty()) {
int u=q.front();
q.pop();
for(int i=0; i<G[u].size(); i++) { //遍历队首结点的相邻顶点
int v=G[u][i]; //加入最小生成树
inDegree[v]--;
if(inDegree[v]==0) {
q.push(v);
}
}
G[u].clear();
num++;
}
if(num==n) return true;
else return false;
}
10.7AOE网和AOV网—都是DAG
1.AOV网
定义:指用顶点表示活动,边集表示活动间优先关系的有向图。
2.AOE网
定义:边集表示活动,顶点表示事件的有向图。
AOE网中的最长路径称为关键路径,关键路径上的活动称为关键活动。
对于一个没有正环的图,如果需要求最长路径长度,那么可以把所有边的边权乘以-1,令其为相反数,然后使用Bellman-Ford算法或者改进的Bellman-Ford算法即SPFA算法来计算最短路径,然后让结果取相反数即可得到结果!!!注意不可以使用Dijkstra算法!!!
关键路径
本质上是求AOE网的最长路径
思路:这些活动的最早开始时间是最迟开始时间,设置数组e、l,其中e[r]和l[r]分别表示活动ar的最早开始时间和最迟开始时间。如果e[r]==l[r]说明该活动是关键活动
则问题就转化为了如何求e[]和l[]:
设置数组ve[],vl[]分别表示事件i的最早发生时间和最迟发生时间:
1)对于活动ar来说,e[r]=ve[i]
2)如果l[r]是活动ar的最迟发生时间,那么l[r]+length[r]就是事件vj的最迟发生时间,则l[r]=vl[i]-
length[r]
如何求ve[i]和vl[i]:
stack<int> topOrder;
bool topologicalSort() {
queue<int> q;
for(int i=0; i<n; i++) {
if(inDegree[i]==0) {
q.push(i);
}
}
while(!q.empty()) {
int u=q.front();
q.pop();
topOrder.push(u); //u加入拓扑序列
for(int i=0; i<G[u].size(); i++) {
int v=G[u][i].v;
inDegree[v]--;
if(inDegree[v]==0) q.push(v);
if(ve[u]+G[u][i].w>ve[v]) {
ve[v]=ve[u]+G[u][i].w;
}
}
}
if(topOrder.size()==n) return true;
else return false;
}
ve[i]是从前往后推,同时进行拓扑排序,为后面的计算vl做好基础
vl[i]刚好是逆拓扑排序的,因此在上面保存拓扑排序的时候使用栈保存即可。
vl[i]是从后往前推.
fill(vl, vl+n, ve[n-1]);
while(!topOrder.empty()) {
int u=topOrder.top();
topOrder.pop();
for(int i=0; i<G[u].size(); i++) {
int v=G[u][i].v;
if(vl[u]=G[u][i].w<vl[u]) {
vl[u]=vl[v]-G[u][i].w;
总结:求解关键路径的步骤如下:
1)按照拓扑排序及逆拓扑排序分别计算各顶点的最早发生时间和最迟发生时间:
ve[i]:max
vl[i]:min
2)按照上面计算得到的可得e[r]和l[r]
e[r]=ve[i];
l[r]=vl[r]-length[i->j]
3)e[i->j]=e[i->j]的即为关键路径
int CriticalPath() {
memset(ve, 0, sizeof(ve));
if(topologicalSort==false) {
return -1; //不是有向图,返回-1
}
fill(vl, vl+n, ve[n-1]);
while(!topOrder.empty() ) {
int u=topOrder.top();
topOrder.pop();
for(int i=0; i<G[u].size(); i++) {
int v=G{u][i].v;
if(vl[v]-G[u][i].w<vl[u]) {
vl[u]=vl[v]-G[u][i].w;
}
}
}
//遍历邻接表的所有边,计算e[r]和l[r]
for(int u=0; u<n; u++) {
for(int i=0; i<G[u].size(); i++) {
int v=G[u][v].v, w=G[u][v].w;
int e=ve[u], l=vl[v]-w;
if(e==l) {
printf("%d->%d\n", u, v);
}
}
}
return ve[n-1];
}