图(带权无向图)最小生成树
带权图的邻接矩阵中无连接的值为无限大
最小生成树的算法:从一个顶点出发找到其他顶点的所有的边,放入优先列队,找到权值最小的,把它和它所到达的顶点放入树的集合中。再以终点作为源点找到所有到其他顶点的边(不包括已放入树中的顶点),放入优先队列中,再从中取最小的把它
到达的顶点放入树的集合中(最小生成树)。再以终点作为源点找到所有到其他顶点的边(不包括已放入树中的顶点),放到优先队列中,再从中取最小的把它和它所到达的顶点放入树的集合中,反复这样操作到全部顶点都放入到树中为止。
除了图之外,我们还需要优先队列
注意是无向有权图的最小生成树
优先队列存放的是边这个对象。
最小生成树与最短路径是不同的东西
最小生成树返回的是一个顶点序列
带权图结构:边,顶点,优先队列,图
步骤:
初始化当前顶点索引为0
{标志当前顶点正在访问
寻找边插入队列(需要判断是否存在相同终点的边,有的话需要删除和替换)
获取队列中最小的边
输出
改变全局变量当前顶点的值
}循环
public class Edge {//边 public int srcVert;//存放源点的索引值 public int destVert;//存放终点的索引值 public int distance;//边的权值 public Edge(int sv,int dv,int d) {//初始化边 srcVert=sv; destVert=dv; distance=d; } }
public class PriorityQ {//优先队列(优先队列中存放的是边对象) private final int SIZE=20;//队列的总长度(规定边最大的数量) private Edge[] queArray;//存放边对象的数组 private int size;//当前边的数量 public PriorityQ() { queArray=new Edge[SIZE]; //初始化数组 size=0;//初始化边数量为0 } //将边插入边数组中 public void insert(Edge item) {//根据权值将边插入队列中 int j; for(j=0;j<size;j++)//循环数组,找到插入边的位置 if(item.distance>=queArray[j].distance)//如果找到要比姚插入的边权值大的边,就插入该边的后面 break;//记录边的位置,即j //插入(移动和插入) for(int k=size-1;k>=j;k--)//位置后移动 queArray[k+1]=queArray[k]; queArray[j]=item;//插入item边 size++;//数据项增一 } //删除值最小的边 public Edge removeMin() { //因为是优先队列,插入的时候最小值都在索引最大的地方,所以直接拿数组最后一个 return queArray[--size];//最后一个数的索引是size-1 } //删除指定的边(n为queArray数组的索引值) public void removeN(int n) { for(int j=n;j<size-1;j++)//从数组中删除一个数(移动) queArray[j]=queArray[j+1]; size--; } public Edge peekMin() { return queArray[size-1]; //查看最小的边(对应权值最小) } public int size() { return size; //当前边的数量 } //判断是否为空 public boolean isEmpty() { return size==0; } //查看特定的边(n为queArray数组的索引) public Edge peekN(int n) { return queArray[n]; } //寻找特定终点的边(findDex为边对象的destVert属性值) public int find(int findDex) { for(int j=0;j<size;j++) if(queArray[j].destVert==findDex) return j;//找到了就返回边位置j return -1;//没找到就返回-1 } }
//图的顶点 public class Vertex { public char label;//顶点的标识符 public boolean isVisited;//顶点有无被访问的标志 public Vertex(char lab) {//初始化顶点(属性) label=lab; isVisited=false; } }
public class Graph { private final int MAX_VERTS=20;//最大顶点数 private final int INFINITY=1000;//无限大的值,用于表示不连通的权值 private Vertex[] vertexList;//顶点数组 private int [][]adjMat;//顶点关系的领接矩阵(邻接矩阵的每行或者每列的位置跟顶点数组是对应的) private int nVerts;//当前顶点个数 private int currentnVert;//标志当前顶点,该值为当前顶点索引值 private PriorityQ thePQ;//优先列队 private int nTree;//最小生成树算法过程中,标志已访问的顶点数量(总共需要访问的个数是顶点的总数) public Graph() {//初始化图 vertexList=new Vertex[MAX_VERTS]; //初始化顶点数组 adjMat=new int [MAX_VERTS][MAX_VERTS] ;//初始化邻接矩阵 for(int j=0;j<MAX_VERTS;j++) for(int i=0;i<MAX_VERTS;i++) adjMat[i][j]=INFINITY; nVerts=0;//初始化当前顶点个数 thePQ=new PriorityQ();//建立列队对象 } //向顶点数组中添加顶点对象(lab为顶点对象的label属性值) public void addVertex(char lab) { vertexList[nVerts++]=new Vertex(lab);//建立lab对象,往数组内添加 } //添加边(向邻接矩阵中改变权值) public void addEdge(int start,int end,int weight) { //因为是无向图所以(i,j)(j,i)都要添加1 adjMat[start][end]=weight; adjMat[end][start]=weight; } //打印顶点数组,根据获取的顶点数组的下标值,打印顶点 public void displayVertex(int v) { System.out.print(vertexList[v].label); } //最小生成树(输出顶点序列) public void mstw() { currentnVert=0;//当前顶点是索引为0的顶点 while(nTree<nVerts-1) { //遍历顶点数组,要遍历nVerts-1次.当nTree为0的时候遍历第一次,所以当遍历nVerts-1次的时候,nTree为nVerts-2 vertexList[currentnVert].isVisited=true;//访问当前顶点 nTree++;//树中值加1(当前访问该顶点) for(int j=0;j<nVerts;j++) {//找出当前顶点的边(遍历数组,是否已经访问过,是否是连通的,是否是自己) if(j==currentnVert) continue;//如果当前比较的是自己,退出此次操作,从j+1开始 if(vertexList[j].isVisited) continue;//如果比较的是已经访问过的,即已经加入最小生成树的序列中,退出此次操作,从j+1开始 int distance=adjMat[currentnVert][j];//在邻接矩阵中取当前顶点到邻接点的边值 if(distance==INFINITY) continue;//无连接 //如果前面都通过了,说明顶点对象数组中索引为j的是邻接点 //放入优先队列中(边) 该边的源点是一个全局变量currentnVert,不断在发生变化 //插入有两步,第一需要判断队列里面是否有以j为终点的边,有的话需要比较删除。第二如果没有的话,就直接插入 putInPQ(j,distance);//边值和该点(源点去哪了) } if(this.thePQ.size()==0) {//如果优先队列一个值也没有,说明没有边,无连接 System.out.println("图中无连接"); return ; } Edge theEdge=thePQ.removeMin();//如果都成功了,移除队列中最小的边(就是我们要找的) int sourceVert=theEdge.srcVert;//最小边的起点 currentnVert=theEdge.destVert;//最小边的终点--下一个循环开始的源点 //打印该边的起点和终点 System.out.print(vertexList[sourceVert].label); System.out.print(vertexList[currentnVert].label+" "); } for(int j=0;j<nVerts;j++) vertexList[j].isVisited=false; } //边是newDist,终点是newVert放入队列中 public void putInPQ(int newVert,int newDist) { int queueIndex=thePQ.find(newVert);//查找终点是newVert的边 if(queueIndex!=-1) {//如果找到了(就是说如果列队中有虽然起点不同,但是终点相同的值,就需要小的替换大的边) Edge tempEdge=thePQ.peekN(queueIndex);//根据边索引查询边 int oldDist=tempEdge.distance; if(oldDist>newDist) {//如果新的边权值小就需要删除旧的边 thePQ.removeN(queueIndex);//删除旧边 Edge theEdge=new Edge(currentnVert,newVert,newDist);//建立新边(新边的起始点是一个全局变量,从一开始就设定了) thePQ.insert(theEdge);//插入新边,不能直接替换旧边,因为还要根据优先队列,比较权值 } }else {//如果没有找到 Edge theEdge=new Edge(currentnVert,newVert,newDist); thePQ.insert(theEdge); } } }
public class Test { public static void main(String[] agrs) { Graph theGraph=new Graph();//创建一个图 theGraph.addVertex('A');//添加顶点 theGraph.addVertex('B');//添加顶点 theGraph.addVertex('C');//添加顶点 theGraph.addVertex('D');//添加顶点 theGraph.addVertex('E');//添加顶点 theGraph.addVertex('F');//添加顶点 theGraph.addEdge(0, 1,6);//添加边 theGraph.addEdge(0, 3,4);//添加边 theGraph.addEdge(1,2,10);//添加边 theGraph.addEdge(1,3,7);//添加边 theGraph.addEdge(1,4,7);//添加边 theGraph.addEdge(2,3,8);//添加边 theGraph.addEdge(2,4,5);//添加边 theGraph.addEdge(2,5,6);//添加边 theGraph.addEdge(3,4,12);//添加边 theGraph.addEdge(4,5,7);//添加边 theGraph.mstw(); } }