【1】什么是最小生成树?
对于连通的带权图(连通网)G,其生成树也是带权的。
生成树T各边的权值总和称为该树的权。
权最小的生成树称为G的最小生成树(Minimum SpannirngTree)。简记为MST。
注意:最小是指权值最小
一个连通图的生成树是一个极小的连通子图,它包含全部的顶点,但只有足以构成一棵树的n-1条边。
求最小生成树有两种算法:普里姆算法和克鲁斯卡尔算法
不好理解?看不懂?能通俗点不?看个实例哈:
假设你是电信实施工程师,需要为一个镇的九个村庄架设通信网络做设计。
村庄位置大致如下图,之间连线的数字表示村与村间的可通达直线距离。
你们领导要求你必须用最小的成本完成这次任务。你说怎么办?
好,这就是很现实的一个最小生成树案例。且听下面详解。
【2】普里姆算法
利用 普里姆算法 要解决如上问题,首先我们构造图的邻接矩阵。如下图所示:
注意:实际中我们用65535来代表无穷大。
关于普里姆算法以及讲解如下图:
针对上面我们遇到的实际案例,普里姆算法执行循环过程如下图:
每次所选最小边分别如 图1-图8 所示:
最后用所有边把各个顶点连通也就是所谓的最小生成树。
【3】普里姆算法的实现
实现代码如下:
#include <iostream> #include "SeqList.h" #include <iomanip> using namespace std; #define INFINITY 65535 template<class NameType, class DistType> class Graph { private: SeqList<NameType> Vertices; DistType **Edges; int nVer, nEdges; public: Graph() : Edges(NULL) , nEdges(0) , nVer(0) {} ~Graph() {} public: istream & operator>>(istream &in) { int v, u, value; int i, j; NameType item; cout << "请输入顶点的个数: " << endl; in >> nVer; cout << "请输入顶点的数据信息: " << endl; for (i = 0; i < nVer; ++i) { in >> item; Vertices.push_back(item); // 保存全部顶点 } /////二维数组的创建并初始化 Edges = new DistType*[nVer]; // DistType *ar[10]; for (i = 0; i < nVer; ++i) { Edges[i] = new DistType[nVer]; for (j = 0; j < nVer; ++j) { Edges[i][j] = 0; } } cout << "请输入边的个数: " << endl; in >> nEdges; cout << "请输入边的信息:" << endl; for (i = 0; i < nEdges; ++i) { in >> v >> u >> value; Edges[v][u] = value; Edges[u][v] = value; } return in; } ostream & operator<<(ostream &out) const { int i, j; out << "顶点信息 " << endl; for (i = 1; i <= nVer; ++i) { out << Vertices[i] << setw(5); } out << endl; out << "矩阵信息:" << endl; out << setw(10); for (i = 1; i <= nVer; ++i) { out << Vertices[i] << setw(5); } out << endl; for (i = 0; i < nVer; ++i) { out << Vertices[i+1] << setw(5); for (j = 0; j < nVer; ++j) { if (0 == Edges[i][j] && i != j) Edges[i][j] = INFINITY; cout << Edges[i][j] << setw(5); } out << endl; } out << endl; return out; } // 图采用邻接矩阵存储 最小生成树 普里姆算法 void MiniSpanTree() { int min = 0, i = 0, j = 0, k = 0; int* adjvex = new int[nVer]; // 保存相关顶点下标 int* lowcost = new int[nVer]; // 保存相关顶点间边的权值 lowcost[0] = 0; // 初始化第一个权值为0,即V0已加入生成树 //lowcost的值为0,在这里就是此下标的顶点已经加入生成树 adjvex[0] = 0; // 初始化第一个顶点下标为0 for (i = 1; i < nVer; ++i) //循环除过下标为0外的全部顶点 { lowcost[i] = Edges[0][i]; // 将v0顶点与之有边的权值存入数组 adjvex[i] = 0; // 并初始化都为v0的下标 } for (i = 1; i < nVer; ++i) { min = INFINITY; // 初始化最小权值为常数,通常设置为不可能到达的数值 k = 0; // 复位 for (j = 1; j < nVer; ++j) { // 如果两个顶点之间存在边有权值,不为0并且小于min if (lowcost[j] != 0 && lowcost[j] < min) { min = lowcost[j]; // 缓存最小值 k = j; // 将当前最小值的下标缓存入k } } cout << "("<< adjvex[k] << "," << k << ")" << endl; //打印当前顶点边中权值最小的边 lowcost[k] = 0; // 将当前顶点的权值设为0,表示此顶点已经完成任务 for (j = 1; j < nVer; ++j) // 循环所有节点 { if (lowcost[j] != 0 && Edges[k][j] < lowcost[j]) { // 若下标为k顶点各边权值小于此前这些顶点未被加入生成树权值 lowcost[j] = Edges[k][j]; // 用较小者替换 adjvex[j] = k; // 将下标为k的顶点存入adjvex } } } delete []adjvex; delete []lowcost; adjvex = NULL; lowcost = NULL; } }; template<class NameType, class DistType> istream & operator>>(istream &in, Graph<NameType,DistType> &g) { g >> in; return in; } template<class NameType, class DistType> ostream & operator<<(ostream &out, const Graph<NameType,DistType> &g) { g << out; return out; } void main() { Graph<char, int> myg; cin >> myg; cout << "打印所有输入信息:" << endl; cout << myg << endl; cout << "最小生成树边信息如下:" << endl; myg.MiniSpanTree(); cout << endl; }
代码中所引用是头文件SeqList.h
#include <iostream> #include <assert.h> #include <typeinfo.h> #include <string.h> using namespace std; #define UM_QUIT 0 #define UM_INPUTR 1 #define UM_PRINT 2 #define UM_INPUTH 3 #define FINDVALUE 4 #define UM_INSERT 5 #define UM_REMOVE 6 #define UM_SORT 7 #define UM_REMOVEALL 8 #define UM_REVERSE 9 #define UM_ERASE 10 #define UM_PVPOS 11 #define MAXSIZE 10 #define INCSIZE 5 template <class Type> class SeqList { private: Type* m_pData; //数据区域 size_t m_nMaxSize; //最大容量 size_t m_nLen; //元素个数 public: SeqList(size_t size = MAXSIZE); SeqList(const SeqList<Type>& sqList); SeqList<Type>& operator=(const SeqList<Type>& sqList); ~SeqList(); public: size_t GetMaxSize() const; void SetMaxSize(size_t nMaxSize) const; size_t GetLength() const; void SetLength(size_t nLen) const; public: bool IsFull() const; bool IsEmpty() const; bool IsElement(const Type& Value); void Push_Back(const Type& Value); void Push_Front(const Type& Value); void At(const size_t pos); size_t FindPos(const Type& Value); void Insert(const size_t pos, const Type& Value); void Remove(const Type& Value); void Erase(const size_t pos); void RemoveAll(const Type& Value); void Sort(); void Reverse(); void Print() const; private: bool IncSize(); void Swap(const size_t leftpos, const size_t rightpos); }; //基本属性函数 template <class Type> size_t SeqList<Type>::GetMaxSize() const { return m_nMaxSize; } template <class Type> void SeqList<Type>::SetMaxSize(size_t nMaxSize) const { m_nMaxSize = nMaxSize; } template <class Type> size_t SeqList<Type>::GetLength() const { return m_nLen; } template <class Type> void SeqList<Type>::SetLength(size_t nLen) const { m_nLen = nLen; } // 作用:扩充顺序表容量 // 当数据数量大于最大容量时,为顺序表增加容量 // 增加度量为INCSIZE template <class Type> bool SeqList<Type>::IncSize() { Type* pNewDate = new Type[m_nMaxSize + INCSIZE + 1]; if (NULL == pNewDate) return false; memcpy(pNewDate, m_pData, sizeof(Type) * (m_nLen + 1)); m_pData = pNewDate; m_nMaxSize += INCSIZE; return true; } //交换数据 template <class Type> void SeqList<Type>::Swap(const size_t leftpos, const size_t rightpos) { Type temp = m_pData[leftpos]; m_pData[leftpos] = m_pData[rightpos]; m_pData[rightpos] = temp; } //默认复合构造函数 template <class Type> SeqList<Type>::SeqList(size_t size = MAXSIZE) { m_nMaxSize = size > MAXSIZE ? size : MAXSIZE; m_pData = new Type[m_nMaxSize + 1]; assert(m_pData != NULL); m_nLen = 0; } //拷贝构造函数 template <class Type> SeqList<Type>::SeqList(const SeqList<Type>& sqList) { if (this != &sqList) { m_pData = new Type[sqList.m_nMaxSize + 1]; if (m_pData != NULL) { memcpy(m_pData, sqList.m_pData, sizeof(Type) * (sqList.m_nLen + 1)); } m_nMaxSize = sqList.m_nMaxSize; m_nLen = sqList.m_nLen; } } //赋值函数(思考什么时候调用赋值函数) template <class Type> SeqList<Type>& SeqList<Type>::operator=(const SeqList<Type>& sqList) { m_nLen = sqList.m_nLen; m_nMaxSize = sqList.m_nMaxSize; memcpy(m_pData, sqList.m_pData, sizeof(Type) * (sqList.m_nLen + 1)); return *this; } //析构函数 template <class Type> SeqList<Type>::~SeqList() { if (m_pData != NULL) { delete []m_pData; m_pData = NULL; } m_nMaxSize = 0; m_nLen = 0; } //判满 template <class Type> bool SeqList<Type>::IsFull() const { return m_nLen == m_nMaxSize; } //判空 template <class Type> bool SeqList<Type>::IsEmpty() const { return 0 == m_nLen; } //是否是顺序表中元素 template <class Type> bool SeqList<Type>::IsElement(const Type& Value) { size_t pos = FindPos(Value); return 0 != pos; } //从顺序表末尾添加元素 template <class Type> void SeqList<Type>::Push_Back(const Type& Value) { if (IsFull() && !IncSize()) //注意写法用意 return; m_pData[++m_nLen] = Value; } //从顺序表首部添加元素 template <class Type> void SeqList<Type>::Push_Front(const Type& Value) { if (IsFull() && !IncSize()) return; for (size_t i = m_nLen; i > 0; --i) { m_pData[i + 1] = m_pData[i]; } ++m_nLen; m_pData[1] = Value; } //输出对应索引处的数据 template <class Type> void SeqList<Type>::At(const size_t pos) { if (pos <= 0 || pos > m_nLen) cout << "输入下标有误!" << endl; else cout << pos << "位置的数据为:" << m_pData[pos] << endl; } //查找某数据的索引 template <class Type> size_t SeqList<Type>::FindPos(const Type& Value) { m_pData[0] = Value; size_t i = m_nLen; while (m_pData[i] != Value) { --i; } return i; } //在索引处插入值 template <class Type> void SeqList<Type>::Insert(const size_t pos, const Type& Value) { if (pos <= 0 || pos > m_nLen + 1) return; if (IsFull() && !IncSize()) return; ++m_nLen; for (size_t i = m_nLen; i > pos; --i) { m_pData[i] = m_pData[i-1]; } m_pData[pos] = Value; } //移除某个值 template <class Type> void SeqList<Type>::Remove(const Type& Value) { if (IsElement(Value)) { size_t pos = FindPos(Value); Erase(pos); } else { cout << "输入数据不存在!" << endl; } } //按索引删除数据 template <class Type> void SeqList<Type>::Erase(const size_t pos) { if (pos <= 0 || pos > m_nLen) { cout << "输入错误" << endl; } else { for (size_t i = pos; i < m_nLen; ++i) { m_pData[i] = m_pData[i+1]; } --m_nLen; cout << "删除成功!删除后结果如下:" << endl; Print(); } } //删除全部(删除所有出现在顺序表中的某值) template <class Type> void SeqList<Type>::RemoveAll(const Type& Value) { if (IsElement(Value)) { for (size_t i = m_nLen; i > 0; --i) { if (m_pData[i] == Value) Remove(Value); } cout << "删除全部成功!删除后结果如下:" << endl; Print(); } else { cout << "输入的数据不存在!" << endl; } } //顺序表数据排序 template <class Type> void SeqList<Type>::Sort() { if (m_nLen > 2) { size_t i = 0, j = 0; for (i = 2; i <= m_nLen; ++i) { if (m_pData[i] < m_pData[i - 1]) { m_pData[0] = m_pData[i]; j = i - 1; do { m_pData[j + 1] = m_pData[j]; --j; } while(m_pData[j] > m_pData[0]); m_pData[j + 1] = m_pData[0]; } } } } //逆置顺序表 template <class Type> void SeqList<Type>::Reverse() { if (m_nLen > 2) { size_t i = 0, j = 0; for (i = 1, j = m_nLen; i < j; ++i, --j) { Swap(i, j); } cout << "逆置结果如下:" << endl; Print(); } } //打印顺序表数据信息 template <class Type> void SeqList<Type>::Print() const { cout << "容量:" << m_nMaxSize << endl; cout << "元素的个数:" << m_nLen << endl; cout << "数据如下:" << endl; for (size_t i = 1; i <= m_nLen; ++i) { cout << m_pData[i] << " "; } cout << endl; }