搜索与图论——最小生成树和二分图(1)

内容梳理:

最小生成树
普利姆算法Prim
克鲁斯卡尔算法Kruskal__稀疏图用这个
朴素版Prim__稠密图
堆优化版Prim__稀疏图
二分图
判断二分图:染色法
求二分图的最大匹配:匈牙利算法

最小生成树

说明:根据实用主义原则,每个算法掌握一个效率最高的算法就行了,所以堆优化版Prim被淘汰。即稠密图用朴素版Prim、稀疏图用Kruskal

Prim算法

时间复杂度O(n^2)

此算法与dijkstra很相似,具体思路为:

初始化dist[i]为正无穷
for(int i=0;i<n;i++)
	//首先用一个集合存储现已有匹配的图(当前在连通图中的所有点)
	t=找到集合外距离最近的点
	用t更新其他点到集合的距离
	st[t]=true;

其他点到集合的距离的定义:

  • 首先初始化——所有点到集合的距离都是正无穷
  • 更新其他点到集合的距离即为找这个点到集合中的所有点的距离的最小值

AW858 Prim算法求最小生成树

#include 
#include 
#include 

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];
int dist[N];   //存储的是点i到集合的最短距离
bool st[N];


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]))  //1.点还没有被遍历  2.还没有找到任何一个点或者dist[j]更小
                t = j;					//这个双循环的含义是在所有点中找目前离t点最近的点并替换t

        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]);   //用t更新其他点到集合的距离
    }

    return res;
}


int main()
{
     
    cin>>n>>m;

    memset(g, 0x3f, sizeof g);

    while (m -- )
    {
     
        int a, b, c;
        cin>>a>>b>>c;
        g[a][b] = g[b][a] = min(g[a][b], c);  //无向边的双向建立
    }

    int t = prim();

    if (t == INF) puts("impossible");
    else cout<<t<<endl;

    return 0;
}
Kruskal算法

时间复杂度O(mlogm)

涉及到并查集实现,而并查集的话离不开这个find函数

int find(int x)//并查集灵魂所在    始终要记得find函数是返回祖宗节点的
//返回x的祖宗节点+路径优化
//路径优化:叶子节点向上查找根节点,当找到根节点时,将所有的其他刚刚经历过的节点的指向变为x,这样的话之后再去查找就一步到位了
//当p[x]==x时,则x时这个集合(这棵树)的根节点即祖宗节点
{
     
	if(p[x]!=x) p[x]=find(p[x]);//路径优化,把x的父节点的祖宗节点赋值为x的父节点,这样的话数被变成了根节点连着很多单树
	return p[x];//返回x的祖宗节点
}

基本思路:

  • 将所有边按权重大小排序
  • 枚举每条边a,b,权重w
    if a,b不连通
    则将这条边加入到集合中
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)		//若ab不连通则连通它们,这样一定是最小情况,因为已经按从小到大排好序了
        {
     
            p[a] = b;		
            res += w;
            cnt ++ ;
        }
    }

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

上代码:
AW859 Kruskal算法求最小生成树

#include 
#include 
#include 

using namespace std;

const int N = 100010, M = 200010, INF = 0x3f3f3f3f;

int 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)//并查集灵魂所在    始终要记得find函数是返回祖宗节点的
//返回x的祖宗节点+路径优化
//路径优化:叶子节点向上查找根节点,当找到根节点时,将所有的其他刚刚经历过的节点的指向变为x,这样的话之后再去查找就一步到位了
//当p[x]==x时,则x时这个集合(这棵树)的根节点即祖宗节点
{
     
	if(p[x]!=x) p[x]=find(p[x]);//路径优化,把x的父节点的祖宗节点赋值为x的父节点,这样的话数被变成了根节点连着很多单树
	return p[x];//返回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)		//若ab不连通则连通它们,这样一定是最小情况,因为已经按从小到大排好序了
        {
     
            p[a] = b;		
            res += w;
            cnt ++ ;
        }
    }

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

int main()
{
     
    cin>>n>>m;

    for (int i = 0; i < m; i ++ )
    {
     
        int a, b, w;
        cin>>a>>b>>w;
        edges[i] = {
     a, b, w};
    }

    int t = kruskal();

    if (t == INF) puts("impossible");
    else cout<<t<<endl;

    return 0;
}

二分图的有关算法见下篇

你可能感兴趣的:(算法基础课学习记录)