/*
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;
}
Kruskal算法求最小生成树 (最小堆优化)
最新推荐文章于 2024-04-18 01:16:20 发布