您的位置:首页 > 游戏 > 手游 > 网络推广培训哪个学校好_微信app下载安装官方版2020_建网站需要什么条件_网络产品及其推广方法

网络推广培训哪个学校好_微信app下载安装官方版2020_建网站需要什么条件_网络产品及其推广方法

2024/12/22 22:46:33 来源:https://blog.csdn.net/D5486789_/article/details/144293337  浏览:    关键词:网络推广培训哪个学校好_微信app下载安装官方版2020_建网站需要什么条件_网络产品及其推广方法
网络推广培训哪个学校好_微信app下载安装官方版2020_建网站需要什么条件_网络产品及其推广方法

目录

1.认识最小生成树

2.最小生成树构建算法

3.Kruskal算法

4.Prim算法

5.源码附录

最小生成树算法源码(Graph.h)

并查集源码(UnionFindSet.h)


1.认识最小生成树

无向图当中,如果我们从任意一个顶点出发能到达图中的其他所有顶点,这样的图我们叫做连通图;连通图中的所有极大无环子图都是原图的一棵生成树。

极大无环子图中的无环和子图好理解,如何理解这里的极大两个字呢?极大指的是在满足某种特性的前提下(连通性),尽可能多的包含原图的顶点和边。假设原连通图有n个顶点,而极大无环子图要保证连通性,肯定要包含n个顶点,又要保证无环,最多只能包含n-1条边。所以,一个连通图的包含n个顶点,n-1条边的连通子图就是生成树

如下所示:

可见,连通图的生成树不止一棵,那么什么是最小生成树呢?我们试着给连通图中的每条边带上权值,权值之和最小的生成树就是该连通图的最小生成树(其意义就是用最小的成本让着n个点连通

如下所示:

由此可见,该连通图的最小生成树生成树C。 

2.最小生成树构建算法

我们已经知道了什么是最小生成树,那如何构建一棵图的最小生成树呢?连通图的生成树必须包含n个顶点和n-1条边(n是原图的顶点个数),因此构建最小生成树的准则有以下三条:

  • 只能使用无向图中的顶点和边来构造最小生成树
  • 只能使用恰好n-1条边来连接图中的n个顶点
  • 选用的n-1条边不能构成回路

构建最小生成树的经典算法有Kruskal算法(克鲁斯卡尔算法)和Prim算法(普里姆算法),这两个算法都采用了逐步求解的贪心策略。

3.Kruskal算法

Kruskal算法的大致思想:先记录连通图所有顶点(n个顶点)和所有边,然后每次都从边集中选取权值最小的边以及这条边对应的两个顶点用于组建最小生成树并且每次添加这条边的时候判断是否会构成环,如果会构成环就舍弃这条边,否则就添加这条边,直到n个顶点都添加进来之后,如果边的条数等于n-1,则构建出来的就是最小生成树。

Kruskal算法流程图:此图源自于《算法导论》

最后的最小生成树如下:权值之和为37。

Kruskal算法流程:任给一个有n个顶点的连通图G{V, E},其中V是点集,E是边集。

  • 首先构造出有n个顶点,不含任何边的图minTree_G{V, NULL},其中,我们可以把每个顶点单独看成一个集合。
  • 然后不断从边集E中取出权值最小的边,如果该边的两个顶点来自不同的集合,则将该边加入到图minTree_G中;如果该边的两个顶点来自同一个集合,在图minTree_G中添加该边就会构成环,需要舍弃。
  • 重复第二点,直到所有的点都在同一个集合中。如果此时的添加的边的条数为n-1条,那么构成的图minTree_G就是图G的最小生成树。

Kruskal算法代码示例:示例代码设置为类的成员函数,由于篇幅原因并没有在此给出图这个类的全部代码,文末附完整源代码

  • 需要注意的是:
  • 我们每添加一条边的时候,都是选取权值最小的边添加,因此我们可以用一个小根堆存储边的信息
  • 而且,我们添加边的时候,都需要判断是否会构成环,也就是判断这条边的两个顶点是否在同一集合中,因此,我们可以使用并查集这种数据结构来判断是否构成环。不了解并查集这种数据结构的朋友可以看一下这篇文章
  • 并查集的原理及实现icon-default.png?t=O83Ahttps://blog.csdn.net/D5486789_/article/details/144227145  (本文末附并查集源码 
/*
* V: 顶点的类型
* W: 权值的类型
* MAX_VAL: 权值的最大值,表示两个顶点之间没有关系,默认是int的最大值
* Direction: 表示该图为有向图还是无向图,默认是无向图
*/
template<class V, class W, W MAX_VAL = INT_MAX, bool Direction = false>
class Graph
{using Self = Graph<V, W, MAX_VAL, Direction>;
public:/*定义边这种数据结构,用于表示边*/struct Edge{// 成员变量size_t _srci;size_t _dsti;W _w;//构造函数Edge(size_t srci, size_t dsti, const W& w):_srci(srci), _dsti(dsti), _w(w){}// 通过权值比较边的大小bool operator>(const Edge& e) const{return this->_w > e._w;}};/** Kruskal算法* 接收一个输出型参数,输出当前连通图的生成树* 返回最小生成树的权值之和*/W Kruskal(Self& minTree){// 为这个输出型参数赋初值,用于获取当前连通图的信息size_t n = _vertexs.size();minTree._indexMap = _indexMap;minTree._vertexs = _vertexs;minTree._matrix.resize(n);for (int i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_VAL);}// 构建存储边的小堆,每次选取权值最小的边std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> minque;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){// 无向图的邻接矩阵是对称的,选取一边即可if (i < j && _matrix[i][j] != MAX_VAL){minque.push(Edge(i, j, _matrix[i][j]));}}}// 选出n-1条边size_t size = 0;W total_w = W();UnionFindSet ufs(n);while (!minque.empty()){// 选取权值最小的边Edge min_edge = minque.top();minque.pop();// 判断如果选取这条边是否构成环if (!ufs.InSet(min_edge._srci, min_edge._dsti)){// 这条边的两个顶点不在一个集合中,可以选取这条边,不会形成环// 添加这条边到最小生成树中minTree.AddEdge(min_edge._srci, min_edge._dsti, min_edge._w);// 将这条边的两个顶点加入到同一个集合ufs.Union(min_edge._srci, min_edge._dsti);// 更新选取的变数和权值和++size;total_w += min_edge._w;}else{// 这条边的两个顶点在一个集合中,不可以选取这条边,会形成环// 提示构成环并打印构成环的边std::cout << "构成环: ";std::cout << _vertexs[min_edge._srci] << "->" << _vertexs[min_edge._dsti] << ":";std::cout << min_edge._w << std::endl;}}// 存在最小生成树就返回总的权值,否则返回权值类型的默认值if (size == n - 1)return total_w;return W();}private:std::vector<V> _vertexs;             // 顶点的集合std::vector<std::vector<W>> _matrix; // 邻接矩阵std::map<V, int> _indexMap;          // 顶点到下标的映射
};

4.Prim算法

Prim算法的大致思想:将图 G{V, E} 中的所有顶点(n个顶点)分成两个集合,分别为集合X和集合Y,集合X初始化为图G中的任意一个顶点,剩余的顶点全部放入集合Y中,每次选取集合X到集合Y中的权值最小的边用于构建最小生成树,直到所有的点都被添加进来之后,如果边的条数为   n-1,则构建出来的就是最小生成树。

  • 需要说明一下,Prim算法的实现同样需要借助这种数据结构建立小根堆,但是可以不依赖并查集。
  • 并且每次选取边的时候,都能够往X集合中添加一个顶点,构建出的图自然而然就是连通的

Prim算法流程图:此图源自于《算法导论》

最终构建出来的最小生成树如下:权值为37

Prim算法代码示例:

/*
* V: 顶点的类型
* W: 权值的类型
* MAX_VAL: 权值的最大值,表示两个顶点之间没有关系,默认是int的最大值
* Direction: 表示该图为有向图还是无向图,默认是无向图
*/
template<class V, class W, W MAX_VAL = INT_MAX, bool Direction = false>
class Graph
{using Self = Graph<V, W, MAX_VAL, Direction>;
public:/*定义边这种数据结构,用于表示边*/struct Edge{// 成员变量size_t _srci;size_t _dsti;W _w;//构造函数Edge(size_t srci, size_t dsti, const W& w):_srci(srci), _dsti(dsti), _w(w){}// 通过权值比较边的大小bool operator>(const Edge& e) const{return this->_w > e._w;}};/** Prim算法* minTree: 这是一个输出型参数,输出当前图的最小生成树* src: 这是一个起始点,Prim算法从指定的起始点开始构建最小生成树*/W Prim(Self& minTree, const V& src){// 输出型参数minTree赋初值size_t n = _vertexs.size();minTree._indexMap = _indexMap;minTree._vertexs = _vertexs;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_VAL);}// 构建出X集合和Y集合size_t srci = GetVertexIndex(src); // 获取起始点的编号std::vector<bool> X(n, false);std::vector<bool> Y(n, true);X[srci] = true;Y[srci] = false;// 建立一个存储边的小根堆std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> min_heap;for (int i = 0; i < n; ++i){if (_matrix[srci][i] != MAX_VAL){min_heap.push(Edge(srci, i, _matrix[srci][i]));}}size_t size = 0; // 记录添加的边的条数W total_w = W(); // 记录总的权值// 开始找边构建最小生成树while (!min_heap.empty()){Edge min_edge = min_heap.top();min_heap.pop();// 判断最小边的目标点是否也在X集合中if (X[min_edge._dsti] == true){// 选的边的目标点也在集合X中,此时就会出现环,所以,此边不能添加到minTree中std::cout << "此边构成环,应舍弃,舍弃的边为 -> ";std::cout << "(" << min_edge._srci << "," << min_edge._dsti << ")";std::cout << ":" << min_edge._w << std::endl;}else{// 选的边的目标点不在集合中,不会出现环,将此边添加到minTree中minTree.AddEdge(min_edge._srci, min_edge._dsti, min_edge._w);// 修该集合中边的从属关系X[min_edge._dsti] = true;Y[min_edge._dsti] = false;// 更新需要记录的值++size;total_w += min_edge._w;// 如果已经添加了n-1条边,说明已经生成了最小生成树,直接结束循环即可if (size == n - 1){break;}// 添加该边的目标点所连接的且没有添加过的边for (size_t i = 0; i < n; ++i){// 没有添加过的点在Y集合中if (_matrix[min_edge._dsti][i] != MAX_VAL && Y[i] == true){min_heap.push(Edge(min_edge._dsti, i, _matrix[min_edge._dsti][i]));}}}}if (size == n - 1){return total_w;}else{return W();}}
private:std::vector<V> _vertexs;             // 顶点的集合std::vector<std::vector<W>> _matrix; // 邻接矩阵std::map<V, int> _indexMap;          // 顶点到下标的映射
};

5.源码附录

最小生成树算法源码(Graph.h)

#include <vector>
#include <map>
#include <queue>          // For std::priority_queue
#include <algorithm>      // For std::greater
#include <stdexcept>      // For std::invalid_argument
#include "UnionFindSet.h" // For class UnionFindSetnamespace Link_Matrix     // 邻接矩阵存储的图
{/** V: 顶点的类型* W: 权值的类型* MAX_VAL: 权值的最大值,表示两个顶点之间没有关系,默认是int的最大值* Direction: 表示该图为有向图还是无向图,默认是无向图*/template<class V, class W, W MAX_VAL = INT_MAX, bool Direction = false>class Graph{using Self = Graph<V, W, MAX_VAL, Direction>;public:/*********************************图的基础操作***********************************/// 默认构造函数Graph() = default;// 构造函数Graph(const V* arr, size_t n){// 把所有的顶点存储起来,并将顶点和顶点的下标建立映射关系,相当于为顶点编号_vertexs.reserve(n);for (int i = 0; i < n; ++i){_vertexs.push_back(arr[i]);_indexMap[arr[i]] = i;}// 为邻接矩阵开辟空间,并全部初始化为MAX_VAL,用于后续手动添加边_matrix.resize(n);for (int i = 0; i < _matrix.size(); ++i){_matrix[i].resize(n, MAX_VAL);}}// 获取顶点的索引size_t GetVertexIndex(const V& vertex){auto it = _indexMap.find(vertex);if (it != _indexMap.end()) // 找到了对应的顶点{return it->second;}else                       // 该顶点不存在{throw invalid_argument("顶点不存在");return -1;}}// 根据点添加边void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);_matrix[srci][dsti] = w;// 如果该图为无向图,对称的位置也要添加对应的权值if (Direction == false){_matrix[dsti][srci] = w;}}// 根据点的编号添加边void AddEdge(size_t srci, size_t dsti, const W& w){_matrix[srci][dsti] = w;// 如果该图为无向图,对称的位置也要添加对应的权值if (Direction == false){_matrix[dsti][srci] = w;}}// 打印图(用于调试)void Print(){// 顶点for (size_t i = 0; i < _vertexs.size(); ++i){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;// 矩阵// 横下标cout << "  ";for (size_t i = 0; i < _vertexs.size(); ++i){//cout << i << " ";printf("%4d", i);}cout << endl;for (size_t i = 0; i < _matrix.size(); ++i){cout << i << " "; // 竖下标for (size_t j = 0; j < _matrix[i].size(); ++j){if (_matrix[i][j] == MAX_VAL){printf("%4c", '*');}else{printf("%4d", _matrix[i][j]);}}cout << endl;}cout << endl;}/**********************************图的最小生成树********************************//*定义边这种数据结构,用于表示边*/struct Edge{// 成员变量size_t _srci;size_t _dsti;W _w;//构造函数Edge(size_t srci, size_t dsti, const W& w):_srci(srci), _dsti(dsti), _w(w){}// 通过权值比较边的大小bool operator>(const Edge& e) const{return this->_w > e._w;}};/** Kruskal算法* 接收一个输出型参数,输出当前连通图的生成树* 返回最小生成树的权值之和*/W Kruskal(Self& minTree){// 为这个输出型参数赋初值,用于获取当前连通图的信息size_t n = _vertexs.size();minTree._indexMap = _indexMap;minTree._vertexs = _vertexs;minTree._matrix.resize(n);for (int i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_VAL);}// 构建存储边的小堆,每次选取权值最小的边std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> minque;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){// 无向图的邻接矩阵是对称的,选取一边即可if (i < j && _matrix[i][j] != MAX_VAL){minque.push(Edge(i, j, _matrix[i][j]));}}}// 选出n-1条边size_t size = 0;W total_w = W();// 使用并查集来表示集合UnionFindSet ufs(n);// 开始选边while (!minque.empty()){// 选取权值最小的边Edge min_edge = minque.top();minque.pop();// 判断如果选取这条边是否构成环if (!ufs.InSet(min_edge._srci, min_edge._dsti)){// 这条边的两个顶点不在一个集合中,可以选取这条边,不会形成环// 添加这条边到最小生成树中minTree.AddEdge(min_edge._srci, min_edge._dsti, min_edge._w);// 将这条边的两个顶点加入到同一个集合ufs.Union(min_edge._srci, min_edge._dsti);// 更新选取的变数和权值和++size;total_w += min_edge._w;}else{// 这条边的两个顶点在一个集合中,不可以选取这条边,会形成环// 提示构成环并打印构成环的边std::cout << "构成环: ";std::cout << _vertexs[min_edge._srci] << "->" << _vertexs[min_edge._dsti] << ":";std::cout << min_edge._w << std::endl;}}// 存在最小生成树就返回总的权值,否则返回权值类型的默认值if (size == n - 1)return total_w;return W();}/** Prim算法* minTree: 这是一个输出型参数,输出当前图的最小生成树* src: 这是一个起始点,Prim算法从指定的起始点开始构建最小生成树*/W Prim(Self& minTree, const V& src){// 输出型参数minTree赋初值size_t n = _vertexs.size();minTree._indexMap = _indexMap;minTree._vertexs = _vertexs;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n,MAX_VAL);}// 构建出X集合和Y集合size_t srci = GetVertexIndex(src); // 获取起始点的编号std::vector<bool> X(n, false);std::vector<bool> Y(n, true);X[srci] = true;Y[srci] = false;// 建立一个存储边的小根堆std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> min_heap;for (int i = 0; i < n; ++i){if (_matrix[srci][i] != MAX_VAL){min_heap.push(Edge(srci, i, _matrix[srci][i]));}}size_t size = 0; // 记录添加的边的条数W total_w = W(); // 记录总的权值// 开始找边构建最小生成树while (!min_heap.empty()){Edge min_edge = min_heap.top();min_heap.pop();// 判断最小边的目标点是否也在X集合中if (X[min_edge._dsti] == true){// 选的边的目标点也在集合X中,此时就会出现环,所以,此边不能添加到minTree中std::cout << "此边构成环,应舍弃,舍弃的边为 -> ";std::cout << "(" << min_edge._srci << "," << min_edge._dsti << ")";std::cout << ":" << min_edge._w << std::endl;}else{// 选的边的目标点不在集合中,不会出现环,将此边添加到minTree中minTree.AddEdge(min_edge._srci, min_edge._dsti, min_edge._w);// 修该集合中边的从属关系X[min_edge._dsti] = true;Y[min_edge._dsti] = false;// 更新需要记录的值++size;total_w += min_edge._w;// 如果已经添加了n-1条边,说明已经生成了最小生成树,直接结束循环即可if (size == n - 1){break;}// 添加该边的目标点所连接的且没有添加过的边for (size_t i = 0; i < n; ++i){// 没有添加过的点在Y集合中if (_matrix[min_edge._dsti][i] != MAX_VAL && Y[i] == true){min_heap.push(Edge(min_edge._dsti, i, _matrix[min_edge._dsti][i]));}}}}if (size == n - 1){return total_w;}else{return W();}}private:std::vector<V> _vertexs;             // 顶点的集合std::vector<std::vector<W>> _matrix; // 邻接矩阵std::map<V, int> _indexMap;          // 顶点到下标的映射};
}

并查集源码(UnionFindSet.h)

#include <iostream>
#include <vector>
#include <map>class UnionFindSet
{
public:// 构造函数UnionFindSet(size_t n):_ufs(n, -1){}// 合并两个元素所在的集合void Union(int x1, int x2){int root1 = FindRoot(x1);int root2 = FindRoot(x2);// 如果本身就在一个集合就没必要合并了if (root1 == root2)return;// 控制数据量小的往大的集合合并if (abs(_ufs[root1]) < abs(_ufs[root2]))std::swap(root1, root2);_ufs[root1] += _ufs[root2];_ufs[root2] = root1;}// 查找一个元素所在的集合int FindRoot(int x){int root = x;while (_ufs[root] >= 0){root = _ufs[root];}// 路径压缩while (_ufs[x] >= 0){int parent = _ufs[x];_ufs[x] = root;x = parent;}return root;}// 判断两个元素是否在同一个集合bool InSet(int x1, int x2){return FindRoot(x1) == FindRoot(x2);}// 获取并查集中集合的个数size_t SetSize(){size_t size = 0;for (size_t i = 0; i < _ufs.size(); ++i){if (_ufs[i] < 0){++size;}}return size;}private:std::vector<int> _ufs;  // 用vector充当并查集的底层结构
};

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com