二分图 【总结】

路过发现有bug的地方,还请大神指出。 (ˇˍˇ) 想~


术语:最大匹配、最小路径覆盖、最小点覆盖、最大独立集、最大团、最大(小)权值匹配。

性质:

(1) 最小路径覆盖 = 节点数 - 最大匹配。

(2) 最小点覆盖 = 最大匹配。

(3) 最大独立集 = 节点数 - 最小点覆盖 = 节点数 - 最大匹配 = 补图的最大团。

(4) 最大团 = 补图的最大独立集。

(5) 对二分图染黑、白色,要求相邻节点颜色不同,问最多可以把多少个节点染成黑色 -> 求解最大独立集。

求解最大匹配算法有三个:匈牙利、HK、KM(大材小用了)

匈牙利算法:时间复杂度O(NM)

#include 
#include 
#include 
#include 
#define MAXN (100+10)
using namespace std;
int N, M;
vector G[MAXN];
bool used[MAXN];
int match[MAXN];
int DFS(int u)
{
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if(!used[v])
        {
            used[v] = true;
            if(match[v] == -1 || DFS(match[v]))
            {
                match[v] = u;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    while(scanf("%d%d", &N, &M) != EOF)
    {
        for(int i = 1; i <= N; i++)
            G[i].clear();
        for(int i = 1; i <= M; i++)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            G[a].push_back(b);
        }
        int ans = 0;
        memset(match, -1, sizeof(match));
        for(int i = 1; i <= N; i++)
        {
            memset(used, false, sizeof(used));
            ans += DFS(i);
        }
        printf("%d\n", ans);
    }
    return 0;
}






HK:时间复杂度O(sqrt(N)M)

#include 
#include 
#include 
#include 
#define MAXN 5000+10
using namespace std;
int mx[MAXN], my[MAXN];//记录X和Y集元素的匹配点
int dx[MAXN], dy[MAXN];//记录距离标号
bool used[MAXN];//标记是否使用
vector G[MAXN];//存储二分图
int n, m;//点数 边数
int numx, numy;//记录X集元素个数 Y集元素个数
int DFS(int u)//同匈牙利算法 多了层次图的判定
{
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if(!used[v] && dy[v] == dx[u] + 1)
        {
            used[v] = true;
            if(my[v] == -1 || DFS(my[v]))
            {
                my[v] = u;
                mx[u] = v;
                return 1;
            }
        }
    }
    return 0;
}
void HK_match()
{
    memset(mx, -1, sizeof(mx));
    memset(my, -1, sizeof(my));
    int ans = 0;
    while(1)
    {
        bool flag = false;//标记是否找到可增广路径
        memset(dx, 0, sizeof(dx));
        memset(dy, 0, sizeof(dy));
        queue Q;
        for(int i = 1; i <= numx; i++)
            if(mx[i] == -1) 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];
                if(!dy[v])
                {
                    dy[v] = dx[u] + 1;//感觉和 Dinic里面建立层次图很像
                    if(my[v] == -1)//未匹配 说明找到增广路
                    flag = true;
                    else
                    {
                        dx[my[v]] = dx[u] + 1;
                        Q.push(my[v]);
                    }
                }
            }
        }
        if(!flag)//未找到增广路
            break;
        memset(used, false, sizeof(used));
        for(int i = 1; i <= numx; i++)
            if(mx[i] == -1)//没有匹配 就开始找匹配
                ans += DFS(i);
    }
    printf("%d\n", ans);//若X、Y集没有详细划分,则numx = numy = n,最后结果除2。
}
int main()
{
    while(scanf("%d%d", &n, &m) != EOF)
    {
        getMap();//建图 用G存储
        HK_match();//HK算法
    }
    return 0;
}




KM 直接上求解最大(小)权匹配:求最小权值匹配,直接将边权取负,最后结果取负就可以了。

时间复杂度O(N*3)

#include 
#include 
#include 
#define MAXN 400
#define INF 100000000//注意INF值要比所有边权值大
using namespace std;
int match[MAXN];//匹配
int lx[MAXN], ly[MAXN];//顶标
int slack[MAXN];//记录T集中节点的松弛量
int Map[MAXN][MAXN];//存储原图S-T节点间的关系
bool visx[MAXN], visy[MAXN];
int nx, ny;//S集中节点数目 T集中节点数目
int DFS(int x)
{
    visx[x] = true;
    for(int y = 0; y < ny; y++)
    {
        if(visy[y]) continue;
        int t = lx[x] + ly[y] - Map[x][y];//判断边是否在相等子图里面
        if(t == 0)//边在相等子图里面
        {
            visy[y] = true;
            if(match[y] == -1 || DFS(match[y]))
            {
                match[y] = x;
                return 1;
            }
        }
        else if(slack[y] > t)// 更新松弛量
            slack[y] = t;
    }
    return 0;
}
int KM()
{
    memset(match, -1, sizeof(match));
    memset(ly, 0, sizeof(ly));
    for(int x = 0; x < nx; x++)//lx[x]初始化为Map[x][y]中最大值
    {
        lx[x] = -INF;
        for(int y = 0; y < ny; y++)
        {
            if(Map[x][y] > lx[x])
                lx[x] = Map[x][y];
        }
    }
    for(int x = 0; x < nx; x++)//匹配
    {
        for(int i = 0; i < ny; i++)//初始化T集中所有节点的 松弛量
            slack[i] = INF;
        while(1)
        {
            memset(visx, false, sizeof(visx));
            memset(visy, false, sizeof(visy));
            if(DFS(x)) break;//匹配成功
            //匹配失败
            int d = INF;
            //寻找最小的松弛量d
            for(int i = 0; i < ny; i++)
            {
                if(!visy[i] && d > slack[i])
                    d = slack[i];
            }
            //修改顶标 为了有更大机会完美匹配
            //S集里面所有点 全部减去最小松弛量d
            for(int i = 0; i < nx; i++)
            {
                if(visx[i])
                    lx[i] -= d;
            }
            //为了保证S-T的匹配不离开相等子图即对边依旧有lx[x] + ly[y] == Map[x][y]
            //T集里面所有点 全部增加最小松弛量d
            for(int i = 0; i < ny; i++)
            {
                if(visy[i]
                    ly[i] += d;
                else//节点的松弛量 减少d
                    slack[i] -= d;
            }
        }
    }
    int res = 0;
    for(int i = 0; i < ny; i++)
    {
        if(match[i] != -1)//有匹配
            res += Map[match[i]][i];//累加权值
    }
    return res;
}
int main()
{
    int N;
    while(scanf("%d", &N) != EOF)
    {
        nx = ny = N;
        for(int i = 0; i < N; i++)
            for(int j = 0; j < N; j++)
                scanf("%d", &Map[i][j]);//第i个和第j个匹配的权值
        int ans = KM();
        printf("%d\n", ans);
    }
    return 0;
}




求解最大团算法:普通DFS 50个点就要跑8、9s,太慢。

优化DFS算法:

#include 
#include 
#include 
#define MAXN (100+10)
using namespace std;
int N, M;
int Clique[MAXN];//Clique[i]记录(i-N)这些节点可以构成的最大团
int Map[MAXN][MAXN];//存储图
int New[MAXN][MAXN];//每次建立从i-N这些节点  DFS
int used[MAXN], Rec[MAXN];//Rec记录最大团里面的点
int ans;//最大团节点数
int DFS(int T, int cnt)//DFS遍历层次 计数
{
    if(T == 0)
    {
        if(ans < cnt)
        {
            ans = cnt;
            memcpy(Rec, used, sizeof(used));
            return 1;
        }
        return 0;
    }
    for(int i = 0; i < T; i++)
    {
        if(T - i + cnt <= ans) return 0;//剪枝一
        int u = New[cnt][i];
        if(Clique[u] + cnt <= ans) return 0;//剪枝二
        int num = 0;
        for(int j = i+1; j < T; j++)
            if(Map[u][New[cnt][j]])
                New[cnt+1][num++] = New[cnt][j];
        used[cnt+1] = u;//记录当前节点
        if(DFS(num, cnt+1)) return 1;
    }
    return 0;
}
void MaxClique()
{
    memset(Clique, 0, sizeof(Clique));
    ans = 0;
    for(int i = N; i >= 1; i--)
    {
        used[1] = i; int Size = 0;
        for(int j = i+1; j <= N; j++)//根据后面的节点构建新图
            if(Map[i][j])
                New[1][Size++] = j;
        DFS(Size, 1);
        Clique[i] = ans;
    }
}
int main()
{
    while(scanf("%d%d", &N, &M) != EOF)
    {
        memset(Map, 0, sizeof(Map));
        for(int i = 1; i <= M; i++)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            Map[a][b] = Map[b][a] = 1;
        }
        MaxClique();
        printf("%d\n", ans);
        for(int i = 1; i <= ans; i++)//输出最大团里面的节点
        {
            if(i > 1)
                printf(" ");
            printf("%d", Rec[i]);
        }
        printf("\n");
    }
    return 0;
}






你可能感兴趣的:(最大匹配(权值匹配),独立集,团,&&,最小路径,点覆盖,算法与有趣代码--记录)