山东大学数据结构与算法实验13最小生成树(Prim算法/Kruskal算法)

A : Prim算法

题目描述

使用 prim 算法实现最小生成树

输入输出格式

输入

第一行两个整数 n,e。n (1≤n≤200000) 代表图中点的个数,e (0≤m≤500000) 代表边的个数。
接下来 e 行,每行代表一条边:

  • i j w 表示顶点 i 和顶点 j 之间有一条权重为 w 的边

输出

最小生成树所有边的权重和

数据结构与算法描述

构造节点结构体包括权、元素大小、下一个节点。

struct vertexnode {
	int weight;
	int element;
	vertexnode* next;
	vertexnode(){}
	vertexnode(const int& element, const int& weight) {
		this->element = element;
		this->weight = weight;
		this->next = NULL;
	}
	
};

在图类中,插入函数与邻接链表类似,终点节点包含了该边的权重,将该节点插入到邻接链表中。因为是无向图,所以反向再来一遍。

class graph {
private:
	int numvertices;//点的个数 
	int numedges;//边的个数 
	vertexnode* list;//邻接链表 
public:
	graph(int n, int e) {
		numvertices = n;
		numedges = e;
		list = new vertexnode[n + 1];
		for (int i = 1; i <= n; i++) {
			list[i].element = i;
			list[i].next = NULL;
			list[i].weight = 0;
		}
	}
	void insert(int i, int j, int w);
	void prim();
};
void graph::insert(int i, int j, int w) {
	vertexnode* insertnode = new vertexnode(j,w);//创造一个终点的节点 
	vertexnode* nextnode = list[i].next;//与起点相连的点 
	if (nextnode == NULL) {//没有相连的点,直接连 
		list[i].next = insertnode;
	}
	else {
		list[i].next = insertnode; 
		insertnode->next = nextnode;
	}
	vertexnode* insertnode1 = new vertexnode(i, w);//因为是无向图,所以反向再来一遍 
	vertexnode* nextnode1 = list[j].next;
	if (nextnode1 == NULL) { 
		list[j].next = insertnode1;
	}
	else {
		list[j].next = insertnode1; 
		insertnode1->next = nextnode1;
	}
	numedges++;
}

Prim函数,利用最小堆,从1点开始循环,循环次数为定点数减一,将与这个点的邻接点且没有到达过的压入最小堆,将已到达的点从堆中删除,权重和更新,在堆中删除该被加入的节点。最后循环结束输出权重和。

void graph::prim() {
	minHeap mh(numvertices);
	reach[1] = 1;
	vertexnode* nextnode = list[1].next;//第一个点相连的点
	long long sumw = 0;//权重和
	for (int i = 2; i <= numvertices; i++) {//只要numvertices-1次
		while (nextnode != NULL) {//将所有的邻接点压入最小堆
			if (reach[nextnode->element] != 1) {
				vertexnode node(nextnode->element, nextnode->weight);
				mh.push(node);
			}
			nextnode = nextnode->next;
		}
		while (reach[mh.top().element] == 1) {//已经到达的点删除
			mh.pop();
		}
		sumw += mh.top().weight;
		reach[mh.top().element] = 1;
		nextnode = list[mh.top().element].next;//要把与该点相连的点压入堆
		mh.pop();
	}
	cout << sumw << endl;
}

完整代码(含注释)

#include<iostream>
using namespace std;
struct vertexnode {
	int weight;
	int element;
	vertexnode* next;
	vertexnode(){}
	vertexnode(const int& element, const int& weight) {
		this->element = element;
		this->weight = weight;
		this->next = NULL;
	}
	
};
class minHeap {
private:
	vertexnode* heap;//元素数组
	int heapSize; //堆中的元素个数
	int arrayLength;
public:
	minHeap(int n) {
		heapSize = 0;
		heap = new vertexnode[n + 1];
		arrayLength = n + 1;
	}
	~minHeap() { delete[] heap; }
	bool empty() { return heapSize == 0; }//判断heapSize是否为0.
	int Size() { return heapSize; }//返回heapSize的值
	void push(vertexnode x);
	vertexnode& top() { return heap[1]; }//输出根 
	void pop();
};
void minHeap::push(vertexnode x) {//插入 
	if (heapSize == arrayLength - 1) {//没有足够的空间 
		arrayLength *= 2;
		vertexnode* newheap = new vertexnode[arrayLength];//数组长度加倍 
		for (int i = 1; i <= heapSize; i++) {
			newheap[i] = heap[i];
		}
		delete[] heap;
		heap = newheap;
	}
	int currentNode = ++heapSize;//从新的叶节点开始 
	while (currentNode != 1 && x.weight < heap[currentNode / 2].weight) {//当currentNode没到根节点,并且父节点比插入的大时,继续上升 
		heap[currentNode] = heap[currentNode / 2];//将该元素的父节点下移 
		currentNode /= 2;//移向父节点 
	}
	heap[currentNode] = x;
}
void minHeap::pop() {//删除 
	vertexnode lastElement = heap[heapSize--];//最后一个元素 
	int currentNode = 1;//从当前被删掉的最小元素的位置开始 
	int child = 2;//currentNode的孩子 
	while (child <= heapSize) {
		if (child<heapSize && heap[child].weight>heap[child + 1].weight)child++;//找到左右孩子中更小的 
		if (lastElement.weight <= heap[child].weight)break;//当孩子比要插入的大,则可以插入了,跳出循环 
		heap[currentNode] = heap[child];//将较小的孩子上移 
		currentNode = child;//currentNode向下移 
		child *= 2;
	}
	heap[currentNode] = lastElement;
}
int reach[2000000] = { 0 };
class graph {
private:
	int numvertices;//点的个数 
	int numedges;//边的个数 
	vertexnode* list;//邻接链表 
public:
	graph(int n, int e) {
		numvertices = n;
		numedges = e;
		list = new vertexnode[n + 1];
		for (int i = 1; i <= n; i++) {
			list[i].element = i;
			list[i].next = NULL;
			list[i].weight = 0;
		}
	}
	void insert(int i, int j, int w);
	void prim();
};
void graph::insert(int i, int j, int w) {
	vertexnode* insertnode = new vertexnode(j,w);//创造一个终点的节点 
	vertexnode* nextnode = list[i].next;//与起点相连的点 
	if (nextnode == NULL) {//没有相连的点,直接连 
		list[i].next = insertnode;
	}
	else {
		list[i].next = insertnode; 
		insertnode->next = nextnode;
	}
	vertexnode* insertnode1 = new vertexnode(i, w);//因为是无向图,所以反向再来一遍 
	vertexnode* nextnode1 = list[j].next;
	if (nextnode1 == NULL) { 
		list[j].next = insertnode1;
	}
	else {
		list[j].next = insertnode1; 
		insertnode1->next = nextnode1;
	}
	numedges++;
}
void graph::prim() {
	minHeap mh(numvertices);
	reach[1] = 1;
	vertexnode* nextnode = list[1].next;//第一个点相连的点
	long long sumw = 0;//权重和
	for (int i = 2; i <= numvertices; i++) {//只要numvertices-1次
		while (nextnode != NULL) {//将所有的邻接点压入最小堆
			if (reach[nextnode->element] != 1) {
				vertexnode node(nextnode->element, nextnode->weight);
				mh.push(node);
			}
			nextnode = nextnode->next;
		}
		while (reach[mh.top().element] == 1) {//已经到达的点删除
			mh.pop();
		}
		sumw += mh.top().weight;
		reach[mh.top().element] = 1;
		nextnode = list[mh.top().element].next;//要把与该点相连的点压入堆
		mh.pop();
	}
	cout << sumw << endl;
}
int main() {
	int n, e;
	cin >> n >> e;
	graph g(n, e);
	for (int i = 0; i < e; i++) {
		int y, j, w;
		cin >> y >> j >> w;
		g.insert(y, j, w);
	}
	g.prim();
	return 0;
}

B : Kruskal算法

题目描述

使用 kruskal 算法实现最小生成树

输入输出格式

输入

第一行两个整数 n,e。n (1≤n≤200000) 代表图中点的个数,e (0≤m≤500000) 代表边的个数。
接下来 e 行,每行代表一条边:

  • i j w 表示顶点 i 和顶点 j 之间有一条权重为 w 的边

输出

最小生成树所有边的权重和

数据结构与算法描述

将输入的边的两个顶点和权重插入结构体数组中,将结构体数组压入最小堆,然后进行一个循环,当边数为零或者所加边数等于定点数减一。在循环中,取出堆顶的边,利用并查集,判断该边是否在同一个集合,如果不在则合并,如果在则不能加,避免成环。

	for (int i = 1; i <= e; i++) {//结构体数组 
		cin >> edge[i].vertex1 >> edge[i].vertex2 >> edge[i].weight;
		int t = 0;
	}
	int k = 0;
	long long sumw=0;
	minHeap heap(1);//最小堆 
	heap.initialize(edge, e);
	while (e > 0 && k < n - 1) {
		edgenode x = heap.top();
		heap.pop();
		e--;
		int a = find(x.vertex1);//找该边两个顶点的根 
		int b = find(x.vertex2);
		if (a != b) {//如果不在同一个集合,进行合并 
			unite(a, b);
			k++;
			sumw += x.weight;
		}
	}
	cout << sumw << endl;

并查集find函数:第一个while循环找根,第二个while循环,将所有的点都连到根上,降低高度,节省找根的时间。Unite函数:将两个数合并,两个跟一个为另一个的父节点。

int find(int theelement) {
	int root = theelement;
	while (parent[root] != 0) {//找根 
		root = parent[root];
	}
	while (theelement != root) {//将所有的点都连到根上,降低高度 
		int t = parent[theelement];;
		parent[theelement] = root;
		theelement = t;
	}
	return root;
}
void unite(int a, int b) {//两个树合并 
	parent[b] = a;
}

完整代码(含注释)

#include<iostream>
using namespace std;
struct edgenode {
	long long weight;
	int vertex1;
	int vertex2;
}edge[5000000];
class minHeap {
private:
	edgenode* heap;//元素数组
	int heapSize; //堆中的元素个数
public:
	minHeap(int n) {
		heapSize = 0;
		heap = new edgenode[n + 1];
	}
	~minHeap() { delete[] heap; }
	bool empty() { return heapSize == 0; }//判断heapSize是否为0.
	int Size() { return heapSize; }//返回heapSize的值
	edgenode top() { return heap[1]; }//输出根 
	void pop();
	void initialize(edgenode* theheap, int theSize);
};
void minHeap::pop() {//删除 
	edgenode lastElement = heap[heapSize--];//最后一个元素 
	int currentNode = 1;//从当前被删掉的最小元素的位置开始 
	int child = 2;//currentNode的孩子 
	while (child <= heapSize) {
		if (child<heapSize && heap[child].weight>heap[child + 1].weight)child++;//找到左右孩子中更小的 
		if (lastElement.weight <= heap[child].weight)break;//当孩子比要插入的大,则可以插入了,跳出循环 
		heap[currentNode] = heap[child];//将较小的孩子上移 
		currentNode = child;//currentNode向下移 
		child *= 2;
	}
	heap[currentNode] = lastElement;
}
void minHeap::initialize(edgenode* theHeap, int theSize) {//初始化
	delete[] heap;
	heap = new edgenode[theSize + 1];
	for (int i = 1; i <= theSize; i++) {
		heap[i] = theHeap[i];
	}
	heapSize = theSize;
	for (int root = heapSize / 2; root >= 1; root--) {//从数组中最右一个有孩子的节点开始
		edgenode rootElement = heap[root];//子树的根 
		int child = 2 * root;//rootElement的孩子位置 
		while (child <= heapSize) {
			if (child<heapSize && heap[child].weight>heap[child + 1].weight)child++;//找到孩子里较小的 
			if (rootElement.weight <= heap[child].weight)break;//如果子树的根小于其孩子,则可以跳出循环 
			//如果孩子较小
			heap[child / 2] = heap[child];//将孩子上移 
			child *= 2;
		}
		heap[child / 2] = rootElement;
	}
}
int parent[2000000] = { 0 };
int find(int theelement) {
	int root = theelement;
	while (parent[root] != 0) {//找根 
		root = parent[root];
	}
	while (theelement != root) {//将所有的点都连到根上,降低高度 
		int t = parent[theelement];;
		parent[theelement] = root;
		theelement = t;
	}
	return root;
}
void unite(int a, int b) {//两个树合并 
	parent[b] = a;
}
int main() {
	int n, e;
	cin >> n >> e;
	for (int i = 1; i <= e; i++) {//结构体数组 
		cin >> edge[i].vertex1 >> edge[i].vertex2 >> edge[i].weight;
		int t = 0;
	}
	int k = 0;
	long long sumw=0;
	minHeap heap(1);//最小堆 
	heap.initialize(edge, e);
	while (e > 0 && k < n - 1) {
		edgenode x = heap.top();
		heap.pop();
		e--;
		int a = find(x.vertex1);//找该边两个顶点的根 
		int b = find(x.vertex2);
		if (a != b) {//如果不在同一个集合,进行合并 
			unite(a, b);
			k++;
			sumw += x.weight;
		}
	}
	cout << sumw << endl;
	return 0;
}

如能打赏,不胜感激[叩谢]。

  • 35
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值