private VNode[] VNodes; // 将顶点放在数组中
private int nodeCount; // 顶点个数,也表示下一个可用的顶点数组下标
private int edgeCount; // 边个数
// int kind; // 图种类标识
public UnDirectedGraph() // 构造一个空图
{
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;
}
当顶点数组不够用时,要扩展顶点数组,这个跟之前的差别在于将VNodes[i]赋给lager[i],后,还要把它的边链表拉过来,见注释
2,建图(顶点,边的添加删除方法)
1)添加一个顶点容易,直接在数组里添加一个元素即可(而且是添加到最后一个下标,所以很easy)
2)删除一个顶点要先删除跟这个顶点有关联的全部边,然后将这个顶点从数组里移除,为了保持数组的连续性,还要把数组里在该顶点之后的顶点前移,而且还要把边链表拉过来(之前我也就考虑到了这一步,写这个文章的时候忽然发现还有错,当后面的顶点都往前移动了,顶点在数组里的位置就变了,那么数组里所有顶点的边链表里的边信息(边的2个端点)就要变)
3)添加一条边,在无向图里,添加一条从VNodes[i]到VNodes[i]的边和反过来添加是一样的,要在两个顶点的边链表里分别添加
4)删除一条边,同上,要在2个顶点的边链表里同时删除
从上面的分析可以看出,删除顶点的时候要调用删除边的方法,也就是说删除顶点包含了删除边。所以先来实现边的方法,再来实现顶点的方法
边的表示:
package Graph;
public class Edge {
private int start,end; // 边指向两个节点的位置(如果用数组存放节点,就是下标,如果用链存放下标,就是位置索引)
private Edge next; // 边指向的下一条边
private int len; // 边的信息(长度)
public Edge( int start, int end) // 用起始顶点和结束顶点构造一条边
{
this .start = start;
this .end = end;
next = null ;
}
public Edge( int start, int end, int len) // 用起始顶点和结束顶点构造一条边
{
this .start = start;
this .end = end;
this .len = len;
next = null ;
}
// set get省略
}
public void addEdge( int start, int end, int len){ // 在两个指定下标的节点之间添加一条边
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,len);
addEdgeToVNodeList(VNodes[start],edge); // 将边添加到某个顶点边链表中的方法
edge = new Edge(end,start,len);
addEdgeToVNodeList(VNodes[end],edge);
edgeCount ++ ;
}
public boolean hasEdge( int start, int end){ // 判断2个顶点之间是否存在边
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);
}
}
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;
}
// 按照广度规则从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 SingleBFS( int position){ // 只遍历position所在连通域的BFS
LinkedQueue queue = new LinkedQueue(); // 放结果的队列
BFSorder(position,queue); // 将position所在联通分量的顶点进队
return queue.iterator();
}
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();
}
2)深度优先遍历
深度优先遍历用栈实现,开始将起始顶点压栈,并置为已访问过的。然后循环的做下列事情:只要栈不空,就出栈,然后将出栈元素的所有未被访问过的邻接顶点入栈,循环直到栈空。
如果要实现一个迭代器,同样用一个结果队列收集每次出栈的顶点,返回队列的迭代器即可。
可以发现,深度和广度的实现唯一的差别就在于一个用栈一个用队列,其他在逻辑上都是相同的,这样导致深度优先实现起来跟上面的代码几乎完全一样,只需要遍历队列改成遍历栈即可。
直接贴出全部代码:
// 深度优先遍历图
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 );
}
package Graph;
import java.util.Iterator;
import Queue.LinkedQueue;
import Stack.LinkedStack;
// 实现一个无向图,将节点放在数组中来实现(也可以将节点放在链表中来实现)
public class UnDirectedGraph {
// private Edge[] Edges;
private VNode[] VNodes; // 将顶点放在数组中
private int nodeCount; // 顶点个数,也表示下一个可用的顶点数组下标
private int edgeCount; // 边个数
// int kind; // 图种类标识
public UnDirectedGraph() // 构造一个空图
{
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, int len){ // 在两个指定下标的节点之间添加一条边
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,len);
addEdgeToVNodeList(VNodes[start],edge); // 将边添加到某个顶点边链表中的方法
edge = new Edge(end,start,len);
addEdgeToVNodeList(VNodes[end],edge);
edgeCount ++ ;
/*
Edge edge = new Edge(start,end);
if(VNodes[start].getFirst() == null) //将边添加到start顶点的边链表中去
VNodes[start].setFirst(edge);
else{
Edge temp = VNodes[start].getFirst();
while(temp.getNext() != null)
temp = temp.getNext(); //最后一条边
temp.setNext(edge);
}
edge = new Edge(end,start); //实际上还是那条边
if(VNodes[end].getFirst() == null) //将边添加到end顶点的边链表中去
VNodes[end].setFirst(edge);
else{
Edge temp = VNodes[end].getFirst();
while(temp.getNext() != null)
temp = temp.getNext();
temp.setNext(edge);
}
edgeCount++;
*/
}
public boolean hasEdge( int start, int end){ // 判断2个顶点之间是否存在边
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
UnDirectedGraph g = new UnDirectedGraph();
for ( int i = 0 ;i < 10 ;i ++ )
g.addVNode(i);
g.addEdge( 0 , 2 , 1 );
g.addEdge( 0 , 3 , 1 );
g.addEdge( 1 , 2 , 1 );
g.addEdge( 1 , 5 , 1 );
g.addEdge( 1 , 4 , 1 );
g.addEdge( 2 , 5 , 1 );
g.addEdge( 3 , 7 , 1 );
g.addEdge( 3 , 9 , 1 );
g.addEdge( 4 , 9 , 1 );
g.addEdge( 5 , 9 , 1 );
g.addEdge( 6 , 8 , 1 );
g.addEdge( 6 , 9 , 1 );
g.addEdge( 7 , 8 , 1 );
// 测试删除边
// g.removeEdge(0, 3);
// 测试删除顶点
g.removeVNode( 9 );
g.removeVNode( 5 );
// 测试添加连通分量,
// g.addEdge(10, 11);
// g.addEdge(10, 12);
// g.addEdge(11, 13);
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\n多联通分量的深度优先遍历为: " );
while (it.hasNext())
{
VNode node = (VNode) it.next();
System.out.print(node.getVNode() + " " );
}
g.clearVisited();
}
}