【图算法】最小生成树之普里姆算法

最小生成树是用最少的边连接所有顶点的图,普里姆算法适用于稠密图,从一个顶点开始,通过优先级队列选择最小权值边逐步构建树,直到所有顶点加入。Java实现中使用数组模拟优先级队列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  所谓最小生成树,就是用最少的边连接所有的顶点。对于给定的一组顶点,可能又很多种最小生成树,但是最小生成树边E的数量总是比顶点V的数量小1,即E=V-1。

普里姆算法

  假设N=(V,{E})是连通网,TE是N上最小生成树中边的集合,算法从 U = u 0 ( u 0 ∈ V ) U={u_0}(u_0∈V) U=u0(u0V),TE={}开始。重复执行下述操作:在所有u∈U,v∈V-U的边(u,v)∈E中找到一条代价最小的边( u 0 , v 0 u_0,v_0 u0,v0)并入集合TE,同时 v 0 v_0 v0并入U,直至U = V为止。此时TE中必有n-1条边,则T =(V,{TE})为N的最小生成树。此算法更适用于稠密图。

算法要点如下:

所谓连通图可以为有权图(网)也可以为无权图(把权值均看成1)。

建议用优先级队列来实现这个反复选择最小的路径,而不是链表或数组,这是解决最小生成树的有效方式。在正式的程序中,优先级队列可能基于堆来实现(堆其实是个很简单的数据结构),这会加快在较大的优先级队列中的操作。但是在本例中,我们使用数组实现优先级队列,仅仅为了说明算法。

任意从一个顶点(一般取第一个顶点)开始,把它放入树的集合中,然后重复做下面的事情:

  1. 找到从最新的顶点到其他顶点的所有边,这些顶点不能在树的集合中,把这些边放入优先级队列中。
  2. 找出权值最小的边,把它和它所达到的顶点放入树的集合中。

重复这些步骤,直到所有的顶点都在树的集合中,这时工作完成。

Java代码实现

边界路径类,主要记录了边的始末顶点,以及边的权值

class Edge{
	public int srcVert;//边起始顶点的索引
	public int destVert;//边终止顶点的索引
	public int weight;//权重
	
	public Edge(int sv, int dv, int w) {
		srcVert = sv;
		destVert = dv;
		weight = w;
	}
}

优先级队列

class PriorityQueue{
	private final int SIZE = 20;
	public Edge[] queArray;
	private int size;
	
	public PriorityQueue() {
		queArray = new Edge[SIZE];
		size = 0;
	}
	
	//有序的插入边
	public void insert(Edge edge) {
		int i;
		//找到插入的位置
		for(i = 0; i < size; i ++)
			if(edge.weight >= queArray[i].weight )
				break;
		
		//插入位置后面的元素往后移一位,即权重比插入边小往后移一位
		for(int j = size - 1; j >= i; j -- )
			queArray[j + 1] = queArray[j];
		
		//将edge插入腾出的位置
		queArray[i] = edge;
		size ++;
	}
	
	//删除权重最小的边
	public Edge removeMin() {
		return queArray[--size];
	}
	
	//删除n位置的边
	public void removeN(int n) {
		for(int i = n; i < size - 1; i ++)
			queArray[i] = queArray[i + 1];
		size --;
	}
	
	//返回权重最小的边
	public Edge peekMin() {
		return queArray[size - 1];
	}
	
	//返回n位置的边
	public Edge peekN(int n) {
		return queArray[n];
	}
	
	public int size() {
		return size;
	}
	
	public boolean isEmpty() {
		return size == 0;
	}
	
	//寻找特定的目的顶点的索引
	public int find(int index) {
		for(int i = 0; i < size; i ++)
			if(queArray[i].destVert == index)
				return i;
		
		return -1;
	}
	public void display() {
		if(size == 0)
			return;
		System.out.println("size:" + size);
		for(int  i = 0; i < size; i ++)
			System.out.print(queArray[i].srcVert + " " + queArray[i].destVert  + " -> ");
		System.out.println();
	}
}

有权图的最小生成树算法——普里姆算法

public class WeightGraph {
	/**
	 * 存储结构为邻接矩阵
	 * @author Administrator
	 *
	 */
	
	class Vertex{//顶点类
		public String  data;
		public boolean isVisted;
		public boolean isInTree;
		
		public Vertex(String data) {
			this.data = data;
			isVisted = false;
			isInTree = false;
		}
	}
		
		public final  int MAX_VERTEX = 25;//顶点个数最大值
		public final  int MAX_VALUE = 65535;
		public Vertex[] vertexArray;//顶点表
		public int[][] edgeArray;//邻接矩阵
		public int verNum;//顶点数量
		public int curIndex;//当前顶点索引
		public PriorityQueue pQueue;//存储边的优先级队列
		public int nTree;//生成树中的顶点数量
		
		//初始化图
		public WeightGraph() {
			vertexArray = new Vertex[MAX_VERTEX];
			edgeArray = new int[MAX_VERTEX][MAX_VERTEX];
			verNum = 0;
			
			for(int i = 0; i < MAX_VERTEX; i ++) 
				for(int j = 0; j < MAX_VERTEX; j ++) 
					edgeArray[i][j] = MAX_VALUE;
			
			pQueue = new PriorityQueue();
				
		}
		
		//增加顶点
		public void addVertex(String data) {
			vertexArray[verNum ++] = new Vertex(data);
		}
		
		//增加边
		public void addEdge(int start , int end, int weight) {
			edgeArray[start - 1][end - 1] = weight;
			edgeArray[end - 1][start - 1] = weight;
		}
		
		
			/**
		 * 带权图的最小生成树之普里姆算法
		 * @return 
		 */
		public int minSpanninTreePlim() {
			int result = 0;
			curIndex = 0;
			//当所有顶点未都加入生成树时
			while(nTree < verNum) {
				vertexArray[curIndex].isInTree = true;
				nTree ++;
				//在pQueue中加入与当前顶点相连接的边
				for(int i = 0; i < verNum; i ++) {
					//当前顶点
					if(i == curIndex) 
						continue;
					//i顶点已加入生成树
					if(vertexArray[i].isInTree)
						continue;
					//计算当前顶点到顶点i的权重
					int weight = edgeArray[curIndex][i];
					if(weight == MAX_VALUE)
						continue;
					//将i顶点加入pQueue中
					putInPQ(i, weight);
				}
				
				if(pQueue.size() != 0) {
					//取出最小边
					Edge edge = pQueue.removeMin();
					int startVert = edge.srcVert;
					curIndex = edge.destVert;
					System.out.print(String.format("(%s,%s)",vertexArray[startVert].data, vertexArray[curIndex].data));
					if(pQueue.size() != 0)
						System.out.print("——>");
					result += edge.weight;
				}			
			}
			System.out.println();
			return result;
		}
		
		private void putInPQ(int newVert, int newWeight) {
			//判断优先级队列中是否已经存在newVert的边
			int queueIndex = pQueue.find(newVert);
			
			//如果有,则与当前顶点到目的顶点的边权重比较,保留小的那个
			if(queueIndex != -1) {
				Edge edge = pQueue.peekN(queueIndex);
				int oldWeight = edge.weight;
				if(oldWeight > newWeight) {
					//更新优先级队列的边
					pQueue.removeN(queueIndex);
					Edge newEdge = new Edge(curIndex, newVert, newWeight);
					pQueue.insert(newEdge);
					
					//System.out.print(vertexArray[newEdge.srcVert].data + ", " + vertexArray[newEdge.destVert].data + " ");
				}
			}else {
				Edge newEdge = new Edge(curIndex, newVert, newWeight);
				pQueue.insert(newEdge);
				
				//System.out.print(vertexArray[newEdge.srcVert].data + ", " + vertexArray[newEdge.destVert].data + " ");
			}
			
		}

测试代码

public static void main(String[] args) {
			WeightGraph graph = new WeightGraph();

			graph.addVertex("a");
			graph.addVertex("b");
			graph.addVertex("c");
			graph.addVertex("d");
			graph.addVertex("e");
			/**
			 *                   a
			 *                  / \
			 *                 /   \
			 *                b ——— d
			 *				 /       \
			 *              e         c
			 */              
			graph.addEdge(1, 2, 2);
			graph.addEdge(1, 4, 4);
			graph.addEdge(2, 4, 4);
			graph.addEdge(2, 5, 5);
			graph.addEdge(3, 4, 3);
			
			//System.out.println("最小生成树(克鲁斯卡尔算法)的权重之和:" + graph.minSpanninTreeKruskal());
			System.out.println("最小生成树(普利姆算法)的权重之和:" + graph.minSpanninTreePlim());
			
		}

参考文章:图也有权重,你们知道吗?(原创作者: 倪升武 程序员私房菜)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值