最小生成树:连通加权无向图的一个连通子图,满足边的数量为顶点个数n减一,且所有边的权值之和最小。
Kruskal算法
初始化生成树为空,将所有边存入小根堆中,然后循环取出权值最小的边,如果取出的边放入生成树后不会在树中产生环,那么就将这条边加入生成树,若产生环,则将这条边丢弃。当加入生成树的边数到达(n-1)时,循环停止,此时生成树就是最小生成树;若生成树的边数未到达(n-1)而小根堆已空,说明无法产生最小生成树。
如何判断加入边之后生成树是否有环:加入边之后产生环的前提是加入边之前边的两顶点v1,v2在生成树中已连通,即两顶点是否在同一子图中。判断是否连通则可以使用并查集,通过判断两顶点在并查集中的根节点是否相同来判断,只有当两顶点在同一子图中是,它们的根节点才会相同。
Prim算法
初始化生成树为空,将所有边存入有序链表(从小到大排列)中,设生成树中初始顶点为1号顶点。循环从链表中取出一条边,这条边是满足一个顶点在生成树中,而另一个顶点不在生成树中的最小的边,将这条边和这个新顶点加入生成树中。加入生成树的边数到达(n-1)时,循环停止,此时生成树就是最小生成树;若在遍历链表时发现已经无法找到满足要求的边或生成树的边数未到达(n-1)而链表已空,都说明无法产生最小生成树。
最小生成树是加权无向图中的概念,如果对加权有向图使用上述两种算法,也会产生一个子图,不过这个子图的边都是有向的(可能有些顶点只有出边而无入边),因此无法保证这个图一定连通。
代码
关于图的完整代码见[C++]有向图(邻接数组描述)
bool Kruskal(edge<T> spanningTreeEdges[])//最小生成树kruskal算法,生成树的所有边存放到spanningTreeEdges[]中
{
edge<T>* edgeArray = new edge<T>[e + 1];//用于存放图的所有边
edgeArray[0] = edge<T>(0, 0, 0);
int k = 0;
int numberOfEdge = e;
for (int i = 1; i <= n; i++)//将图的所有边存入数组
{
for (int j = 1; j <= n; j++)
{
if (a[i][j] != noEdge)
{
k++;
edgeArray[k] = edge<T>(i, j, a[i][j]);
}
}
}
minHeap<edge<T>> heap(edgeArray, e, e + 1);//将边的数组放入小根堆
unionFind uf(n);//并查集
k = 0;
while (numberOfEdge > 0 && k < n - 1)
{
edge<T> x = heap.front();//取出堆中的最小边
heap.pop();
numberOfEdge--;
int a = uf.find(x.vertex1);//比较取出边的两顶点是否在同一子图中,从而判断加入这条边后是否会成环
int b = uf.find(x.vertex2);
if (a != b)//如果不成环就将这条边加入到生成树中
{
spanningTreeEdges[k] = x;
k++;
uf.unite(a, b);//此时这两个顶点已经连通,所以要将它们在并查集中的根节点合并
}
}
return k == n - 1;//返回是否能够产生最小生成树
}
bool Prim(edge<T> spanningTreeEdges[])//最小生成树Prim算法,生成树的所有边存放到spanningTreeEdges[]中
{
graphChain<edge<T>> edgeChain;//用链表存放所有边
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (a[i][j] != noEdge)
{
edge<T> newEdge(i, j, a[i][j]);
edgeChain.insert(newEdge);//此处insert方法确保链表按照从小到大排序
}
}
}
int* treeVertex = new int[n + 1];//记录已经存在于树中的顶点,0代表不存在,1代表存在
for (int i = 0; i <= n; i++)
treeVertex[i] = 0;
treeVertex[1] = 1;//此算法默认从1开始生成树
int k = 0;//k记录生成树的边数,生成树的边存放在spanningTreeEdges[]中
while ((!edgeChain.empty()) && k < n - 1)
{
chainNode<edge<T>>* currentNode = edgeChain.firstNode;
//以下表示用currentNode找到有序链表中的一条边,这条边满足一个顶点在树中,另一个顶点不在树中
while (currentNode!=NULL&&(!((treeVertex[currentNode->element.vertex1] == 1 && treeVertex[currentNode->element.vertex2] == 0) || (treeVertex[currentNode->element.vertex1] == 0 && treeVertex[currentNode->element.vertex2] == 1))))
{
currentNode = currentNode->next;
}
if (currentNode == NULL)//如果没找到这种边则直接退出循环
break;
edge<T> theNode(currentNode->element.vertex1, currentNode->element.vertex2, currentNode->element.weight);
edgeChain.erase(theNode);
spanningTreeEdges[k] = theNode;
treeVertex[theNode.vertex1] = 1;
treeVertex[theNode.vertex2] = 1;
k++;
}
return k == n - 1;
}