java 图的最小生成树有关问题 (代码自己写)
java 图的最小生成树问题 (代码自己写)
最小生成树是基于无无向图,并且是没有权值的图的。它的实现可以用深度遍历还有广度遍历实现
本代码是基于图的,所以和前面的 java 图 的那个代码是一样的
不同是加入了 生成最小生成图的代码
*惯理论:
/** * 详细代码后面会给出来的,现在只是大概的只是代码怎么写,怎么个思路 package endual; public class ShortTree { //mst public void mst() { while (!(theStack).isEmpty()) { int currentVertex = theStack.peek() ; //get next univisited heighbor //找到下一个邻居的未访问的顶点 int v = getAdUnvisitedVertex(currentVertex) ; if (v == -1) {//如果不存在,那么跳出一个栈里面的作为当前的点 theStack.pop() ; } else { //否则就是要 vertexList[v].isVisited = true ; //已经被访问过了的。然后就是true了 theStack.push(v) ; //压入栈 displayVertex(currentVertex) ; //打印出访问过的点 displayVertex(v) ; System.out.println(" * * "); } } } for(int i=0; i < nVertex; i++) { vertext[j].isVisited() = false ; } } */
最小生成树其实找的是连接顶点的用最少的边,并不用去考虑边的长度,这个不是带权的边,所以是不用考虑的。
需要注意的是
最小生成树的边数E总是比该图的所以顶点之和少一个
E = V -1 ;
记住,不必关心边的长度,并不需要找到一条最短路径,而是要找最少数量的边。
创建最小生成树的算法与搜索的算法几乎是相同的。它同样可以基于广度搜索或者是深度搜索算法来实现
我们用深度搜索算法来实现。
import java.util.LinkedList; import java.util.Queue; import java.util.Stack; import java.util.concurrent.ArrayBlockingQueue; import javax.management.Query; /** * 下面来看下Graph类,包含的是创建连接链表和连接矩阵的方法,以向Graph对象插入顶点和边的方法 * (最小生产树的方法的代码在最后面,代码请看 前面的图那个文章 ,这只是一部分代码) * @author Endual * */ public class Graph { private final int MAX_VERTS = 20 ; private Vertex[] vertexList ; //存放顶点的数组 private int adjMat[][] ; //表示用矩阵的形式来表示边有没有 private int nVerts ; //当前的图中有多少个顶点了 public Graph() { super(); this.vertexList = new Vertex[this.MAX_VERTS]; this.nVerts = 0 ; this.initialAdjMat () ; //初始化矩阵的 } //初始化矩阵的 private void initialAdjMat() { for (int i=0; i < this.MAX_VERTS; i++) { for (int j=0; j < this.MAX_VERTS; j++) { this.adjMat[i][j] = 0 ; //初始数组 } } } ////////////////////////////////////////////////////////////////////////////////////////////// //创建图 = 创建顶点和创建边,两个步骤是分开的 //添加顶点 public void addVertex(char lab) { Vertex ver = new Vertex(lab) ; this.vertexList[this.nVerts] = ver ; this.nVerts++ ; } //创建边(start 和 end 要有范围的 从0 到 N-1) public void addEdge(int start, int end) { this.adjMat[start][end] = 1 ; this.adjMat[end][start] = 1 ; } /////////////////////////////////////////////////////////////////////////////////////////////////// //显示一个顶点 public void displayVertex(int v) { System.out.println(this.vertexList[v].getLabel()) ; } ////////////////////////////////////////////////////////////////////////////////////////////////// /** * 搜索 * 在图中实现的最基本的操作之一就是搜索从一个指定顶点可以到达哪些顶点可以到达哪些顶点。例如,可以想象要找出美国有多少个城市 * 可以从kansan乘坐旅行车到达(当然了中间是不能换车的)。一些城市可以直达,而有些城市因为没有旅行列车服务而不能到达。 * 有些地方即使有列子服务也不能到达,因为他们的铁轨不能和出发或者沿途的标准铁轨系统相连。 * * 还有一种情况就是需要找出所有的当前顶点可以到达的顶点。 * 假设有这么一张图,现在需要一种算法来提供系统的方法,从某个特定顶点开始,沿着边移动到其他顶点,移动完毕后,需要保证访问了喝 * 起始点相连的每一个顶点。 * 有两种常用的方法可以用来搜索图;深度优先搜索和广度优先搜索,他们最终都会到达所有连通的顶点 * * 深度优先搜索通过栈来实现,而广度优先搜索通过队列实现。 * * * 深度优先搜索 * * 在搜索到尽头的时候,深度优先搜索用栈记住下一步的走向,这里展示了一个例子, * 为了实现深度优先搜索,找到一个起始本例的顶点是A * 需要做的三件事情: * 首先是访问该顶点,然后是把该节点放入到栈中去,以便记住它 * 最后标记该节点,这样就不会再访问它了 * 下面就可以访问任何与顶点A相连的顶点了,只要还没有访问过它,假设顶点按字母顺序访问,所以下面访问顶点B。然后标记它,放入栈中。 * 已经访问过B,做相同的事情,找下一个为访问的节点的顶点,也就是F,这个过程就叫做规则1。 * * 规则1: * 如果可能,访问一个邻接的未访问顶点,标记它,并把它放入到栈中。 * * 再次应用规则1,这次访问顶点H。然而,这里还需要做一些事情,因为没有和H邻接的未访问顶点。 * * 规则2. * 当不能执行规则1的时候,如果栈不空,就从栈中探出一个顶点 * * 根据这条规则,从栈中探出H,这样就又回到了顶点F,F也没有和邻接且邻接没有访问的顶点了。那么再弹出F,这回到顶点B。这时 * 只有顶点A在栈中了。 * 然而A还没有访问过的连接点,所以访问下一个顶点C,但是C又是在这一条线路的终点,所以栈中弹出它,再次回到A点。接着访问D,G和I。 * 当到达I时,把它们都弹出栈,现在回到A然后访问E,最后再次回到A * * 规则3 * 如果不能执行规则1 和规则 2,那么就完成了整一个深度搜索的过程了 * 栈的内容就是刚才起始顶点到各个顶点访问的整个过程。从起始顶点出发访问下一个顶点时,就是把这个顶点入栈,回到起始顶点,出栈 * 访问顶点的顺序是ABFHCDGIE * * 深度优先搜索算法要得到距离起始点最远的顶点,然后再不能继续前进的时候返回,使用深度这个属于表示与起始点的距离,便可以理解深度优先 * 搜索的意义。 * * 模拟问题 * * 深度优先搜索与迷宫问题有类似的,迷宫在英国很流行,可以由一方给另一方设置障碍,由另一方想办法通过,迷宫由狭小的过道和过道的交汇点组成 * * * JAVA代码 深度优先搜索的关键在于能够找到与某一顶点邻接且没有访问过的顶点。邻接矩阵是关键,找到指定顶点所在的行。第一列开始向后寻找值为1的列; * 列号是邻接顶点的号码。检验这个顶点是不是被访问过的,如果是这样的,那么就是要访问的下个顶点,如果该行没有顶点既等于1,且又是没有访问过的,那么 * 与指定点相邻接的顶点就全部访问过了。这个过程在下面的代码中实现 * * * */ public int getAdjUnivisitedVertex(int v) { //从哪个顶点开始访问 for(int j=0; j < this.nVerts; j++) { if (this.adjMat[v][j] == 1 && this.vertexList[j].isVisisted == false) { return j ; } } return -1 ;//如果没有访问到,那么返回-1 ; } //考察dfs()方法,这个方法实际执行了深度的优先搜索。包含三条规则。它循环执行,知道栈为空,每次循环中,有四件事情 /** * 1. 用peek()方法检查栈顶的顶点 * 2. 试图找到这个顶点还未访问的邻接点 * 3. 如果找没有找到,出栈 * 4. 如果找到了这样的顶点,访问这个顶点,并且把它压入栈 */ //深度搜索 public void dfs() { Stack theStack = new Stack() ; //所谓遍历是从第一个顶点开始的 也是 vertesList[0]开始的 this.vertexList[0].isVisisted = true ; this.displayVertex(0) ; theStack.push(0) ; while(!theStack.isEmpty()) { int v = this.getAdjUnivisitedVertex((Integer) theStack.peek()) ; if (-1 == v) { //已经被访问过的 theStack.pop() ;//弹出一个 } else { this.vertexList[v].isVisisted = true ; this.displayVertex(v) ; theStack.push(v) ; } } //stack 是空的话,就做 for(int i=0; i < this.nVerts; i++) { this.vertexList[i].isVisisted = false ; //重新初始化节点,为下次的使用 } } //end dfs /** * 广度优先搜索 * * 正如深度优先搜索中看到的,算法表现得好像要尽快地远离起始点的。相反,在广度优先搜索中,算法好像要尽可能地靠近起始点 * 它首先访问的是顶点的所有邻接点,然后再访问较远的区域。这种搜索不能用栈,只能用到队列来实现 * * 一个例子 * * A是起始点,所有访问它,并且标记为当前空的顶点。然后应用下面几条规则 * * 规则1 * 访问下一个未来访问的邻接点(如果存在), 这个顶点必须是当前顶点的邻接点,标记它,并把它插入到队列中 * (当前节点为改变) * 规则2 * 如果因为已经没有未访问顶点而不能执行规则1, 那么从队列头取一个顶点(如果有),并且使得成为当前的顶点。 * (当前节点改变了) * 规则3 * 如果因为队列为空而不能执行规则2.那么广度优先搜索的到此就结束了 * * * A-------B--------C------D * \ | X * \ | | * \-----e------F----G * | Y----H 在每一个时刻队列中包含的顶点是那些本身已经被访问,而它的邻居还未被访问的顶点。(对比深度优先搜索,栈的内容是起始点 到当前顶点经过的所有顶点)顶点访问的顺序。 深度优先遍历和广度优先遍历的异同点 可以认为广度优先搜索就像往水中投入石块,水波纹扩展的过程===对于喜爱流行病学的人来说,就好比流感通过航空旅行客从一个城市 到另一个城市了。首先是相距起始点只有一条边的所有顶点被访问,然后是相距两条边的所有顶点被访问,一次类推 */ //广度搜索与深度类似的,只是用队列代替了栈,嵌套的循环代替了单层循环。外层循环等待队列为空,而内层循环依次寻找当前顶点的未访问邻接点 //下面是代码 public void bfs() { Queue theQueue = new LinkedList(); this.vertexList[0].isVisisted = true ; this.displayVertex(0) ; theQueue.add(0) ; int v2 ; while (!theQueue.isEmpty()) { int v1 = (Integer) theQueue.remove() ; v2 = this.getAdjUnivisitedVertex(v1) ; while (v2 != -1) { this.vertexList[v2].isVisisted = true ; this.displayVertex(v2) ; theQueue.add(v2) ; } } for(int i=0; i < this.nVerts; i++) { this.vertexList[i].isVisisted = false ; //重新初始化节点,为下次的使用 } } /* * * * 请看我请看我请看我 * * * * */ //最小生成树的代码 public void mst() { Stack theStack = new Stack() ; this.vertexList[0].isVisisted = true ; //从顶点开是访问,所以立马设置ture //得到它的邻接的 theStack.push(0) ; //从节点0开始访问,压入到栈中 while (!theStack.isEmpty()) { //当前的栈不为空 int currentVertex = (Integer) theStack.peek() ; //只是看,没删删除掉最上面的那个元素 //取得当前顶点的邻接顶点 int v = this.getAdjUnivisitedVertex(currentVertex) ; if(v == -1) { theStack.pop() ; } //如果没有找到当前顶点的邻接的顶点,那么弹出一个顶点 else { //否则就是访问 this.vertexList[v].isVisisted = true ; theStack.push(v) ; this.displayVertex(currentVertex) ; //打印当前的节点 this.displayVertex(v) ; //大于邻接节点 System.out.println(" ") ; } //end 当这个栈的空间都没有的时候,那么就停止操作 } //初始化图的顶点未访问过的,下次用的时候还是还可以访问 for(int i=0; i < this.nVerts; i++) { this.vertexList[i].isVisisted = false ; //全部设置成没有访问过的 } } }