最小生成树【做题记录】c++(Prim,Kruskal)

目录

Prim算法求最小生成树

【算法思想】

【算法实现】

【数据结构设计】

【算法步骤】

【输入输出】

【代码示例】

Kruskal算法求最小生成树

【算法思想】

判断是否会产生回路的方法

【算法描述】

【图的存储结构】

【输入输出】

【代码示例】


Prim算法求最小生成树

【算法思想】

普里姆算法在找最小生成树时,将顶点分为两类,一类是在查找的过程中已经包含在树中的(设为 已选集合),剩下的是另一类(设为 待选集合)。

对于给定的连通网,起始状态全部顶点都归为 待选集合。在找最小生成树时,选定任意一个顶点作为起始点,并将之从 待选集合移至已选集合;然后找出 待选集合中到已选集合中的顶点之间权值最小的顶点,将之从 待选集合移至已选集合,如此重复,直到 待选集合中没有顶点为止。

所走过的顶点和边就是该连通图的最小生成树。

【算法实现】

输入:网(带权值的图)

输出:依次访问各个顶点 创建一个辅助数组,每次将当前已选类顶点到待选类顶点的距离填写到数组中。然后从中筛选出权值最小的边的邻接点。

在辅助数组中找出权值最小的边的数组下标,就可以间接找到此边的终点顶点。找到后lowcost标记为0。

【数据结构设计】

  1. 图的存储结构:由于在算法执行过程中,需要不断读取任意两个顶点之间边的权值,所以,图采用邻接矩阵存储。
  2. 候选最短边集:设置数组shortEdge[n]表示候选最短边集,数组元素包括adjvex和lowcost两个域,分别表示候选最短边的邻接点和权值。

【算法步骤】

  1. 初始化两个辅助数组lowcost和adjvex;
  2. 输出顶点u0,将顶点u0加入集合U中;
  3. 重复执行下列操作n-1次 3.1 在lowcost中选取最短边,取adjvex中对应的顶点序号k; 3.2 输出顶点k和对应的权值; 3.3 将顶点k加入集合U中; 3.4 调整数组lowcost和adjvex;

【输入输出】

第一行输入顶点的个数n

第二行输入n个顶点的值

第三行输入边的条数m

下面m行依次输入边的起点的值,终点的值和权值。

输出最小生成树,每行输出它的一条边。

【测试数据】

输入:

请输入顶点的个数:6

请依次输入6个顶点的值:

A B C D E F

请输入边的个数:9

请依次输入9个边的起点、终点和权值:

A C 46

A F 19

A B 34

B E 12

E F 26

D E 38

D F 25

C D 17

C F 25

输出:

最小生成树是:

(AF)19

(FC)25

(CD)17

(FE)26

(EB)12

【代码示例】

cpp 复制代码
#include <iostream>
#include <limits.h>
#include <string> // 包含string以便处理顶点的字符表示
#define MAX_V_NUM 20

using namespace std;

struct closedge
{
    char adjvex; // 邻接点的字符表示
    int lowcost; // 到邻接点的最小权值
};

closedge shortEdge[MAX_V_NUM]; // 存储候选最短边集

class MGraph
{
private:
    char vertex[MAX_V_NUM]; // 图的顶点数组
    int arc[MAX_V_NUM][MAX_V_NUM]; // 图的邻接矩阵
    int vexNum, arcNum; // 顶点数和边数

public:
    MGraph(); // 构造函数
    void Prim(int start); // 实现Prim算法
};

MGraph::MGraph()
{
    cout << "请输入顶点的个数:";
    cin >> vexNum;
    cout << "请依次输入" << vexNum << "个顶点的值:" << endl;
    for (int i = 0; i < vexNum; i++)
    {
        cin >> vertex[i];
    }
    cout << "请输入边的个数:";
    cin >> arcNum;
    char vi, vj;
    int w;
    // 初始化邻接矩阵
    for (int i = 0; i < vexNum; i++)
    {
        for (int j = 0; j < vexNum; j++)
        {
            if(i==j)
            {
                arc[i][j] =0;
            }
            else
            {
                arc[i][j]=INT_MAX;
            }
        }
    }
    cout << "请依次输入" << arcNum << "个边的起点、终点和权值:" << endl;
    for (int i = 0; i < arcNum; i++)
    {
        cin >> vi >> vj >> w;
        arc[vi - 'A'][vj - 'A'] = w;
        arc[vj - 'A'][vi - 'A'] = w; // 因为是无向图
    }
    // 假设从顶点0开始Prim算法
    Prim(0);
}

void MGraph::Prim(int start)
{
    // 初始化shortEdge数组
    for (int i = 0; i < vexNum; i++)
    {
        shortEdge[i].lowcost = arc[start][i];
        shortEdge[i].adjvex = start;
    }
    shortEdge[start].lowcost = 0;
    cout << "最小生成树是:" << endl;
    for (int i = 0; i < vexNum - 1; i++)
    {
        int k = 0;
        for (int j = 0; j < vexNum; j++)
        {
            if(shortEdge[j].lowcost!=0&&shortEdge[j].lowcost!=INT_MAX){
                    k=j;
                    break;
            }
        }
        for (int j = 0; j < vexNum; j++){
               if (shortEdge[j].lowcost!=0&&shortEdge[k].lowcost>shortEdge[j].lowcost)
                {
                    k =j;
                    break;
                }
        }
        // 输出最小边
        cout << "(" << vertex[shortEdge[k].adjvex] << vertex[k] << ")" << shortEdge[k].lowcost << endl;
        // 将找到的最小边标记为0,表示已访问
        shortEdge[k].lowcost = 0;
        for (int j = 0; j < vexNum; j++)
        {
            if (arc[k][j]!=0&&arc[k][j] < shortEdge[j].lowcost)
            {
                shortEdge[j].lowcost = arc[k][j];
                shortEdge[j].adjvex = k;
            }
        }
    }
}

int main()
{
    MGraph graph;
    return 0;
}

Kruskal算法求最小生成树

【算法思想】

将所有边按照权值的大小进行升序排序,然后从小到大一一判断,条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。

注意:排序时如果两条边权值相同,起点下标较小边排在前面,保证测试数据的准确。

判断是否会产生回路的方法

1、标记法:在初始状态下给每个顶点赋予不同的标记,对于遍历过程的每条边,其都有两个顶点,判断这两个顶点的标记是否一致,如果一致,说明它们本身就处在一棵树中,如果继续连接就会产生回路;如果不一致,说明它们之间还没有任何关系,可以连接。 假设遍历到一条由顶点 A 和 B 构成的边,而顶点 A 和顶点 B 标记不同,此时不仅需要将顶点 A 的标记更新为顶点 B 的标记,还需要更改所有和顶点 A 标记相同的顶点的标记,全部改为顶点 B 的标记。

2、初始时每个顶点构成一棵生成树,然后每生长一次就将两棵树合并,到最后合并成一棵树。判断两个顶点是否在同一课树中,如果在同一棵树则产生了回路。

判断顶点是否在同一棵树中,可以从顶点出发,寻找树的根结点。如果两个顶点的根结点相同,则属于同一棵树。

【算法描述】

设无向连通网为G=(V, E),令G的最小生成树为T=(U, TE),其初态为U=V,TE={ },然后,按照边的权值由小到大的顺序,考察G的边集E中的各条边。

若被考察的边的两个顶点属于T的两个不同的连通分量,则将此边作为最小生成树的边加入到T中,同时把两个连通分量连接为一个连通分量;若被考察边的两个顶点属于同一个连通分量,则舍去此边,以免造成回路,如此下去,当T中的连通分量个数为1时,此连通分量便为G的一棵最小生成树。

【图的存储结构】

因为Kruskal算法是依次对图中的边进行操作,因此考虑用边集数组存储图中的边,为了提高查找速度,将边集数组按边上的权排序。

struct EdgeType

{

int from,to; //边依附的两个顶点

int weight; //边上的权值

};

【输入输出】

请输入图的顶点数和边数:6 9

请输入顶点的值:0 1 2 3 4 5

请输入边的起点终点和权值:

1 4 12

2 3 17

0 5 19

2 5 25

3 5 25

4 5 26

0 1 34

3 4 38

0 2 46

用Kruskal算法生成最小生成树的生成次序为:

(1,4)12

(2,3)17

(0,5)19

(2,5)25

(4,5)26

【代码示例】

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;
const int MAX = 10000;
template <class T>
struct EdgeType
{
	T from, to;     //边依附的两个顶点
	int weight;      //边上的权值
};
template <class T>
class EdgeGraph
{
private:
	char* vertex;  //存放图顶点的数组
	EdgeType<char>* edge;      //存放边的数组
	int vertexNum, edgeNum;         //图的顶点数和边数
public:
	EdgeGraph(int vNum, int eNum);//构造函数
	void inputGraph();//输入图
	void Kruskal();
	int getIndex(char v)
	{
		for (int i = 0; i < vertexNum; i++) {
			if (vertex[i] == v) {
				return i;
			}
		}
		return -1; // 如果找不到顶点,返回-1
	}
	void sorts();
private:
	T findRoot(int parent[], T v);//找到树的根
};
template <class T>
EdgeGraph<T>::EdgeGraph(int vNum, int eNum)
{
	vertexNum = vNum;
	edgeNum = eNum;
	vertex = new char[vertexNum];
	edge = new EdgeType<T>[edgeNum];
	char v;
	T f, t;
	int w;
	cout << "请输入顶点的值:";
	for (int i = 0; i < vertexNum; i++)
	{
		cin >> v;
		vertex[i] = v;
	}
	cout << "请输入边的起点终点和权值:\n";
	for (int i = 0; i < edgeNum; i++)
	{
		cin >> f >> t >> w;
		edge[i].from = f;
		edge[i].to = t;
		edge[i].weight = w;
	}
	sorts();

}
//对weight排序
// 最开始写的冒泡排序,但是有问题
// 冒泡排序不是对边的数组edge中的元素进行排序,而是对数组的索引进行排序。这意味着数组edge中的元素并没有按照权值从小到大进行排序,而是数组的索引被重新排列,使得具有较小权值的边的索引在前。这并不是Kruskal算法所需要的。
// 需要对边的数组edge中的EdgeType对象按照权值进行排序,而不是对索引进行排序。
// 修正后,使用了C++11的lambda表达式来定义排序准则,即按照weight成员进行比较。这样,edge数组中的EdgeType对象将根据它们的权值进行排序,而不是它们的索引。
template <class T>
void EdgeGraph<T>::sorts()
{
		// 使用标准库的sort函数对边按照权值进行排序
        
		sort(edge, edge + edgeNum, [](const EdgeType<T>& a, const EdgeType<T>& b) {
			return a.weight < b.weight;
			});

}
template <class T>
void EdgeGraph<T>::Kruskal()
{
	int parent[MAX] = { 0 };
	T vex1, vex2;
	sorts();
	for (int i = 0; i < vertexNum; i++)
	{
		parent[i] = -1;
	}
	
	for (int num = 0, i = 0; i < edgeNum; i++)
	{
		//找到所在生成树的根节点
		int idx1 = getIndex(edge[i].from);
		int idx2 = getIndex(edge[i].to);
		vex1 = findRoot(parent, idx1);
		vex2 = findRoot(parent, idx2);
		if (vex1 != vex2) //如果两个根节点不同,不会构成环
		{
			cout << "(" << edge[i].from << "," << edge[i].to << ")" << edge[i].weight << endl;
			parent[vex2] = vex1;//合并生成树
			num++;
			if (num == vertexNum - 1)  //循环了顶点数-1次,提前返回
				return;
		}
	}
}
//求根节点
template <class T>
T EdgeGraph<T>::findRoot(int parent[], T v)
{
	T t = v;
	while (parent[t] > -1)
	{
		t = parent[t];
	}
	return t;
}
int main()
{
	int vNum, eNum;
	cout << "请输入图的顶点数和边数:";
	cin >> vNum >> eNum;
	EdgeGraph<char> edgegraph(vNum, eNum);
	cout << "用Kruskal算法生成最小生成树的生成次序为:\n";
	edgegraph.Kruskal();
	return 0;
}
相关推荐
不去幼儿园12 分钟前
【MARL】深入理解多智能体近端策略优化(MAPPO)算法与调参
人工智能·python·算法·机器学习·强化学习
Mr_Xuhhh14 分钟前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
Ajiang28247353041 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
盼海1 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
网易独家音乐人Mike Zhou5 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
‘’林花谢了春红‘’6 小时前
C++ list (链表)容器
c++·链表·list
机器视觉知识推荐、就业指导7 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Swift社区8 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman9 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法