Kruskal算法求最小生成树 (最小堆优化)

/*
	Name: Kruskal算法求最小生成树 (最小堆优化)
	Copyright: 
	Author: 巧若拙 
	Date: 28-02-17 14:27
	Description: 实现了 Kruskal算法求最小生成树 (三元组边表集)的最小堆优化算法。
	关于并查集的算法,参见《一种简单而有趣的数据结构——并查集》http://blog.csdn.net/qiaoruozhuo/article/details/39674991 
*/
#include<iostream>

using namespace std;

const int MAXV=2000;   //最大顶点数量 
const int MAXE=2000;   //最大边数量
const int INFINITY=999999;   //无穷大 

class EdgeNode //图的三元组边表集类  
{
    public:
	    void PrintEdgeNode() {printf("<%d, %d> = %d\t", u, v, w);}
        void SetEdgeNode(int _u, int _v, int _w) {u = _u; v = _v; w = _w;}
        int GetU() {return u;}
	    int GetV() {return v;}
	    int GetW() {return w;}
	    friend bool operator >(const EdgeNode &op1, const EdgeNode &op2) {return op1.w > op2.w;}
	    friend bool operator <(const EdgeNode &op1, const EdgeNode &op2) {return op1.w < op2.w;}
	    EdgeNode operator =(const EdgeNode &op) {u = op.u; v = op.v; w = op.w; return *this;}
    private:
	   int u, v;  //弧尾和弧头 
	   int w; //权值,对于非网图可以不需要  
};

template <typename type> class MinHeap //一个简化的最小堆类  
{
	public:
		   MinHeap(int maxSize); //创建一个容量为maxSize的空堆
		   bool IsEmpty() const {return size == 0;}
		   bool IsFull() const {return size == capacity;}
		   type GetMin() const {return heap[0];} //返回堆顶的最小元素 
		   bool Insert(const type &x); //将x插入到最小堆
		   bool DeleteMin(); //删除堆顶的最小元素
		      
	private:
			type *heap;   //存放堆的元素的数组 
			int capacity; //堆的容量 
			int size;     //堆的长度,即当前元素个数 
			
			void FilterDown(int i); //从下标i到m自顶向下进行调整成为最小堆
			void FilterUp(int i); //从下标i到0自底向上进行调整成为最小堆	
};

template <typename type> MinHeap<type>::MinHeap(int maxSize)
{
    capacity = maxSize;
    heap = new type[capacity];
    size = 0;
}

template <typename type> void MinHeap<type>::FilterDown(int i) //从下标i到堆的尾部自顶向下进行调整成为最小堆
{
    type t = heap[i];   //保存heap[i]的值以备放到适合的位置 
    int child = i * 2 + 1; //指向左孩子   
    
    while (child < size) //有左孩子 
    {
 	    if (child+1 < size && heap[child+1] < heap[child]) //有右孩子,且右孩子更小些,定位其右孩子   
            child += 1;  
          
        if (heap[child] < t)//用最小值覆盖其父亲结点的值,即将空位下滤到新的位置 
        {  
      	    heap[i] = heap[child];      
  		    i = child; 
  		    child = i * 2 + 1;
        }  
        else  
            break;  
	}
    
    heap[i] = t;
}

template <typename type> void MinHeap<type>::FilterUp(int i) //从下标i到0自底向上进行调整成为最小堆
{
    type t = heap[i];
    
    while (i > 0 && heap[i/2] > t) //若比父亲结点小,则用父亲结点的值覆盖该结点,即将空位上滤到新的位置 
    {
        heap[i] = heap[i/2];	   
        i /= 2;
	}
    
    heap[i] = t;
}

template <typename type> bool MinHeap<type>::Insert(const type &x) //将x插入到最小堆
{
    if (IsFull())
    {
        cerr << "heap is full" << endl;
        return false;
    }
    //从尾部插入并向上调整成最小堆,然后长度增1 
    heap[size++] = x;
    FilterUp(size-1); 
    return true;
}

template <typename type> bool MinHeap<type>::DeleteMin() //删除堆顶的最小元素
{
    if (IsEmpty())
    {
        cerr << "heap is empty" << endl;
        return false;
    }
    
    heap[0] = heap[--size];//用尾部元素覆盖顶部元素,然后长度减1
    FilterDown(0); //顶部元素向下调整成最小堆
    return true;
}

void CreatGraph(EdgeNode *E, int m, int n);//创建三元组边表集图  
void CreatGraph_2(EdgeNode *E, int m, int n);//创建三元组边表集图 (随机图) 
bool Locate(EdgeNode *E, int n, int u, int v);//判断边(u,v)是否出现过 
void PrintGraph(EdgeNode *E, int m);//输出图
int FindFatherAndReducePath(int father[], int pos);//查找族长并压缩路径:找到族长后,将所途经的前辈结点均指向族长
bool UnionBySize(int father[], int posI, int posJ);//按大小求并:将成员posI和posJ合并到同一个家族
void Kruskal(EdgeNode *E, int m, int n);//克鲁斯卡尔算法求最小生成树

int main()
{
	EdgeNode E[MAXE];
	int i, m, n;

	printf("请输入顶点数量:"); 
    scanf("%d", &n);
    printf("\n请输入边数量:"); 
    scanf("%d", &m);
    
    CreatGraph(E, m, n);//创建三元组边表集图 
    PrintGraph(E, m);//输出图
    Kruskal(E, m, n);//克鲁斯卡尔算法求最小生成树

    system("pause");				   
	return 0;
}

void CreatGraph(EdgeNode *E, int m, int n)//创建三元组边表集图 
{
    int u, v, w;
   
    printf("\n请按照a b c格式输入边信息:\n"); 
    for (int i=0; i<m; i++)
    {
        scanf("%d%d%d", &u, &v, &w);
        E[i].SetEdgeNode(u, v, w);
    }
} 

void CreatGraph_2(EdgeNode *E, int m, int n)//创建三元组边表集图 (随机图) 
{
    for (int i=1; i<n; i++)//每个点都和0结点邻接,确保是连通图
    {
	 	E[i-1].SetEdgeNode(0, i, rand() % 100 + 1);
    } 
 
    int top = n - 1; //已经有n-1条边了 
    while (top < m)//共m条边 
    {
        for (int i=1; i<n; i++) 
        {
            for (int j=i+1; j<n; j++)
            {
                if (rand()%100 == 0) //有10%的概率出现边
                {//因为有多次循环来设置边,要避免为同一条边设置多次 
                    if (!Locate(E, top, i, j))//判断边(i,j)是否出现过 
                    {   
						E[top++].SetEdgeNode(i, j, rand() % 100 + 1);
                        if (top == m)
                            return;
                    }
                } 
            }
        }
    }
} 

bool Locate(EdgeNode *E, int n, int u, int v)//判断边(u,v)是否出现过 
{
    int i;
    for (i=0; i<n; i++) 
    {
    	if (E[i].GetU() == u && E[i].GetV() == v)
    		return true;
    }
    
    return false;
}

void PrintGraph(EdgeNode *E, int m)//输出图
{
    int i;
    
    for (i=0; i<m; i++)
    {
        E[i].PrintEdgeNode();
    }
    printf("\n");
} 

void Kruskal(EdgeNode *E, int m, int n)//克鲁斯卡尔算法求最小生成树
{
	int father[MAXV] = {0};
    MinHeap<EdgeNode> h(m);
    EdgeNode e;
	
	for (int i=0; i<m; i++) //初始化工作,创建一个最小堆 
	{
		h.Insert(E[i]);  
	}
    
    for (int i=0; i<n; i++)//初始化每个家族的成员都是1,为便于比较,取家族成员数的相反数 
    	father[i] = -1;
    
    int minDis = 0;
    for (int i=1; i<n;)//只需选择n-1条边即可构造一棵最小生成树 
    {
	 	e = h.GetMin(); 
	 	h.DeleteMin();
	 	
    	if (UnionBySize(father, e.GetU(), e.GetV()))//判断该边的两个顶点是否已经连通,未连通则按大小求并
    	{
    		printf("<%d, %d> = %d   ", e.GetU(), e.GetV(), e.GetW());
    		minDis += e.GetW();
    		i++;
    	}
    }
    
    printf("最小生成树总长度(权值)为 %d\n", minDis); 
} 

int FindFatherAndReducePath(int father[], int pos)//查找族长并压缩路径:找到族长后,将所途经的前辈结点均指向族长
{
    if (father[pos] < 0)
		return pos;
    //若自己不是族长,则找到族长后,将所途经的结点均指向族长   
	return father[pos] = FindFatherAndReducePath(father, father[pos]);
}

bool UnionBySize(int father[], int posI, int posJ)//按大小求并:将成员posI和posJ合并到同一个家族
{
    //首先各自去寻找自己的族长
    int fI = FindFatherAndReducePath(father, posI);
    int fJ = FindFatherAndReducePath(father, posJ);

    if (fI == fJ) //如果是同一个族长门下,不必合并,即合并失败 
        return false;
        
    if (father[fI] < father[fJ])
    {//如果族长fI的实力比fJ强,即|fI|>|fJ|,则fI当族长,并修改father[fI]和father[fJ]
        father[fI] += father[fJ];
        father[fJ] = fI;
    }
    else              //否则fJ当族长
    {
        father[fJ] += father[fI];
        father[fI] = fJ;
    }
    
    return true;
}

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值