图论(2-1) 有向图的实现
在无向图的基础上,稍作修改就可以实现有向图:
1)public void addEdge(int start,int end)
在无向图中要调用2次private void addEdgeToVNodeList(VNode node,Edge edge),将2个端点的边链表中都加上边,有向图中只需要在一个方向添加
2)public void removeEdge(int start,int end)
同上,只调用一次private void removeEdgeFromVNodeList(VNode node,Edge edge)
3)public void addVNode(Object element)
没有变化
4)public Object removeVNode(int position)
没有变化
5)public boolean hasEdge(int start,int end)
实现上没有变化 但含义变了,现在两个结点之间有边是有方向的
6)遍历迭代器
实现没有变
package Graph; import java.util.Iterator; import Queue.LinkedQueue; import Stack.LinkedStack; //实现一个有向图,将节点放在数组中来实现(也可以将节点放在链表中来实现), //其中顶点和边的定义都不变,唯一一点就是边定义中的start和end的含义不同了,此时包含方向 //与无向图对比,在以下操作上会不同:添加边,删除顶点,迭代器 public class DirectedGraph { private VNode[] VNodes; //将顶点放在数组中 private int nodeCount; //顶点个数,也表示下一个可用的顶点数组下标 private int edgeCount; //边个数 //int kind; //图种类标识 public DirectedGraph() //构造一个空图 { VNodes = new VNode[5]; nodeCount = 0; edgeCount = 0; } private void expand() //数组扩展 { VNode[] larger = new VNode[VNodes.length * 2]; for(int i = 0;i < VNodes.length;i++) { larger[i] = VNodes[i]; larger[i].setFirst(VNodes[i].getFirst()); //注意还要把边链表拉起来 } VNodes = larger; } public int size(){ return nodeCount; } public boolean isEmpty(){ return size() == 0; } //在图中的2个顶点之间添加一条边(此时有方向) public void addEdge(int start,int end){ //在两个指定下标的节点之间添加一条边 if(start < 0 || start >= size() || end < 0 || end >= size() || start == end) { System.out.println("节点选取非法!!!"); return; } if(hasEdge(start,end)) { System.out.println("这两点之间已经存在一条边!!!"); return; } //写一个支持方法将某个边添加到一个顶点的边链表中,减少了许多注释中的重复代码 Edge edge = new Edge(start,end); addEdgeToVNodeList(VNodes[start],edge); //注释掉无向图中在另一个方向添加 //edge = new Edge(end,start); //addEdgeToVNodeList(VNodes[end],edge); edgeCount++; } //判断2个顶点之间是否存在边(虽然相对无向图此方法没有变,但含义已经变了),在无向图中只判断单方向的从 //start到end有没有边是依赖于在无向图中每次添加边都在2个方向添加,如果一个方向有则另一个方向肯定有 public boolean hasEdge(int start,int end){ if(VNodes[start].getFirst() == null) return false; else { Edge temp = VNodes[start].getFirst(); while(temp != null) { if(temp.getEnd() == end) return true; else temp = temp.getNext(); } } return false; } private void addEdgeToVNodeList(VNode node,Edge edge){//将一条边添加到某个节点的边链表中 if(node.getFirst() == null) //将边添加到start顶点的边链表中去 node.setFirst(edge); else{ Edge temp = node.getFirst(); while(temp.getNext() != null) temp = temp.getNext(); //最后一条边 temp.setNext(edge); } } //在图中2个顶点之间删除一条边 public void removeEdge(int start,int end){ //删除两个指定下标顶点之间的边 if(start < 0 || start >= size() || end < 0 || end >= size() || start == end) { System.out.println("节点选取非法!!!"); return; } if(!hasEdge(start,end)) { System.out.println("所删除的两点之间不存在边!!!"); return; } //存在边得时候分别从两个顶点的边链表中删除即可 Edge edge = new Edge(start,end); removeEdgeFromVNodeList(VNodes[start],edge); //同样注释掉在另一个顶点的边链表中删除 //edge = new Edge(end,start); //removeEdgeFromVNodeList(VNodes[end],edge); edgeCount--; } //从某个顶点的边链表中删除某个边的操作(顶点的边链表中存在这条边的时候才会调用,所以不用再讨论存不存在) private void removeEdgeFromVNodeList(VNode node,Edge edge){ Edge temp = node.getFirst(); if(temp.getEnd() == edge.getEnd()) //末尾相等即可,同一顶点的边链表中的边得起始点都相同 node.setFirst(temp.getNext()); else //如果首条边不是要删的边 { Edge preTemp = temp; temp = temp.getNext(); while(temp.getEnd() != edge.getEnd()) { preTemp = temp; temp = temp.getNext(); } preTemp.setNext(temp.getNext()); } } //向图中添加一个顶点 public void addVNode(Object element){ //添加顶点 VNode node = new VNode(element); if(size() == VNodes.length) expand(); VNodes[nodeCount] = node; nodeCount++; //VNodes[nodeCount-1].setFirst(null);//可以省略,默认null } //从图中删除一个顶点 public Object removeVNode(int position){//删除指定下标处的顶点(注意先将相关联边删除再将顶点删除) if(isEmpty()) { System.out.println("图为空!!!"); return null; } if(position < 0 || position >= size()) { System.out.println("下标非法!!!"); return null; } Object result = VNodes[position]; for(int i = 0; i < size();i++) //1,删除每一条与它相关联的边 if(i != position && hasEdge(i,position)) removeEdge(i,position); for(int i = 0;i < size();i++) //2,调整由于将要移动顶点导致的每个顶点边链表的边信息 if(i != position) { Edge temp = VNodes[i].getFirst(); while(temp != null) { if(temp.getStart() > position) //与要移动的顶点关联的边都要调整 temp.setStart(temp.getStart()-1); if(temp.getEnd() > position) temp.setEnd(temp.getEnd()-1); temp = temp.getNext(); } } for(int i = position;i < size()-1;i++) //3,然后直接移除顶点,移动数组保持数组的连续性即可 { VNodes[i] = VNodes[i+1]; VNodes[i].setFirst(VNodes[i+1].getFirst()); } VNodes[nodeCount-1] = null;//最后一位置空 nodeCount--; return result; } //广度优先遍历图 public Iterator GraphBFS(int position){ //遍历整个图的BFS LinkedQueue queue = new LinkedQueue(); BFSorder(position,queue);//将position所在联通分量的顶点进队 for(int i = 0;i < size();i++) if(VNodes[i].getVisited() == false) BFSorder(i,queue); return queue.iterator(); } public Iterator SingleBFS(int position){ //只遍历position可以到达的顶点的BFS LinkedQueue queue = new LinkedQueue();//放结果的队列 BFSorder(position,queue);//将position所在联通分量的顶点进队 return queue.iterator(); } //按照广度规则从position开始将position所在连通分量顶点进队 private void BFSorder(int position,LinkedQueue queue){ LinkedQueue tempQueue = new LinkedQueue();//遍历队列 tempQueue.enqueue(VNodes[position]);//队列中同一存放顶点类型 VNodes[position].setVisited(true); while(!tempQueue.isEmpty()) { VNode node = (VNode) tempQueue.dequeue();//tempQueue的元素一次出队放入Queue即可 queue.enqueue(node); Edge temp = node.getFirst(); while(temp != null) //将没有被访问过的关联顶点放入tempQueue { int index = temp.getEnd();//相关联的顶点的下标 VNode linkNode = VNodes[index]; if(linkNode.getVisited() == false) //如果没有被访问过 { tempQueue.enqueue(VNodes[index]); VNodes[index].setVisited(true); } temp = temp.getNext(); } } } //深度优先遍历图 public Iterator GraphDFS(int position){ //遍历整个图的DFS LinkedQueue queue = new LinkedQueue();//放结果的队列 DFSorder(position,queue); for(int i = 0 ;i < size();i++) if(VNodes[i].getVisited() == false) DFSorder(i,queue); return queue.iterator(); } public Iterator SingleDFS(int position){ //只遍历position可以到达的顶点的DFS LinkedQueue queue = new LinkedQueue();//放结果的队列 DFSorder(position,queue); return queue.iterator(); } //按照深度优先规则将position所在连通域顶点进队 public void DFSorder(int position,LinkedQueue queue){ LinkedStack tempStack = new LinkedStack();//遍历栈 tempStack.push(VNodes[position]);//栈中同一存放顶点类型 VNodes[position].setVisited(true); while(!tempStack.isEmpty()) //每个顶点出栈进入队列后要将其未被访问的关联顶点入栈 { VNode node = (VNode) tempStack.pop();//tempStack的元素依次出栈放入Queue即可 queue.enqueue(node); Edge temp = node.getFirst(); while(temp != null) //将没有被访问过的关联顶点放入tempStack { int index = temp.getEnd();//相关联的顶点的下标 VNode linkNode = VNodes[index]; if(linkNode.getVisited() == false) //如果没有被访问过 { tempStack.push(VNodes[index]); VNodes[index].setVisited(true); } temp = temp.getNext(); } } } public void clearVisited(){ //清除访问记录 for(int i = 0;i < size();i++) VNodes[i].setVisited(false); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub DirectedGraph g = new DirectedGraph(); for(int i = 0;i < 10;i++) g.addVNode(i); g.addEdge(0, 2); g.addEdge(0, 3); g.addEdge(1, 2); g.addEdge(1, 5); g.addEdge(1, 4); g.addEdge(2, 5); g.addEdge(3, 7); g.addEdge(3, 9); g.addEdge(4, 9); g.addEdge(5, 9); g.addEdge(6, 8); g.addEdge(6, 9); g.addEdge(7, 8); Iterator it = g.SingleBFS(0); System.out.println("广度优先遍历为: "); while(it.hasNext()) { VNode node = (VNode) it.next(); System.out.print(node.getVNode() + " "); } g.clearVisited(); it = g.SingleDFS(0); System.out.println("\n深度优先遍历为: "); while(it.hasNext()) { VNode node = (VNode) it.next(); System.out.print(node.getVNode() + " "); } g.clearVisited(); it = g.GraphBFS(0); System.out.println("\n\n整个图的广度优先遍历为: "); while(it.hasNext()) { VNode node = (VNode) it.next(); System.out.print(node.getVNode() + " "); } g.clearVisited(); it = g.GraphDFS(0); System.out.println("\n整个图的深度优先遍历为: "); while(it.hasNext()) { VNode node = (VNode) it.next(); System.out.print(node.getVNode() + " "); } g.clearVisited(); } }
构造下面的有向图测试一下:
广度优先遍历为:
0 2 3 5 7 9 8
深度优先遍历为:
0 3 9 7 8 2 5
整个图的广度优先遍历为:
0 2 3 5 7 9 8 1 4 6
整个图的深度优先遍历为:
0 3 9 7 8 2 5 1 4 6
可以发现,有向图的遍历,当顶点之间不可到达时,遍历不到,就像无向图中不在一个连通域一样。