寒假最小生成树

最小生成树

树的遍历
BFS:一开始队列中有一个点,将一个点出队,将它的子结点全都入队。
DFS:递归到一个点时,依次递归它的子结点。

无根树变有根树
选择一个点作为根结点,开始遍历。
遍历到一个点时,枚举每一条连接它和另一个点的边。若另一个点不是它的父结点,那就是它的子结点。递归到子结点。

并查集
处理不相交合并和查询问题。

邻接表存图:

const int N=1005;
const int M=10050;
int point[N],to[M],next[M],cc
void AddEdge(int x,int y)
{
    cc++;
    to[cc]=y;
    next[cc]=point[x];
    point[x]=cc;
}
void find(int x)
{
    int now=point[x];
    while(now)
    {
        printf("%d\n",now);
        now=next[now];
    }
}
int main()
{
}    

链式前向星

struct edge
{
    int end;
    edge *suc;
    edge(int _end,edge *_suc):end(_end),suc(_suc){}
}*head[N];
void add_edge(int u,int v)
{
    head[u]=new edge(v,head[u]);
}

最小生成树
稀疏图用kruskal,稠密图用prim

prim:
先随机找一个点x作为根,然后维护一个集合S(存已经连通的点)和一个集合D(存当前连接S中所有点的线段,两端点都在S中的除外)
初始化S={x},D={x所连接的所有边};
每次在D中选一个权值最小的边,然后将该边删去。该边所连接的另一个点放入S中,直到S中点数=全部点数。
这里记顶点数v,边数e
邻接表:O(elog2v)

kruskal:
将边权从小到大排序,每次选择边权最小的,如果当前边连接的点已经连通了,就不选这条边。
利用并查集维护是否连通。
e为图中的边数
邻接表:O(elog2e)

最小生成树:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

struct node{
    int x,y,z;
    }a[200005];

int n,m,ans;
int f[5010];

bool cmp(node x,node y){
    return x.z<y.z;
}//快排按边权排序

int find(int x){
    return f[x]==x?x:f[x]=find(f[x]);
}//并查集压缩路径


int main()
{
    int n,m, cnt = 0;
    cin>>n>>m;
        for(int i=1;i<=m;i++){
            cin>>a[i].x>>a[i].y>>a[i].z;
        }
        sort(a+1,a+m+1,cmp);//排序使权值从小到大顺序

    for(int i=1;i<=n;i++){
        f[i]=i;//找顶点,最开始为序号1;
    }
    for(int i=1;i<=m;i++){
        if(find(a[i].x)!=find(a[i].y)){
            cnt++;
            ans+=a[i].z;
            f[find(a[i].x)] = f[find(a[i].y)];
   //         f[f[a[i].x]]=f[a[i].y];
        }
    }
    if(cnt == n-1)  cout<<ans<<endl;
    else    cout<<"orz"<<endl;
    
    
    return 0;
}

洛谷:买礼物[https://blog.csdn.net/qq_40400202/article/details/82355202]

kij =kji :无向图

建立的过程:要买所有的物品,把物品看做点,优惠看做边,然后建边,求一次最小生成树再加上第一件的A元就行了

1.Well
【题目描述】
缺水的村庄需要水。乡村规划者决定挖造若干口井来缓解缺水危机。一共有N个村庄,在第i号村庄挖井的代价是ai。为了节约财力,两个村庄均可以修建水管来共享水资源,以此来获得水源。第i个村庄与第j个村庄修建水管的代价为bij。若要让每个乡村都有水资源,请问需要最小花费代价是多少。
【输入格式】
第一行两个正整数N。
第二行N个正整数依次表示在1到N号村庄挖井代价。
接下来N行,每行N个正整数,第i行第j列的数字w表示从第i号村庄到第j号村庄修建代价为w(保证bij = bji且bii = 0)。
【输入格式】
一个正整数代表最小花费代价。
【输入样例】
4
5 4 4 3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
【输出样例】
9
【数据范围】
1 <= N,ai,bij <= 1000

#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
int B,tot,sum,num,now=1;
int st[10010];
int father[10010];
//最小生成树用来解决最小代价如何用最小的“代价”用N-1条边连接N个点的问题.
struct Node
{
    int th1;//顶点
    int th2;//边
    int pri;//权值
}rec[1000000];


bool cmp(Node a,Node b)
{
    return a.pri<b.pri;//sort()函数,Sort(start,end,cmp),cmp用于规定排序的方法,可不填,默认升序。
}



int find(int x)//压缩路径,使的每次寻找的时候只有一个
{
    if( x==father[x] )
        return x;
    else
         return father[x]=find( father[x] );
}

/*int find(int x){
    return f[x]==x?x:f[x]=find(f[x]);
}//并查集压缩路径
*/

void unite(int x,int y)//按顺序枚举每一条边。如果这条边连接着两个不同的集合,那么就把这条边加入最小生成树,这两个不同的集合就合并成了一个集合;如果这条边连接的两个点属于同一集合,就跳过。直到选取了n-1条边为止。
{
    x=find(x);
    y=find(y);
    father[x]=y;
}
int main()
{
    cin>>B;
    for(int i=1; i<=B; i++)
        father[i]=i;
    for(int i=1;i<=B;i++){
            cin>>st[i];
        }
    for(int i=0; i<=B; i++)
        for(int j=1; j<=B; j++)
        {
        	if(i==0)
        	{
        		rec[now].th1=0;
        		rec[now].th2=j;
        		rec[now].pri=st[j];
        		now++;
        		continue;
        	}
            cin>>num;
            if(num==0)continue;
            rec[now].th1=i;
            rec[now].th2=j;
            rec[now].pri=num;
            now++;
        }

    sort( rec+1,rec+now,cmp );

    for(int i=1; i<now; i++)
        if( find(rec[i].th1) != find(rec[i].th2) )
        {
            unite(rec[i].th1 , rec[i].th2);
            tot+=rec[i].pri;
            sum++;
        }
    cout<<tot;
    return 0;

}




一、关于并查集

  1. 定义
    并查集(Disjoint-Set)是一种可以动态维护若干个不重叠的集合,并支持合并与查询两种操作的一种数据结构。

  2. 基本操作

  3. 合并(Union/Merge)1:合并两个集合。

  4. 查询(Find/Get):查询元素所属集合。
    实际操作时,我们会使用一个点来代表整个集合,即一个元素的根结点(可以理解为父亲)。

  5. 具体实现
    我们建立一个数组fa[ ]或pre[ ]表示一个并查集,fa[i]表示i的父节点。
    初始化:每一个点都是一个集合,因此自己的父节点就是自己fa[i]=i
    查询:每一个节点不断寻找自己的父节点,若此时自己的父节点就是自己,那么该点为集合的根结点,返回该点。
    修改:合并两个集合只需要合并两个集合的根结点,即fa[RootA]=RootB,其中RootA,RootB是两个元素的根结点。

路径压缩:
实际上,我们在查询过程中只关心根结点是什么,并不关心这棵树的形态(有一些题除外)。因此我们可以在查询操作的时候将访问过的每个点都指向树根,这样的方法叫做路径压缩,单次操作复杂度为O(logN)。
结合下图食用更好(图为状态压缩的过程):
图片3.

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值