最小生成树——克鲁斯卡尔(Kruskal)算法详解和实现

最小生成树

  • 克鲁斯卡尔(Kruskal)算法
    • 代码实现
      • Kruskal算法代码
      • 辅助代码
        • 边类
        • 并查集
        • 最小堆
    • 算法效率分析
    • 思考

克鲁斯卡尔(Kruskal)算法

  1. 克鲁斯卡尔算法的基本思想是:设一个有n个顶点的连通网络G={V,E},
    (1)先构造一个包括全部n个顶点和0条边的森林F={T0,T1,…,Tn-1},
    (2)以后每一步向下中加入一条边(v,u),它应是所依附的两个顶点v和u分别在森林F的两棵不同的树上的所有边中具有最小权值的边。由于这条边的加入,使F中的某两棵树合并为一棵,树的棵数减一。
    (3)如此,经过n-1步,最终得到一棵有n-1条边且各边权值总和达到最小的生成树,即最小生成树。
  2. 例如,对于图7-18(a)所示的连通网络,图7-19中(a)~(f)给出了按克鲁斯卡尔算法生成最小生成树的过程。
    最小生成树——克鲁斯卡尔(Kruskal)算法详解和实现_第1张图片
  3. 在克鲁斯卡尔算法中,利用最小堆来存放连通网络中的边,堆中每个元素代表连通网络中的一条边,它由三个域组成:adjvex1、adjvex2和 weight。
    其中adjvex1和adjvex2存储该边所依附的两个顶点的序号,weight存储边上的权值。
    利用并查集存放所有连通分量,同一个连通分量的顶点组成并查集的一个子集(等价类)
  4. 克鲁斯卡尔算法步骤如下。
    1)初始化,在并查集中,连通网络的每一个顶点独立成一个等价类,连通网络的所有的边建立最小堆,最小生成树T中没有任何边,T中边的条数计数器i为0。
    2)如果T中边的条数计数器i等于顶点数减1,则算法结束;否则继续步骤3)。
    3)选取堆顶元素代表的边(v,u),同时调整堆。
    4)利用并查集的运算检查依附于边(v,u)的两个顶点v和u是否在同一个连通分量(即并查集的同一个子集合)上,如果是则转步骤2);否则继续步骤5)。
    5)将边(v,u)加入到最小生成树T中,同时将这两个顶点所在的连通分量合并成一个连通分量(即并查集中的相应两个子集合并成一个子集),继续步骤2)。

代码实现

Kruskal算法代码

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 &copy);		     // 复制构造函数
	UFSets &operator =(const UFSets &copy);	// 赋值运算符
};


// 并查集的实现部分
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)
// 操作结果:由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)
// 操作结果:将copy赋值给当前对象——赋值运算符
{
	if (&copy != 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)。由此可见,克鲁斯卡尔算法适用于稀疏的(顶点个数较多而边数较少)连通网络。

思考

  1. Q:除了堆找权值最小,还有什么方法?
    A:在遍历边的过程中我们会访问到所有的边的权值,所以我们可以选择在一次遍历的过程中就用一个辅助数组对所有的边进行排序,那么我们只要遍历一次就得到了所有边的大小关系,以后在取最小边时也只需按数组顺序依次取出即可。这样所花费的平均时间也会适当的减少。
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:既然我们想知道两个点是否在一棵树上,我们也可以通过从一条边的端点出发,看看能不能通过搜索的方式搜索到另一个端点,如果可以那么就说明这两个点位于同一棵树。判断是否连通也可以用广度/深度遍历,在之前已经实现过。

你可能感兴趣的:(#,数据结构,数据结构,c++)