AcWing算法基础课----搜索与图论(三) 笔记 (最小生成树 + 二分图)

搜索与图论

  • 最小生成树
    • 1. 朴素版prim算法(稠密图) O(n^2)
    • 2. Kruskal算法 (稀疏图) O(mlogm)
  • 二分图
    • 1.染色法判别二分图 O(n+m)
    • 2.匈牙利算法 O(nm)

图论题难点:如何抽象成图论问题并实现建图

最小生成树

☆无环★

1. 朴素版prim算法(稠密图) O(n^2)

s:当前已在连通块中最短距离的点

算法思路:

  1. 初始化距离 dist[i]=inf
  2. n次迭代 for(int i=0;i
    a. 找到不在集合s中的距离最短的点t
    b. t放到s中去
    c.用t更新其他点到集合的距离

模板:

时间复杂度是 O(n2+m), n 表示点数,m 表示边数
int n;      // n表示点数
int g[N][N];        // 邻接矩阵,存储所有边
int dist[N];        // 存储其他点到当前最小生成树的距离
bool st[N];     // 存储每个点是否已经在生成树中


// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
int prim()
{
    memset(dist, 0x3f, sizeof dist);

    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        if (i && dist[t] == INF) return INF;

        if (i) res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}

堆优化版prim算法(稀疏图) O(mlogn)

2. Kruskal算法 (稀疏图) O(mlogm)

算法思路:

  1. 将所有边按权重从小到大排序
  2. 枚举每条边a、b,权重为c
    if(a,b不连通) 将这条边加入集合中

模板:

时间复杂度是 O(mlogm)O(mlogm), nn 表示点数,mm 表示边数
int n, m;       // n是点数,m是边数
int p[N];       // 并查集的父节点数组

struct Edge     // 存储边
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)     // 并查集核心操作
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集

    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)     // 如果两个连通块不连通,则将这两个连通块合并
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }

    if (cnt < n - 1) return INF;
    return res;
}

二分图

1.染色法判别二分图 O(n+m)

性质:一个图是二分图,当且仅当图中不含奇数环(环中边数为奇数)

二分图:将所有的点分成两组

由于图中不含奇数环,所以染色过程中一定没有矛盾

算法思路:

for(int i=1;i<=n;i++) 
	if(i未染色) DFS(i,1);
	

模板:

时间复杂度是 O(n+m), n 表示点数,m 表示边数
int n;      // n表示点数
int h[N], e[M], ne[M], idx;     // 邻接表存储图
int color[N];       // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色

// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{
    color[u] = c;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (color[j] == -1)
        {
            if (!dfs(j, !c)) return false;
        }
        else if (color[j] == c) return false;
    }

    return true;
}

bool check()
{
    memset(color, -1, sizeof color);
    bool flag = true;
    for (int i = 1; i <= n; i ++ )
        if (color[i] == -1)
            if (!dfs(i, 0))
            {
                flag = false;
                break;
            }
    return flag;
}

2.匈牙利算法 O(nm)

数组越界之后什么错误都有可能发生

实际运行时间远小于O(nm)

求二分图最大匹配
成功匹配:不存在两条边共用一个点

基本思路;

从前往后看

模板:

时间复杂度是 O(nm), n 表示点数,m 表示边数
int n1, n2;     // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx;     // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int match[N];       // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N];     // 表示第二个集合中的每个点是否已经被遍历过

bool 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;
                return true;
            }
        }
    }

    return false;
}

// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
    memset(st, false, sizeof st);
    if (find(i)) res ++ ;
}

你可能感兴趣的:(AcWing,算法学习笔记,图,c++,c语言)