Freckles -- 考研 最小生成树

题目描述

In an episode of the Dick Van Dyke show, little Richie connects the freckles on his Dad’s back to form a picture of the Liberty Bell.
so his Ripley’s engagement falls through. Consider Dick’s back to be a plane with freckles at various (x,y) locations. Your job is to tell Richie how to connect the dots so as to minimize the amount of ink used. Richie connects the dots by drawing straight lines between pairs, possibly lifting the pen between lines. When Richie is done there must be a sequence of connected lines from any freckle to any other freckle.

输入描述:

The first line contains 0 < n <= 100, the number of freckles on Dick’s back.
For each freckle, a line follows; each following line contains two real numbers indicating the (x,y) coordinates of the freckle.

输出描述:

Your program prints a single real number to two decimal places: the minimum total length of ink lines that can connect all the freckles.

示例1
输入
3
1.0 1.0
2.0 2.0
2.0 4.0
输出
3.41

分析:

这是一道最小生成树的模板题 代码见下方
最小生成树:
关于图的几个概念定义:
连通图:在无向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该无向图为连通图。
强连通图:在有向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该有向图为强连通图。
连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
最小生成树肯定是n个点,n-1条边 没有环
解决最小生成树一般有两种算法,Kruskal 和Prime算法 都是贪心的算法

kruskal算法

又可以形象的叫做“加边法”,就是先将所有的顶点看作独立的森林,然后把图中所有的边按从小到大排序,每次选权重最小的边,如果所选的边的两个顶点u,v属于两棵不同的树,就将此边加入到最小生成树中,重复这一过程直到所有的点都在数中或者已经有n-1条边
其中判断两个节点是否属于两棵树,可以递归的分别找他们的根节点,然后判断
kruskal算法通过本题:

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#include<cctype>
#include<cmath>
using namespace std;
struct Point //表示坐标
{
    double a, b;
    int num;
};
Point points[105];
struct Edge//边
{
    int s, e;
    double cost;
};
Edge edge[10000];
int Tree[105];//用于寻根
double dis(Point i, Point j)//求距离
{
    return sqrt((i.a - j.a)*(i.a - j.a) + (i.b - j.b)*(i.b - j.b));
}
bool cmp(const  Edge &a, const Edge &b)//对边排序
{
    return (a.cost < b.cost);
}
int findRoot(int x)//递归找根,直到找到根节点为-1的
{
    if (Tree[x] == -1)return x;
    else {
        return findRoot(Tree[x]);
    }
}
int main()
{
    int n;
    while (~scanf("%d", &n)) {
        for (int i = 0; i < n; i++)
        {
            scanf("%lf%lf", &points[i].a, &points[i].b);
            points[i].num = i;
            Tree[i] = -1;
        }
        int t = 0;
        for(int i =0;i<n;i++)
            for (int j = i+1; j < n; j++)
            {
                edge[t].s =i;
                edge[t].e = j;
                edge[t].cost = dis(points[i], points[j]);
                t++;
            }
        sort(edge, edge + t, cmp);
        double ans = 0;
        for (int i = 0; i < t; i++)
        {
            int ar = findRoot(edge[i].s);
            int br = findRoot(edge[i].e);
            if (ar != br)
            {
                Tree[ar] = br;
                ans += edge[i].cost;
            }
        }
        printf("%.2lf\n", ans);
    }
}

Prime算法

又可以叫做加点法
原图中所有顶点集合为V,生成树最终会包含全部n个节点,就先从一个节点开始
令Vtree为最小生成树包含的点,不妨就是第一个点Vtree={v0}, E={所有的边},Etree={最小生成树包含的边,一开始为空}, V‘=V-Vtree={尚未加入生成树中的点}
思路是这样的,每次找这样的边加入到Etree中:
是集合E中权值最小,并且两端节点(v,u)分别属于Vtree和V‘,则把u节点加到Vtree中,边(u,v)加入到Etree中
具体到代码级别可以这样:
mapp[i][j]就是图,表示节点i,j之间的距离,不连通就是INF;
Tree[i]=1表示节点i在生成树中,否则不在;一开始只有Tree[0]=1;
dis[j]表示所有Vtree中节点到V’中节点j的当前最小距离
那么每次检查E中权值最小且Tree[]=0的边,加进去就好了
代码如下:

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#include<cctype>
#include<cmath>
using namespace std;
const int maxx = 105;
double dis[maxx];//dis[j]: min distance between points in Vtree and V-Vtree
double mapp[maxx][maxx];//distance between point i&j
int Tree[maxx];//Vtree
struct node
{
    double x, y;
};
node nodes[maxx];
int n;
const int inf = 100000000;
double Prime()
{
    for (int i = 0; i < n; i++)Tree[i] = 0;
    Tree[0] = 1;
    double ans = 0.0;
    for (int i = 0; i < n; i++)dis[i] = mapp[0][i];//初始时树中0节点到个点的距离
    dis[0] = 0;
    double minn = inf;
    int u;
    for(int i =1;i<n;i++)
    {
        minn = (1 << 29);
        u = -1;
        for (int j = 0; j < n; j++)
        {
            if (Tree[j] == 0 && dis[j] < minn)
            {
                u = j;
                minn = dis[j];
            }
        }
        //printf("u=%d,minn %lf\n", u,minn);
        ans += minn;
        Tree[u] = 1;
        for (int j = 0; j < n; j++)
        //每次生成树中加了新节点u后要遍历检查到剩下的点的距离有没有发生变化
            if (Tree[j] == 0 && dis[j] > mapp[u][j])
                dis[j] = mapp[u][j];
    }
    return ans;
}
int main()
{

    while (~scanf("%d", &n)) {
        for (int i = 0; i < n; i++)
            scanf("%lf%lf", &nodes[i].x, &nodes[i].y);
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
            {
                if (i == j)mapp[i][j] = 0;
                else mapp[i][j] = sqrt((nodes[i].x - nodes[j].x)*(nodes[i].x - nodes[j].x) + (nodes[i].y - nodes[j].y)*(nodes[i].y - nodes[j].y));
                //if(i==25)printf("mapp[%d][%d]=%lf\n", i, j, mapp[i][j]);
            }

        double ans = Prime();
        printf("%.2lf\n", ans);

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值