算法导论实验——贪心算法求解最小生成树问题

1.贪心算法求解最小生成树问题:
要求:分别用c/c++实现prim算法和Kruskal算法求解一个网络的最小生成树;
分析两种算法的时间复杂度和各自的特点

2.代码:

//c实现prim算法源代码:
#include 
#include 
#define N 100

int p[N], key[N], tb[N][N];

void prim(int v, int n)
{
	int i, j;
	int min;

	for (i = 1; i <= n; i++)
	{
		p[i] = v;
		key[i] = tb[v][i];
	}
	key[v] = 0;
	for (i = 2; i <= n; i++)
	{
		min = INT_MAX;
		for (j = 1; j <= n; j++)
		if (key[j] > 0 && key[j] < min)
		{
			v = j;
			min = key[j];
		}
		printf("%d--%d ", p[v], v);
		key[v] = 0;
		for (j = 1; j <= n; j++)
		if (tb[v][j] < key[j])
			p[j] = v, key[j] = tb[v][j];
	}
}

int main()
{
	int n, m;
	int i, j;
	int u, v, w;
	while (scanf("%d%d", &n, &m))
	{
		for (i = 1; i <= n; i++)
		{
			for (j = 1; j <= n; j++)
				tb[i][j] = INT_MAX;
		}

		while (m--)
		{
			scanf("%d%d%d", &u, &v, &w);
			tb[u][v] = tb[v][u] = w;
		}
		prim(1, n);
		printf("\n");
	}
	return 0;
}

实验结果:
算法导论实验——贪心算法求解最小生成树问题_第1张图片
C++实现prim算法源代码:

#include 
#include 
#include 
using namespace std;

#define MAXINT   1000              //定义了最大的权值

//图中的边的类
class Edge
{
public:
	int head, tail;
	double cost;
	Edge(int h, int t, double c) :head(h), tail(t), cost(c){}
};

void prim(int ** matrix, int n, vector  & mstree)
{
	double * lowcost = new double[n];
	int   * nearver = new int[n];
	//以0号节点作为树的根节点进行适当的初始化
	nearver[0] = -1;             //nearver中值为1的表示这个节点已经加到了生成数中了
	lowcost[0] = MAXINT;                 //将0的权值设置为最大值
	for (int i = 1; i < n; i++)
	{
		lowcost[i] = matrix[0][i];         //由于生成树中只有一个节点,因此每一个节点和生成树中最短的距离都是到0节点的距离
		nearver[i] = 0;                          //对应的节点是0
	}

	//进行n-1次循环,每一次向生成树的节点集合中添加一个节点,向生成树中添加一条边
	for (int i = 1; i < n; i++)
	{
		//首先从lowcost中找出最小的值,它对应的节点就是下一个需要加到生成树节点集合中的节点。
		double mincost = MAXINT;
		int   node = 0;
		for (int j = 1; j < n; j++)
		if (nearver[j] != -1 && lowcost[j] < mincost)
		{
			mincost = lowcost[j];
			node = j;
		}

		//上边的循环已经找出了距离生成树最近的节点了,下边对相应的lowcost和nearver进行设置
		Edge  tem(nearver[node], node, mincost);
		mstree.push_back(tem);
		//cout << tem.cost << "    " << tem.head << "   " << tem.tail << endl;
		lowcost[node] = MAXINT;
		nearver[node] = -1;

		//现在已经将新的node加到了生成树的节点的集合中了,此时需要对lowcost数组进行调整,因为
		//有可能新添加的节点到不再树中的某个的节点的距离表现在的更短
		for (int j = 1; j < n; j++)
		if (matrix[node][j] < lowcost[j] && nearver[j] != -1)
		{
			lowcost[j] = matrix[node][j];
			nearver[j] = node;
		}
	}
}
void test()
{
	typedef int * INT;
	INT * matrix = new INT[6];
	for (int i = 0; i< 6; i++)
		matrix[i] = new int[6];
	for (int i = 0; i < 6; i++)
	for (int j = 0; j < 6; j++)
		matrix[i][j] = MAXINT;
	matrix[0][3] = 5;
	matrix[0][1] = 6;
	matrix[0][2] = 1;
	matrix[1][2] = 5;
	matrix[2][3] = 5;
	matrix[1][4] = 3;
	matrix[2][4] = 6;
	matrix[2][5] = 4;
	matrix[3][5] = 2;
	matrix[4][5] = 6;
	matrix[3][0] = 5;
	matrix[1][0] = 6;
	matrix[2][0] = 1;
	matrix[2][1] = 5;
	matrix[3][2] = 5;
	matrix[4][1] = 3;
	matrix[4][2] = 6;
	matrix[5][2] = 4;
	matrix[5][3] = 2;
	matrix[5][4] = 6;

	vector edges;
	prim(matrix, 6, edges);
	cout << edges.size() << endl;
	double cost = 0;
	for (int i = 0; i < edges.size(); i++)
	{
		cost += edges[i].cost;
		cout << edges[i].head << "   " << edges[i].tail << "  " << edges[i].cost << endl;
	}
	cout << cost << endl;
}

int main()
{
	test();
	return 0;
}

算法导论实验——贪心算法求解最小生成树问题_第2张图片

c实现kruskal算法源代码:

#include 
#include 
#define MAX_VEX_NUM 50
#define MAX_ARC_NUM 100
#define UN_REACH 1000

typedef char VertexType;
typedef enum {
	DG, UDG
} GraphType;
typedef struct {
	VertexType vexs[MAX_VEX_NUM];
	int arcs[MAX_VEX_NUM][MAX_VEX_NUM];
	int vexnum, arcnum;
	GraphType type;
} MGraph;

int getIndexOfVexs(char vex, MGraph *MG) {
	int i;
	for (i = 1; i <= MG->vexnum; i++) {
		if (MG->vexs[i] == vex) {
			return i;
		}
	}
	return 0;
}

void create_MG(MGraph *MG) {
	int i, j, k, weight;
	int v1, v2, type;
	char c1, c2;
	type = 0;
	if (type == 0)
		MG->type = DG;
	else if (type == 1)
		MG->type = UDG;
	else {
		printf("Please input correct graph type DG(0) or UDG(1)!");
		return;
	}

	printf("请输入顶点个数: ");
	scanf("%d", &MG->vexnum);
	printf("请输入边的个数: ");
	scanf("%d", &MG->arcnum);
	getchar();
	for (i = 1; i <= MG->vexnum; i++) {
		printf("请输入第%d个顶点为:", i);
		scanf("%c", &MG->vexs[i]);
		getchar();
	}

	//初始化邻接矩阵
	for (i = 1; i <= MG->vexnum; i++) {
		for (j = 1; j <= MG->vexnum; j++) {
			if (i == j)
				MG->arcs[i][j] = 0;
			else
				MG->arcs[i][j] = UN_REACH;
		}
	}

	//输入边的信息,建立邻接矩阵
	for (k = 1; k <= MG->arcnum; k++) {
		printf("请输入一条边的两个顶点以及边的距离 ", k);

		scanf("%c %c %d", &c1, &c2, &weight);
		v1 = getIndexOfVexs(c1, MG);
		v2 = getIndexOfVexs(c2, MG);
		if (MG->type == 1)
			MG->arcs[v1][v2] = MG->arcs[v2][v1] = weight;
		else
			MG->arcs[v1][v2] = weight;
		getchar();
	}




}
void print_MG(MGraph MG) {
	int i, j;
	/*if (MG.type == DG){
		printf("Graph type: Direct graph\n");
	}
	else{
		printf("Graph type: Undirect graph\n");
	}*/

	printf("图的顶点个数为: %d\n", MG.vexnum);
	printf("图的边数为: %d\n", MG.arcnum);

	printf("顶点设置为:\n ");
	for (i = 1; i <= MG.vexnum; i++)
		printf("%c\t", MG.vexs[i]);
	printf("\n邻接矩阵为:\n");

	for (i = 1; i <= MG.vexnum; i++) {
		j = 1;
		for (; j < MG.vexnum; j++) {
			printf("%d\t", MG.arcs[i][j]);
		}
		printf("%d\n", MG.arcs[i][j]);
	}
}


// 定义边结构体
typedef struct{
	int start;
	int end;
	int cost;
}Edge;

void init_edge(MGraph MG, Edge edge[]){
	int i, j;
	int count = 0;
	if (MG.type == 0){
		for (i = 1; i <= MG.vexnum; i++){
			for (j = 1; j <= MG.vexnum; j++){
				if (MG.arcs[i][j] != 0 && MG.arcs[i][j] != UN_REACH){
					edge[count].start = i;
					edge[count].end = j;
					edge[count].cost = MG.arcs[i][j];
					count++;
				}
			}
		}
	}
	else{
		for (i = 1; i <= MG.vexnum; i++){
			for (j = i; j <= MG.vexnum; j++){
				if (MG.arcs[i][j] != 0 && MG.arcs[i][j] != UN_REACH){
					edge[count].start = i;
					edge[count].end = j;
					edge[count].cost = MG.arcs[i][j];
					count++;
				}
			}
		}
	}

}


void sort_edge(Edge edge[], int arcnum){
	int i, j;
	Edge temp;
	for (i = 0; i < arcnum - 1; i++){
		for (j = i + 1; j < arcnum; j++){
			if (edge[i].cost > edge[j].cost){
				temp = edge[i];
				edge[i] = edge[j];
				edge[j] = temp;
			}
		}
	}
}
void print_edge(Edge edge[], int arcnum){
	int i = 0;
	while (i < arcnum){
		printf("%d,%d,%d\n", edge[i].start, edge[i].end, edge[i].cost);
		i++;
	}
}
int findFather(int father[], int v){
	int t = v;
	while (father[t] != -1)
		t = father[t];
	return t;
}

void Kruskal_MG(MGraph MG, Edge edge[]){
	int father[MAX_VEX_NUM];
	int i, count, vf1, vf2;
	// 初始化father数组
	for (i = 0; i < MAX_VEX_NUM; i++){
		father[i] = -1;
	}
	i = 0;
	count = 0; // 统计加入最小生树中的边数
	// 遍历任意两个结点之间的边
	while (i < MG.arcnum && count < MG.arcnum){
		vf1 = findFather(father, edge[i].start);
		vf2 = findFather(father, edge[i].end);
		// 如果这两个节点不属于同一个连通分量,则加入同一个连通分量
		if (vf1 != vf2){
			father[vf2] = vf1;
			count++;
			printf("%c,%c,%d\n", MG.vexs[edge[i].start], MG.vexs[edge[i].end], edge[i].cost);
		}
		i++;
	}
}
int main(void) {
	MGraph MG;
	Edge edge[MAX_ARC_NUM];
	create_MG(&MG);
	print_MG(MG);
	init_edge(MG, edge);
	sort_edge(edge, MG.arcnum);
	printf("最小生成树的结果为:\n");
	Kruskal_MG(MG, edge);



	return 0;
}

实验结果
算法导论实验——贪心算法求解最小生成树问题_第3张图片

C++实现Kruskal算法源代码:

#include
#include
using namespace std;

struct EdgeNode
{
	int v1; int v2; int value;
	bool operator<(const EdgeNode &a) const
	{
		return a.value pq;
int Find(int x)
{
	int i = x;
	while (i != root[i])
		i = root[i];
	while (i != root[x])
	{
		x = root[x];
		root[x] = i;
	}
	return i;
}
void Union(int a, int b)
{
	a = Find(a);
	b = Find(b);
	if (a != b)
		root[a] = b;
}
void Kruskal()
{
	EdgeNode b;
	cout << "加入最小生成树中的边依次为 : " << endl;
	while (!pq.empty())
	{
		b = pq.top();
		pq.pop();
		if (Find(b.v1) != Find(b.v2))
		{
			cout << b.v1 << "----" << b.v2 << endl;
			Union(b.v1, b.v2);
		}
	}
}
void main()
{
	int n = 0; int m = 0;
	cout << "请输入图中点的个数 : " << endl; cin >> n;
	root = new int[n + 1];
	for (int i = 1; i <= n; i++) root[i] = i;
	cout << "请输入图中边的条数 : " << endl;
	cin >> m;
	EdgeNode a;
	cout << "请依次输入每条边的两个顶点及其权重 : " << endl;
	while (m--)
	{
		cin >> a.v1 >> a.v2 >> a.value;
		pq.push(a);
	}
	Kruskal();
}

实验结果:
算法导论实验——贪心算法求解最小生成树问题_第4张图片

比较prim算法和kruskal算法的时间复杂度和各自的特点:
Prim算法这里是用邻接矩阵来保存图的,适合于稠密图,时间复杂度是O(n^2),观察代码很容易发现,时间主要用于每次遍历所有点寻找最小距离的顶点,在这点上,可以使用堆来优化,使得每次可以在log级别的时间内找到距离最小的点,复杂度为O(e+vlogv)。
Kruskal算法对所有的边进行排序,复杂度为O(eloge),适合于稀疏图。我认为无论在算法的复杂度上还是编程的复杂度上都比较偏向于prim,对于n比较大的矩阵,prim算法时间复杂度更小,对于比较小的矩阵我觉得从时间复杂度上讲两者的区别不是很大,所以总体来说使用prim比kruskal算法是更优的选择。

你可能感兴趣的:(算法导论)