图的基本算法(最小生成树)

假设以下情景,有一块木板。板上钉上了一些钉子。这些钉子能够由一些细绳连接起来。假设每一个钉子能够通过一根或者多根细绳连接起来。那么一定存在这种情况,即用最少的细绳把全部钉子连接起来。

更为实际的情景是这种情况。在某地分布着N个村庄。如今须要在N个村庄之间修路,每一个村庄之前的距离不同,问怎么修最短的路,将各个村庄连接起来。

以上这些问题都能够归纳为最小生成树问题,用正式的表述方法描写叙述为:给定一个无方向的带权图G=(V, E),最小生成树为集合T, T是以最小代价连接V中全部顶点所用边E的最小集合。 集合T中的边能够形成一颗树。这是由于每一个节点(除了根节点)都能向上找到它的一个父节点。

解决最小生成树问题已经有前人开道,Prime算法和Kruskal算法。分别从点和边下手攻克了该问题。

Prim算法

Prim算法是一种产生最小生成树的算法。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现。并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现。1959年,艾兹格·迪科斯彻再次发现了该算法。

Prim算法从随意一个顶点開始,每次选择一个与当前顶点集近期的一个顶点,并将两顶点之间的边增加到树中。

Prim算法在找当前近期顶点时使用到了贪婪算法。

算法描写叙述:

1. 在一个加权连通图中,顶点集合V。边集合为E

2. 随意选出一个点作为初始顶点,标记为visit,计算全部与之相连接的点的距离,选择距离最短的,标记visit.

3. 反复以下操作,直到全部点都被标记为visit

在剩下的点钟。计算与已标记visit点距离最小的点,标记visit,证明增加了最小生成树。

以下我们来看一个最小生成树生成的过程:

1 起初,从顶点a開始生成最小生成树

2 选择顶点a后,顶点啊置成visit(涂黑),计算周围与它连接的点的距离:

3 与之相连的点距离分别为7,6,4,选择C点距离最短,涂黑C,同一时候将这条边高亮增加最小生成树:

4 计算与a,c相连的点的距离(已经涂黑的点不计算),由于与a相连的已经计算过了,仅仅须要计算与c相连的点,假设一个点与a,c都相连。那么它与a的距离之前已经计算过了,假设它与c的距离更近。则更新距离值。这里计算的是未涂黑的点距离涂黑的点的近期距离,非常明显,ba7bc的距离为6。更新b和已訪问的点集距离为6,而f,ec的距离各自是8,9,所以还是涂黑b,高亮边bc

5 接下来非常明显。d距离b最短。将d涂黑。bd高亮:

6 f距离d7,距离b4。更新它的最短距离值是4。所以涂黑f,高亮bf

7 最后仅仅有e了:

针对如上的图,代码实比例如以下:

#include<iostream>
#define INF 10000
using namespace std;
const int N = 6;
bool visit[N];
int dist[N] = { 0, };
int graph[N][N] = { {INF,7,4,INF,INF,INF},   //INF代表两点之间不可达
                    {7,INF,6,2,INF,4},
                    {4,6,INF,INF,9,8},
                    {INF,2,INF,INF,INF,7},
                    {INF,INF,9,INF,INF,1},
                    {INF,4,8,7,1,INF}
                  };
int prim(int cur)
{
    int index = cur;
    int sum = 0;
    int i = 0;
    int j = 0;
    cout << index << " ";
    memset(visit, false, sizeof(visit));
    visit[cur] = true;
    for (i = 0; i < N; i++)
        dist[i] = graph[cur][i];//初始化。每一个与a邻接的点的距离存入dist
    for (i = 1; i < N; i++)
    {
        int minor = INF;
        for (j = 0; j < N; j++)
        {
            if (!visit[j] && dist[j] < minor)          //找到未訪问的点中,距离当前最小生成树距离最小的点
            {
                minor = dist[j];
                index = j;
            }
        }
        visit[index] = true;
        cout << index << " ";
        sum += minor;
        for (j = 0; j < N; j++)
        {
            if (!visit[j] && dist[j]>graph[index][j])      //运行更新,假设点距离当前点的距离更近,就更新dist
            {
                dist[j] = graph[index][j];
            }
        }
    }
    cout << endl;
    return sum;               //返回最小生成树的总路径值
}
int main()
{
    cout << prim(0) << endl;//从顶点a開始
    return 0;
}

Kruskal算法

Kruskal是还有一个计算最小生成树的算法,其算法原理例如以下。首先,将每一个顶点放入其自身的数据集合中。然后,依照权值的升序来选择边。当选择每条边时,推断定义边的顶点是否在不同的数据集中。假设是,将此边插入最小生成树的集合中,同一时候。将集合中包括每一个顶点的联合体取出。假设不是。就移动到下一条边。反复这个过程直到全部的边都探查过。

以下还是用一组图示来表现算法的过程:

1 初始情况,一个联通图,定义针对边的数据结构,包括起点,终点。边长度:

typedef struct _node{
    int val;   //长度
    int start; //边的起点
    int end;   //边的终点
}Node;

2 在算法中首先取出全部的边,将边依照长短排序,然后首先取出最短的边,将a,e放入同一个集合里,在实现中我们使用到了并查集的概念:

3 继续找到第二短的边,将c, d再放入同一个集合里:

4 继续找。找到第三短的边ab,由于a,e已经在一个集合里。再将b增加:

5 继续找,找到b,e。由于b,e已经同属于一个集合,连起来的话就形成环了。所以边be不增加最小生成树:

6 再找,找到bc,由于c,d是一个集合的。a,b,e是一个集合。所以再合并这两个集合:

这样全部的点都归到一个集合里,生成了最小生成树。

依据上图实现的代码例如以下:

#include<iostream>
#define N 7
using namespace std;
typedef struct _node{
    int val;
    int start;
    int end;
}Node;
Node V[N];
int cmp(const void *a, const void *b)
{
    return (*(Node *)a).val - (*(Node*)b).val;
}
int edge[N][3] = {  { 0, 1, 3 },
                    { 0, 4, 1 },
                    { 1, 2, 5 },
                    { 1, 4, 4 },
                    { 2, 3, 2 },
                    { 2, 4, 6 },
                    { 3, 4, 7}
                    };

int father[N] = { 0, };
int cap[N] = {0,};

void make_set()              //初始化集合,让全部的点都各成一个集合。每一个集合都仅仅包括自己
{
    for (int i = 0; i < N; i++)
    {
        father[i] = i;
        cap[i] = 1;
    }
}

int find_set(int x)              //推断一个点属于哪个集合,点假设都有着共同的祖先结点,就能够说他们属于一个集合
{
    if (x != father[x])
     {
        father[x] = find_set(father[x]);
    }
    return father[x];
}                                  

void Union(int x, int y)         //将x,y合并到同一个集合
{
    x = find_set(x);
    y = find_set(y);
    if (x == y)
        return;
    if (cap[x] < cap[y])
        father[x] = find_set(y);
    else
    {
        if (cap[x] == cap[y])
            cap[x]++;
        father[y] = find_set(x);
    }
}

int Kruskal(int n)
{
    int sum = 0;
    make_set();
    for (int i = 0; i < N; i++)//将边的顺序按从小到大取出来
    {
        if (find_set(V[i].start) != find_set(V[i].end))     //假设改变的两个顶点还不在一个集合中,就并到一个集合里。生成树的长度加上这条边的长度
        {
            Union(V[i].start, V[i].end);  //合并两个顶点到一个集合
            sum += V[i].val;
        }
    }
    return sum;
}
int main()
{
    for (int i = 0; i < N; i++)   //初始化边的数据,在实际应用中可依据详细情况转换而且读取数据,这边仅仅是測试用例
    {
        V[i].start = edge[i][0];
        V[i].end = edge[i][1];
        V[i].val = edge[i][2];
    }
    qsort(V, N, sizeof(V[0]), cmp);
    cout << Kruskal(0)<<endl;
    return 0;
}

原文地址:https://www.cnblogs.com/zhchoutai/p/8452876.html

时间: 2024-10-10 05:34:42

图的基本算法(最小生成树)的相关文章

数据结构与算法系列研究七——图、prim算法、dijkstra算法

图.prim算法.dijkstra算法 1. 图的定义 图(Graph)可以简单表示为G=<V, E>,其中V称为顶点(vertex)集合,E称为边(edge)集合.图论中的图(graph)表示的是顶点之间的邻接关系. (1) 无向图(undirect graph)      E中的每条边不带方向,称为无向图.(2) 有向图(direct graph)      E中的每条边具有方向,称为有向图.(3) 混合图       E中的一些边不带方向, 另一些边带有方向.(4) 图的阶      指

zw&#183;准专利&#183;高保真二值图细部切分算法

zw·准专利·高保真二值图细部切分算法     高保真二值图细部切分算法,是中国字体协会项目的衍生作品.     说准专利算法,是因为对于图像算法的标准不了解,虽然报过专利,但不是这方面的,需要咨询专业的专利顾问.     原型是用opencv+python实现的,因为Halcon,对于协会的设计师,门槛太高,所以,特意设计了一套opencv+python的live-cd,解压即可,无需配置. 中国传统书法,有很多飞白.泼墨的手法,产生了很多小孔.孤点,从图像学角度,这些都是细小的感染区. 传统

ppt 图的基本算法 Bfs

输入: 8 91 21 32 42 53 63 74 85 86 7 // 图的BFS,使用C++队列#include <stdio.h>#include <string.h>#include <queue>using namespace std;#define N 10int g[N][N],bz[N],n,m;queue <int> q;void BFS(int cur){ int j; bz[cur]=1; q.push(cur); while (!q

ppt 图的基本算法 dfs

#include <stdio.h>#include<string.h>#define N 10int g[N][N];int bz[N];int n,m ;void DFS(int cur){ int j; bz[cur]=1; printf("V%d",cur); for(j=1;j<=n ;j++ ) if(g[cur][j] && !bz[j]) DFS(j); } void input(){ int i,j,f,t ; scanf

图形化排序算法比较:快速排序、插入排序、选择排序、冒泡排序

图形化排序算法比较:快速排序.插入排序.选择排序.冒泡排序

带权图的最短路径算法(Dijkstra)实现

一,介绍 本文实现带权图的最短路径算法.给定图中一个顶点,求解该顶点到图中所有其他顶点的最短路径 以及 最短路径的长度.在决定写这篇文章之前,在网上找了很多关于Dijkstra算法实现,但大部分是不带权的.不带权的Dijkstra算法要简单得多(可参考我的另一篇:无向图的最短路径算法JAVA实现):而对于带权的Dijkstra算法,最关键的是如何“更新邻接点的权值”.本文采用最小堆主辅助,以重新构造堆的方式实现更新邻接点权值. 对于图而言,存在有向图和无向图.本算法只需要修改一行代码,即可同时实

实验四 图的遍历算法设计与实现

一.实验名称:图的遍历算法设计与实现 二.实验目的: 1.掌握图的深度优先遍历的算法. 2.掌握图的广度优先遍历的算法. 3.实验章节:算法设计与分析 第四章 三.实验内容.实验问题和程序运行结果 第一部分 广度优先遍历算法 1. 分析Graph类,画出Graph类初始化以后的Graph对象的数据结构图. 2. 分析BFS函数,画出流程图. 3. 上述程序   int data[7][7]={{ 1,-1,-1,-1,-1,-1,-1}, { 6, 3, 2,-1,-1,-1,-1}, { 0,

图的割点算法、图的割边算法

割点算法 • 在一个无向连通图中,如果删除某个顶点后,图不再连通(即任意两点之间不能相互到达),我们称这样的顶点为割点(或者称割顶). 判断一个顶点是不是割点除了从定义,还可以从DFS(深度优先遍历)的角度出发.我们先通过DFS定义两个概念. 假设DFS中我们从顶点U访问到了顶点V(此时顶点V还未被访问过),那么我们称顶点U为顶点V的父顶点,V为U的孩子顶点.在顶点U之前被访问过的顶点,我们就称之为U的祖先顶点. 显然如果顶点U的所有孩子顶点可以不通过父顶点U而访问到U的祖先顶点,那么说明此时去

图的遍历算法:DFS、BFS

在图的基本算法中,最初需要接触的就是图的遍历算法,根据访问节点的顺序,可分为深度优先搜索(DFS)和广度优先搜索(BFS). DFS(深度优先搜索)算法 Depth-First-Search 深度优先算法,是一种用于遍历或搜索树或图的算法.沿着树的深度遍历树的节点,尽可能深的搜索树的分支. 当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点. 这一过程一直进行到已发现从源节点可达的所有节点为止. 如果还存在未被发现的节点, 则选择其中一个作为源节点并重复以上过程,整个进程反复

图基本算法 最小生成树 Prim算法(邻接表+优先队列STL)

这篇文章是对<算法导论>上Prim算法求无向连通图最小生成树的一个总结,其中有关于我的一点点小看法. 最小生成树的具体问题可以用下面的语言阐述: 输入:一个无向带权图G=(V,E),对于每一条边(u, v)属于E,都有一个权值w. 输出:这个图的最小生成树,即一棵连接所有顶点的树,且这棵树中的边的权值的和最小. 举例如下,求下图的最小生成树: 这个问题是求解一个最优解的过程.那么怎样才算最优呢? 首先我们考虑最优子结构:如果一个问题的最优解中包含了子问题的最优解,则该问题具有最优子结构. 最小