leetcode周赛270记录-有向图中的欧拉迹与欧拉回路

定义(参考集合论与图论教材)

  • 有向图中的欧拉闭迹:含所有顶点和所有弧的有向闭迹称为欧拉闭迹
  • 迹的定义:一条所有弧均不相同的有向通道;起点和终点相同的迹称为闭迹

定理

定理1:有向图D = (V,A)是有向欧拉图当且仅当D是连通的且 ∀ v ∈ V , 总 有 i d ( v ) = o d ( v ) \forall v \in V,总有id(v) = od(v) vV,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) vV,id(v)=od(v),我们证明D是一个有向欧拉图。
      • 首先,图中一定至少存在一个环。考虑其最长的一条简单路径的开始结点和终末结点,由于这条简单路径是最长的,因此开始结点不存在该路径外上的结点指向它,结束结点不存在指向该路径外上的结点的有向边。但是,一定有结点的有向边指向这条简单路径上的开始结点,于是这个结点一定是这条简单路径上的结点。显然从这条简单路径上的开始结点,沿着这条最长的简单路径走向刚找到的有边指向开始结点的结点,再加上这个结点往开始结点走的边,可以构成一个环。
      • 其次,考虑这个环上所有结点,一定至少存在一个结点与其他的结点相互连接,否则图不连通。
      • 将这个找到的环上所有边去掉后,再去掉所有孤立的结点,还有 ∀ v ∈ V , 总 有 i d ( v ) = o d ( v ) \forall v \in V,总有id(v) = od(v) vV,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的有向边就证明了命题。

欧拉回路的深度优先搜索算法

  • 任选一个结点
  • 从这个结点开始,遍历每个从此结点出发的边,遍历了就记录。
  • 从新的结点开始递归深搜
  • 最后在回溯的过程中打印访问的边即可。

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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值