二分图算法(染色法 , 匈牙利),欧拉回路

二分图判断

二分图:将所有点分成两个集合,使得所有边只出现在集合之间。一定不含有奇数环,可能含有长度为偶数的环,不一定是连通图。

染色法

存储结构:邻接表
dfs 思路:

  • 染色可以使用 1 和 2 区分不同颜色,用 0 表示未染色
  • 遍历所有点,每次将为染色的点进行dfs,默认染成1或2
  • 某个点某个点染色成功不代表整个图就是二分图
    • 当某个点染色失败时,这个图不是二分图
    • 染色失败相当于相邻的 2 个点染了相同的颜色。

代码实现

    boolean ans = true;
    for(int i = 1 ; i <= n ;i ++){
        if( st[i] == 0  && !dfs( i , 1 ) ){
            ans = false;
            break;
          }
       }

    static boolean dfs(int u , int color){
        st[u] = color;
        for(int i = h[u] ; i != -1 ; i = ne[i] ){
            int j = e[i];
            if( st[j] == 0 &&  !dfs(j ,3 - color)) return false;
            if( st[j] == color ) return false;
        }
        return true;
    }

bfs代码:

static boolean bfs(){
        Queue queue = new LinkedList<>();
        int[] st = new int[N];
        for(int i = 1 ; i <= n ;i ++ ){
            if( st[i] == 0 ){
                st[i] = 1;
                queue.add(i);
                while( !queue.isEmpty() ){
                    int u = queue.poll();
                    for(int k = h[u] ;  k != -1 ; k = ne[k] ){
                        int j = e[k];
                        if( st[j] == st[u] ) return false;
                        if( st[j] == 0 ){
                            st[j] = 3 - st[u];
                            queue.add(j);   
                        }
                    }
                }
            }
        }
        return true;
    }


最大匹配

匹配:在图论中,一个“匹配”是一个边的集合,其中任意两条边都没有公共顶点
最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。
完美匹配:在一个图的某个匹配中,所有顶点都是匹配点。
交替路:从一个未匹配的点出发,依次经过非匹配边,匹配边,非匹配边...形成的路径。
增广路:从一个匹配点出发,走交替路,如果途径另一个未匹配点,则这条交替路为增广路。

匈牙利算法

存储结构:邻接表
算法思路: a 找到 点 b进行匹配如果 b没有进行匹配,则 a , b进行匹配 , 记为 match[b] = a。如果 b点已经进行匹配了 ,则看 匹配 b点的点 match[b]能否找到另一个匹配点,把改点匹配让给 a ,如果可以,则匹配成功,如果不可以则匹配失败。

通俗例子:
如果你想找的妹子已经有了男朋友,
你就去问问她男朋友,
你有没有备胎,
把这个让给我好吧


image.png

实现代码:

import java.util.*;

public class Main{
    
    static int N = 510;
    static int M = 100010;
    static int[] h , e , ne;
    static int idx;
    static int[] match;
    static boolean[] st;
    
    static{
        h     = new int[N];
        match = new int[N];
        st    = new boolean[N];
        e     = new int[M];
        ne    = new int[M];
        Arrays.fill( h  , -1 );
    }
    
    static void add(int a ,int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }
    
    static boolean find(int x ){
        for(int i = h[x] ; i != -1 ; i = ne[i] ){
            int j = e[i];
            if( !st[j] ){
                st[j] = true;
                if( match[j] == 0 || find( match[j] ) ){
                    match[j] = x;
                    st[j] = true;
                    return true;
                }
            }
        }
        return false;
    }
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n1 = sc.nextInt() , n2 = sc.nextInt() , m = sc.nextInt();
        while(m -- > 0){
            int a = sc.nextInt() , b = sc.nextInt();
            add(a , b);
        }
        
        int ans = 0 ;
        for(int i = 1 ; i <= n1 ; i ++ ){
 // 标记右边的图的节点是否被访问过,每一次匹配被访问的情况都不一样
 // 所以每一次匹配都需要初始化
            Arrays.fill(st , false );
            if( find(i) ) ans++;
        }
        System.out.println(ans);
    }
}

最小点覆盖 :选择最少的点覆盖所有的边
最小点覆盖 == 最大匹配数

最大独立集:选出最多的点,使得所选出的点之间没有边。
< == > 去掉最少的点,将所有边都破坏掉
< == > 找最小覆盖点
< == > 最大匹配
最大独立集 = 总点数 - 最大匹配

最小路径点覆盖(最小路径覆盖):有向无环图中,用最少的,互不相交的路径(点不重复),将所有点覆盖。
思路:拆点 a -> b 变成 a -> b' ,然后原来的 结点 v 和 新的结点 v'就构成了二分图。例如 路径 1 -> 2 -> 3 变成 1 -> 2' , 2 - > 3'。
新二分图中
<==> 让左侧的非匹配点最少
<==> 让左侧匹配点最多
<==> 找最大匹配
最小路径点覆盖 = 结点数 - 最大匹配

最小路径可重复覆盖:选取最少可相交的边覆盖全部顶点。
思路:
1)求原图的传递闭包
2)在新图中求最小路径覆盖

image.png
#include
#include
#include

using namespace std;

const int N = 210;

int n , m;
int match[N];
bool g[N][N] , st[N];

bool find(int x)
{
    for(int i = 1 ; i <= n ;i ++ )
        if( g[x][i] && !st[i] )
        {
            st[i] = true;
            if( !match[i] || find(match[i]) )
            {
                match[i] = x;
                return true;
            }
        }
    return false;
}

int main()
{
    cin >> n >> m;
    while(m--)
    {
        int x , y;
        cin >> x >> y;
        g[x][y] = true;
    }
    
//求传递闭包
    for(int k = 1 ; k <= n ;k ++ )
        for(int i = 1 ; i <= n ;i ++ )
            for(int j = 1 ; j <= n ;j ++ )
                g[i][j] |= g[i][k] & g[k][j];
     
//在具体求二分图最大匹配的时候并没有真的构造出图 G —— G'
//而是把它想象成一个这样子的二分图           
    int cnt = 0;
    for(int i = 1 ; i <= n ;i ++)
    {
        memset(st ,0 ,sizeof st);
        if( find(i) ) cnt ++;
    }
    
    cout << n - cnt << endl;
    
    return 0;
    
}

欧拉路径

欧拉路径:一条能够不重不漏地经过图上的每一条边的路径。
欧拉回路:起点和终点相同的欧拉路径。

  • 1,对于无向图,所有边都是连通的:

    • 1)存在欧拉路径的充分必要条件:度数为奇数的点只能有0个或2个。
    • 2) 存在欧拉回路的充分必要条件:度数为奇数的点只能有0个。
  • 2 ,对于有向图,所有边都是连通的:

    • 1)存在欧拉路径的充分必要条件:要么所有点的出度均等于入度;要么除了两个点以外,所有的点的出度均等于入度,剩余的两个点,一个出度比入度多 1 (起点),另一个入度比出度多 1 (终点)。
    • 2) 存在欧拉回路的充分必要条件:所有点的出度均等于入度。
image.png
#include
#include
#include
#include

using namespace std;

const int N = 100010 , M = 400010;

int type;
int n , m;
int h[N] , e[M] , ne[M] , idx;
int ans[M / 2] , din[N] , dout[N] , cnt;
bool used[M];

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

void dfs(int u )
{
    for(int &i = h[u] ; ~i ; )
    {
        if( used[i] )
        {
            i = ne[i] ;
            continue;
        }
        
        used[i] = true;
        if( type == 1 ) used[i ^ 1] = true;
        
        int t;
        if( type == 1 )
        {
            t = i / 2 + 1;
            if( i & 1 ) t = -t;
        }
        else t = i + 1;
        
        int j = e[i];
        i = ne[i];
        
        dfs(j);
        
        ans[++ cnt] = t;
    }
}

int main()
{
    scanf("%d",&type);
    scanf("%d%d" , &n ,&m);
    
    memset(h , -1 ,sizeof h);
    for(int i = 0 ; i < m ;i ++ )
    {
        int a , b;
        scanf("%d%d" ,&a ,&b);
        add(a , b);
        if( type == 1 ) add(b , a) ;
        din[b] ++ , dout[a] ++ ;
    }
    
    if( type == 1 )
    {
        for(int i = 1 ; i <= n ;i ++ )
            if( din[i] + dout[i] & 1 )
            {
                puts("NO");
                return 0;
            }
        
    }
    else
    {
        for(int i = 1 ; i <= n ;i ++ )
            if( din[i] != dout[i] )
            {
                puts("NO");
                return 0; 
            }
    }
    
    for(int i = 1; i <= n ;i ++ )
        if( h[i] != -1 )
        {
            dfs(i);
            break;
        }
    
    if( cnt < m )
    {
        puts("NO");
        return 0;
    }
    
    puts("YES");
    for(int i = cnt ; i ; i -- ) printf("%d ",ans[i]);
    puts("");
    
    return 0;
}

你可能感兴趣的:(二分图算法(染色法 , 匈牙利),欧拉回路)