template<class ElemType,class WeightType> void MiniSpanTreeKruskal(const AdjMatrixUndirGraph<ElemType,WeightType> &g)
{
int count,VexNum=g.GetVexNum();
KruskalEdge<ElemType,WeightType> KEdge;
MinHeap<KruskalEdge<ElemType,WeightType> > ha(g.GetArcNum());
ElemType *kVex,v1,v2;
kVex=new ElemType[VexNum];
for(int i=0; i<VexNum; i++)
g.GetElem(i,kVex[i]);
UFSets<ElemType> f(kVex,VexNum);//根据顶点数组构造并查集
for(int v=0; v<VexNum; v++)
for(int u=g.FirstAdjVex(v); u>=0; u=g.NextAdjVex(v,u))
if(v<u)//将v
{
g.GetElem(v,v1);
g.GetElem(u,v2);
KEdge.vertex1=v1;
KEdge.vertex2=v2;
KEdge.weight=g.GetWeight(v,u);
ha.Insert(KEdge);
}
///至此是初始化,将边加入最小堆中
count=0;
cout<<"Kruskal最小生成树需要连接一下边:"<<endl;
while(count<VexNum-1)
{
ha.DeleteTop(KEdge);
v1=KEdge.vertex1;
v2=KEdge.vertex2;
if(f.Differ(v1,v2))//边所依附的两顶点不在同一棵树上
{
cout<<"edge:( "<<v1<<" , "<<v2<<" ) weight:"<<KEdge.weight<<endl;
f.Union(v1,v2);
count++;
}
}
}
template<class ElemType,class WeightType> class KruskalEdge
{
public:
ElemType vertex1,vertex2;
WeightType weight;
KruskalEdge(ElemType v1,ElemType v2,WeightType w);
KruskalEdge() {}
KruskalEdge<ElemType,WeightType>& operator=(const KruskalEdge<ElemType,WeightType> &Ed);
bool operator<=(const KruskalEdge<ElemType,WeightType> &Ed);
bool operator>(const KruskalEdge<ElemType,WeightType> &Ed);
friend ostream& operator<<(ostream &out,const KruskalEdge<ElemType,WeightType> &Ed)
{
out<<"( "<<Ed.vertex1<<" , "<<Ed.vertex2<<" ) weight:"<<Ed.weight;
return out;
}
};
template<class ElemType,class WeightType> KruskalEdge<ElemType,WeightType>::
KruskalEdge(ElemType v1,ElemType v2,WeightType w)
{
vertex1=v1;
vertex2=v2;
weight=w;
}
template<class ElemType,class WeightType> bool
KruskalEdge<ElemType,WeightType>::operator<=(const KruskalEdge<ElemType,WeightType> &Ed)
{
return (weight<=Ed.weight);
}
template<class ElemType,class WeightType> bool
KruskalEdge<ElemType,WeightType>::operator>(const KruskalEdge<ElemType,WeightType> &Ed)
{
return (weight>Ed.weight);
}
template<class ElemType,class WeightType> KruskalEdge<ElemType,WeightType>&
KruskalEdge<ElemType,WeightType>::operator=(const KruskalEdge<ElemType,WeightType> &Ed)
{
if(&Ed!=this)
{
vertex1=Ed.vertex1;
vertex2=Ed.vertex2;
weight=Ed.weight;
}
return *this;
}
#ifndef __ELEM_NODE_H__
#define __ELEM_NODE_H__
// 并查集元素结点类
template <class ElemType>
struct ElemNode
{
// 数据成员:
ElemType data; // 数据域
int parent; // 双亲域
};
#endif
#ifndef __UFSETS_H__ // 如果没有定义__UFSETS_H__
#define __UFSETS_H__ // 那么定义__UFSETS_H__
// 并查集
template <class ElemType>
class UFSets
{
protected:
// 并查集的数据成员:
ElemNode<ElemType> *sets; // 存储结点的双亲
int size; // 结点个数
// 辅助函数
int Find(ElemType e) const; // 查找元素e所在等价类的根
int CollapsingFind(ElemType e) const;// 查找元素e所在等价类的根
public:
// 并查集的函数成员:
UFSets(ElemType es[], int n); // 构造sz个单结点树(等价类)
virtual ~UFSets(); // 析构函数
ElemType GetElem(int p)const; // 取指定元素在数组中的下标
int GetOrder(ElemType e)const; // 根据指定下标取元素值
void Union(ElemType a, ElemType b); // 合并a与b所在的等价类
void WeightedUnion(ElemType a, ElemType b); // 根据结点多少合并a与b所在的等价类
bool Differ(ElemType a, ElemType b); // 判断元素a、b是否在同一个等价类
UFSets(const UFSets ©); // 复制构造函数
UFSets &operator =(const UFSets ©); // 赋值运算符
};
// 并查集的实现部分
template <class ElemType>
UFSets<ElemType>::UFSets(ElemType es[], int n)
// 操作结果:根据数组es中的元素,构造n个单元素等价类
{
size = n; // 设置容量
sets = new ElemNode<ElemType>[size]; // 分配空间
for (int i = 0; i < size; i++) {
sets[i].data = es[i];
sets[i].parent = -1;
}
}
template <class ElemType>
int UFSets<ElemType>::Find(ElemType e) const
// 操作结果:查找元素e所在树的根
{
int p = 0;
while (p < size && sets[p].data != e)
p++;
if (p == size)
return -1; // 集合中不存在元素e
while (sets[p].parent > -1)
p = sets[p].parent; // 查找根
return p;
}
template <class ElemType>
int UFSets<ElemType>::CollapsingFind(ElemType e) const
// 操作结果:带压缩路径功能,查找元素e所在树的根
{
int i, k, p = 0;
while (p < size && sets[p].data != e)
p++;
if (p == size)
return -1; // 集合中不存在元素e
for(i = p ; sets[i].parent >= 0; i= sets[i].parent) ; //查找p的根结点的序号i
while ( i!= sets[p].parent ) { //从p开始向上逐层压缩
k = sets[p].parent ;
sets[p].parent = i;
p = k;
}
return i;
}
template <class ElemType>
UFSets<ElemType>::~UFSets()
// 操作结果:释放对象占用的空间——析构函数
{
delete []sets; // 释放数组parent
}
template <class ElemType>
ElemType UFSets<ElemType>::GetElem(int p) const
// 操作结果:求下标为p的元素值
{
if (p < 0 || p >= size)
throw Error("元素不存在!"); // 抛出异常
return sets[p].data; // 返回元素值
}
template <class ElemType>
int UFSets<ElemType>::GetOrder(ElemType e) const
// 操作结果:取指定元素e的下标
{
int p = 0;
while (p < size && sets[p].data != e)
{
p++;
}
if (p == size)
return -1; // 集合中不存在元素e
return p; // 返元素下标
}
template <class ElemType>
void UFSets<ElemType>::Union(ElemType a, ElemType b)
// 操作结果:合并a与b所在的等价类
{
int r1 = Find(a); // 查找a所在等价类的根
int r2 = Find(b); // 查找b所在等价类的根
if (r1 != r2 && r1 != -1) {
sets[r1].parent += sets[r2].parent;
sets[r2].parent = r1; // 合并等价类
}
}
template <class ElemType>
void UFSets<ElemType>::WeightedUnion(ElemType a, ElemType b)
// 操作结果:根据结点多少合并a与b所在的等价类
{
int r1 = Find(a); // 查找a所在等价类的根
int r2 = Find(b); // 查找b所在等价类的根
if (r1 != r2 && r1 != -1) {
int temp = sets[r1].parent + sets[r2].parent;
if (sets[r1].parent <= sets[r2].parent ) {
sets[r2].parent = r1;
sets[r1].parent = temp;
}
else {
sets[r1].parent = r2; //r1中的结点个数少,r1指向r2
sets[r2].parent = temp;
}
}
}
template <class ElemType>
bool UFSets<ElemType>::Differ(ElemType a, ElemType b)
// 操作结果:如果a与b不在同一等价类上,返回true,否则返回false
{
int r1 = Find(a); // 查找a所在等价类的根
int r2 = Find(b); // 查找b所在等价类的根
return r1 != r2;
}
template <class ElemType>
UFSets<ElemType>::UFSets(const UFSets ©)
// 操作结果:由copy构造新对象——复制构造函数
{
size = copy.size; // 设置容量
sets = new ElemNode<ElemType>[size]; // 分配空间
for (int i = 0; i < size; i++)
sets[i] = copy.sets[i]; // 复制每个元素
}
template <class ElemType>
UFSets<ElemType> &UFSets<ElemType>::operator=(const UFSets<ElemType> ©)
// 操作结果:将copy赋值给当前对象——赋值运算符
{
if (© != this) {
size = copy.size; // 设置容量
delete []sets; // 释放原空间
sets = new ElemNode<ElemType>[size];// 分配新空间
for (int i = 0; i < size; i++)
sets[i] = copy.sets[i]; // 复制每个元素
}
return *this;
}
template<class type> class MinHeap
{
private:
type *heapArr;
int CurrentSize;
int Maxsize;
void FilterDown(const int start);
void FilterUp(const int end);
public:
MinHeap(int maxsize);
MinHeap(type a[],int maxsize,int n);
~MinHeap(){delete [] heapArr;}
bool IsFull(){return CurrentSize==Maxsize;}
bool IsEmpty(){return CurrentSize==0;}
void Insert(const type &e);
void DeleteTop(type &e);
void Show()const;
};
template<class type> MinHeap<type>::MinHeap(int maxsize)
{
if(maxsize<=0)
{
cout<<"maxsize<=0"<<endl;
exit(1);
}
Maxsize=maxsize;
heapArr=new type[Maxsize];
CurrentSize=0;
}
template<class type> void MinHeap<type>::FilterDown(const int start)
{
int i=start,j;
type temp=heapArr[i];
j=2*i+1;//取i的左孩子j
while(j<=CurrentSize-1)//i不是叶子结点,i有孩子节点
{
if(j<CurrentSize-1&&(heapArr[j]>heapArr[j+1]))
j++;
if(temp<=heapArr[j])
break;
else
{
heapArr[i]=heapArr[j];
i=j;
j=2*j+1;
}
}
heapArr[i]=temp;
}
template<class type> MinHeap<type>::MinHeap(type a[],int maxsize,int n)
{
if(n<=0)
{
cout<<"maxsize<=0"<<endl;
exit(1);
}
Maxsize=maxsize;
heapArr=new type[Maxsize];
for(int i=0;i<n;i++)
heapArr[i]=a[i];
CurrentSize=n;
int i=(CurrentSize-2)/2;//取序列最右边的分支结点
while(i>=0)
{
FilterDown(i);
i--;
}
}
template<class type> void MinHeap<type>::Insert(const type &e)
{
if(IsFull())
{
cout<<"over flow"<<endl;
return;
}
heapArr[CurrentSize]=e;
FilterUp(CurrentSize);
CurrentSize++;
return;
}
template<class type> void MinHeap<type>::FilterUp(const int end)
{
int j=end,i;
type temp=heapArr[j];
i=(j-1)/2;
while(j>0)
{
if(heapArr[i]<=temp)
break;
else
{
heapArr[j]=heapArr[i];
j=i;
i=(j-1)/2;
}
heapArr[j]=temp;
}
}
template<class type> void MinHeap<type>::DeleteTop(type &e)
{
if(IsEmpty())
{
cout<<"under flow"<<endl;
return;
}
e=heapArr[0];
heapArr[0]=heapArr[CurrentSize-1];
CurrentSize--;
FilterDown(0);
return;
}
template<class type> void MinHeap<type>::Show()const
{
cout<<endl<<"The MinHeap:"<<endl;
int level=1;
for(int i=1;i<=CurrentSize;i++)
{
cout<<heapArr[i-1]<<'\t';
if(i==level)
{
cout<<endl;
level=level*2+1;
}
}
}
设连通网络由n个顶点和e条边组成,则克鲁斯卡尔算法在初始建立最小堆时需要e次调用堆的插入操作,因此初始建堆的时间复杂度为O(elog2(e))。在构造最小生成树时,最多调用e次堆的删除操作、2e次并查集的查找操作和n-1次并查集的合并操作,计算时间分别为O(elog2(e))、O(elog2(e))和O(n)。由于n小于e,所以总的时间复杂度为O(elog2e)。由此可见,克鲁斯卡尔算法适用于稀疏的(顶点个数较多而边数较少)连通网络。
for(i=0; i<VexNum; i++)
{
for(j=0; j<VexNum; j++)
{
if( i<j && g.GetWeight(i,j)!=0 && g.GetWeight(i,j)!= g.GetInfinity())//权值满足条件
{
for(k=0;k<length;k++)
if(edge[k].weight > g.GetWeight(i,j))
break;//找到合适的插入位置
index=k;
for(k=length;k>index;k--)//数组元素右移
edge[k]=edge[k-1];
length++;
//插入边
edge[index].vertex1=i;
edge[index].vertex2=j;
edge[index].weight=g.GetWeight(i,j);
}
}
}
相较于堆插入方法,每次按权排序是O(n^3),另外边上需要加一个标记域以判别是否访问过。
2. Q:除了并查集能够勘定是否在同一个连通分量(是否成环),还有没有别的方法?
A:既然我们想知道两个点是否在一棵树上,我们也可以通过从一条边的端点出发,看看能不能通过搜索的方式搜索到另一个端点,如果可以那么就说明这两个点位于同一棵树。判断是否连通也可以用广度/深度遍历,在之前已经实现过。