定义(参考集合论与图论教材)
- 有向图中的欧拉闭迹:含所有顶点和所有弧的有向闭迹称为欧拉闭迹
- 迹的定义:一条所有弧均不相同的有向通道;起点和终点相同的迹称为闭迹
定理
定理1:有向图D = (V,A)是有向欧拉图当且仅当D是连通的且 ∀ v ∈ V , 总 有 i d ( v ) = o d ( v ) \forall v \in V,总有id(v) = od(v) ∀v∈V,总有id(v)=od(v);即每个顶点的入度和出度相同。
- 证明:
- 若D是有向欧拉图,则一定存在一条欧拉迹。沿着这条迹行走,每个顶点每出去一次,必定对应进入一次。由于包含了所有顶点和所有的弧,所以 i d ( v ) = o d ( v ) ∀ v id(v) = od(v) \ \forall v id(v)=od(v) ∀v,且图是连通的
- 反之,如果 D是连通的且
∀
v
∈
V
,
总
有
i
d
(
v
)
=
o
d
(
v
)
\forall v \in V,总有id(v) = od(v)
∀v∈V,总有id(v)=od(v),我们证明D是一个有向欧拉图。
- 首先,图中一定至少存在一个环。考虑其最长的一条简单路径的开始结点和终末结点,由于这条简单路径是最长的,因此开始结点不存在该路径外上的结点指向它,结束结点不存在指向该路径外上的结点的有向边。但是,一定有结点的有向边指向这条简单路径上的开始结点,于是这个结点一定是这条简单路径上的结点。显然从这条简单路径上的开始结点,沿着这条最长的简单路径走向刚找到的有边指向开始结点的结点,再加上这个结点往开始结点走的边,可以构成一个环。
- 其次,考虑这个环上所有结点,一定至少存在一个结点与其他的结点相互连接,否则图不连通。
- 将这个找到的环上所有边去掉后,再去掉所有孤立的结点,还有 ∀ v ∈ V , 总 有 i d ( v ) = o d ( v ) \forall v \in V,总有id(v) = od(v) ∀v∈V,总有id(v)=od(v)。使用同样的思路找圈即可。
- 最后将这些边不相交的圈以合理的顺序游走即可得到欧拉回路。
上面的定理给出了查找欧拉回路的算法。
推论:如果有向图D中存在一条包含所有顶点和所有弧的迹,但迹的起点和终点不同(即不是闭迹),当且仅当存在顶点
x
x
x的出度比入度多1,存在顶点
y
y
y出度比入度少1,
x
≠
y
x \neq y
x=y;其余的顶点入度和出度相同,且图是连通的。
证明:
- 如果如果有向图D中存在一条包含所有顶点和所有弧的迹,但迹的起点和终点不同(即不是闭迹),沿着这条迹进行游走,对不是起点和终点的点,进入和出去的次数一致。对于起点,出去的次数比进入的次数多1,对于终点,进入的次数比出去的次数多1。由于迹包含了所有顶点和所有弧,命题得证
- 如果存在顶点
x
x
x的出度比入度多1,存在顶点
y
y
y出度比入度少1,
x
≠
y
x \neq y
x=y;其余的顶点入度和出度相同。添加一条从
y
y
y到
x
x
x的有向弧,则此时图中由定理存在一条欧拉闭迹满足最后一条边为
y
y
y指向
x
x
x的有向边。
- 否则所有的欧拉闭迹都不满足最后一条边为
y
y
y到
x
x
x的有向边,从中任取一条欧拉迹,一定在途中经过了
y
y
y到
x
x
x的有向边。设这条迹以
a
a
a为起点,则这条迹的可以分为:
- 从 a a a到 y y y所经过的有向边序列
- 从 y y y到 x x x经过的有向边
- 从 x x x回到 a a a所经过的有向边
- 此时只需先从x出发到a,再从a出发到y,最后从y出发返回x就构造了一条满足条件的欧拉闭迹。
- 最后,从该欧拉闭迹中去除 y y y到 x x x的有向边就证明了命题。
- 否则所有的欧拉闭迹都不满足最后一条边为
y
y
y到
x
x
x的有向边,从中任取一条欧拉迹,一定在途中经过了
y
y
y到
x
x
x的有向边。设这条迹以
a
a
a为起点,则这条迹的可以分为:
欧拉回路的深度优先搜索算法
- 任选一个结点
- 从这个结点开始,遍历每个从此结点出发的边,遍历了就记录。
- 从新的结点开始递归深搜
- 最后在回溯的过程中打印访问的边即可。
leetcode周赛270-T4 Valid Arrangement of Pairs
- 将数字抽象成图中的结点
- 将区间抽象成图中的有向边
- 等价于找欧拉回路或欧拉闭迹
超时DFS代码
class Solution {
public void DFS(Map<Integer,List<Integer>> adjmap,int[][] pairs,boolean[] visit,int i, Stack<Integer> seq,int from)
{
for(int j : adjmap.get(i)) //遍历每一条边
{
if(!visit[j])
{
visit[j] = true; //遍历这条边
DFS(adjmap,pairs,visit,pairs[j][1],seq,j);
}
}
if(from!=-1)
seq.push(from);
}
public int[][] validArrangement(int[][] pairs) {
Map<Integer,List<Integer>> adjmap = new HashMap<>();
boolean[] visit = new boolean[pairs.length];
Map<Integer,int[]> inoutmap = new HashMap<>();
for(int i = 0;i<pairs.length;i++)
{
if(!adjmap.containsKey(pairs[i][1]))
adjmap.put(pairs[i][1],new ArrayList<>());
List<Integer> temp = adjmap.getOrDefault(pairs[i][0],new ArrayList<>());
temp.add(i); //该顶点与那一条边建立了关联
adjmap.put(pairs[i][0],temp);
if(!inoutmap.containsKey(pairs[i][0]))
inoutmap.put(pairs[i][0],new int[2]);
if(!inoutmap.containsKey(pairs[i][1]))
inoutmap.put(pairs[i][1],new int[2]);
inoutmap.get(pairs[i][1])[0]++; //目标结点的入度加一
inoutmap.get(pairs[i][0])[1]++; //开始结点的出度加一
}
int[][] ans = new int[pairs.length][pairs[0].length];
int start = -1;
int dest = -1;
int random = 0;
for(int i : inoutmap.keySet()) //对于每个顶点而言
{
if(inoutmap.get(i)[0] == inoutmap.get(i)[1] + 1)
dest = i;
else if(inoutmap.get(i)[0] + 1 == inoutmap.get(i)[1])
start = i;
else random = i;
}
Stack<Integer> seq= new Stack<>();
if(start!=-1) //不存在欧拉回路,只存在欧拉开迹
{
DFS(adjmap,pairs,visit,start,seq,-1);
}
else DFS(adjmap,pairs,visit,random,seq,-1);
for(int i = 0;i<ans.length;i++)
{
System.arraycopy(pairs[seq.pop()],0,ans[i],0,2);
}
return ans;
}
public static void main(String[] args)
{
int[][] pairs = new int[][]{{5,1},{4,5},{11,9},{9,4}};
System.out.println(Arrays.deepToString(new Solution().validArrangement(pairs)));
}
}
- 超时原因:对于每条边来说,使用visit就遍历了从该结点发出的所有边。而该结点之后访问了该边的话是实际上不需要重复再访问该边的。因此可以将这条边从列表中删除。
AC DFS代码(211ms)
class Solution {
public void DFS(Map<Integer,List<Integer>> adjmap,int[][] pairs,boolean[] visit,int i, Stack<Integer> seq,int from)
{
// for(int j : adjmap.get(i)) //遍历每一条边
// {
// if (!visit[j]) {
// visit[j] = true; //遍历这条边
// DFS(adjmap, pairs, visit, pairs[j][1], seq, j);
// }
// }
while(!adjmap.get(i).isEmpty()) //访问就从列表中删除
{
int j = adjmap.get(i).get((adjmap.get(i).size() - 1));
adjmap.get(i).remove(adjmap.get(i).size() - 1);
DFS(adjmap,pairs,visit,pairs[j][1],seq,j);
}
if(from!=-1)
seq.push(from);
}
public int[][] validArrangement(int[][] pairs) {
Map<Integer,List<Integer>> adjmap = new HashMap<>();
boolean[] visit = new boolean[pairs.length];
Map<Integer,int[]> inoutmap = new HashMap<>();
for(int i = 0;i<pairs.length;i++)
{
if(!adjmap.containsKey(pairs[i][1]))
adjmap.put(pairs[i][1],new ArrayList<>());
List<Integer> temp = adjmap.getOrDefault(pairs[i][0],new ArrayList<>());
temp.add(i); //该顶点与那一条边建立了关联
adjmap.put(pairs[i][0],temp);
if(!inoutmap.containsKey(pairs[i][0]))
inoutmap.put(pairs[i][0],new int[2]);
if(!inoutmap.containsKey(pairs[i][1]))
inoutmap.put(pairs[i][1],new int[2]);
inoutmap.get(pairs[i][1])[0]++; //目标结点的入度加一
inoutmap.get(pairs[i][0])[1]++; //开始结点的出度加一
}
int[][] ans = new int[pairs.length][pairs[0].length];
int start = -1;
int dest = -1;
int random = 0;
for(int i : inoutmap.keySet()) //对于每个顶点而言
{
if(inoutmap.get(i)[0] == inoutmap.get(i)[1] + 1)
dest = i;
else if(inoutmap.get(i)[0] + 1 == inoutmap.get(i)[1])
start = i;
else random = i;
}
Stack<Integer> seq= new Stack<>();
if(start!=-1) //不存在欧拉回路,只存在欧拉开迹
{
DFS(adjmap,pairs,visit,start,seq,-1);
}
else DFS(adjmap,pairs,visit,random,seq,-1);
for(int i = 0;i<ans.length;i++)
{
System.arraycopy(pairs[seq.pop()],0,ans[i],0,2);
}
return ans;
}
public static void main(String[] args)
{
int[][] pairs = new int[][]{{5,1},{4,5},{11,9},{9,4}};
System.out.println(Arrays.deepToString(new Solution().validArrangement(pairs)));
}
}
重新安排行程(HARD)
欧拉回路的模板写法
class Solution {
public void dfs(String now,List<String> ans,Map<String,Queue<String>> adjmap)
{
while(adjmap.containsKey(now) && !adjmap.get(now).isEmpty())
{
String next = adjmap.get(now).poll();
dfs(next,ans,adjmap);
}
ans.add(0,now);
}
public List<String> findItinerary(List<List<String>> tickets) {
Map<String,Queue<String>> adjmap = new HashMap<>();
for(List<String> e : tickets)
{
Queue<String> q = adjmap.getOrDefault(e.get(0),new PriorityQueue<>());
q.add(e.get(1));
adjmap.put(e.get(0),q);
}
List<String> ans = new ArrayList<>();
dfs("JFK",ans,adjmap);
return ans;
}
}