图的描述和抽象——动态聚集、关联和依赖的应用实例

图的描述和抽象——动态聚集、关联和依赖的应用实例

一、           问题描述

在数据结构和图论中,我们经常会接触到“图”这种数据结构。“图”在很多程序中都有着极其重要的作用。因此,在计算机当中描述一个图,并将其显示出来是我们经常碰到的一个问题。在这里,我们将用面向对象的思想实现图的描述及其抽象。注意,我们描述的图是有向的(无向图可以当成有向图的一种特殊情况),而且我们允许两点之间存在多条线。比如,我们应该可以描述下面这样的这一种图(图1):

(图1)(深色为起点,淡色为终点)

(图2

二、           问题分析

从上面的问题描述中可以看出,我们主要是解决两个问题。一个是描述图,一个是显示图。显然对于一个图来说,它应该是没有位置信息的,我们用一个BasicGraph类来描述这样的一种图,它描述纯粹的图的数据结构。而我们用另一个PositionGraph类来添加位置的信息,该类可以不必理会图的数据结构是如果组织的,它只需要知道点和线的位置信息。因此,这两个类有图2所示的继承关系。

(一)用动态聚集方法描述图

对于一个图来说,它动态聚集了点和线。我们用BasicPoint类来描述点,用BasicLine类来描述线。这两个类与BasicGraph位于同一个层次,它们都没有位置信息。

现在我们对一个图引入了三个类:BasicPointBasicLineBasicGraph。这三个类具有如下的关系:

1.   一条线有两个端点,一个为起点,一个为始点。

2.   一个点可能与多条边相连。将与某一端点相连的线分成两类:一类是以该点为起点的线;另一类是以该点为终点的线。

3.  一个图由一组点与一组线组成。

由此,我们可以得到这三个类的关系图如图3,图4和图5所示:

(图4

(图5

(图3

其中,图3说明了一个图是由一组点和一组线动态聚集而成。图并不知道点和线的关系,点和线的关系是散布在点和线之间的,点和线之间更详细的关系见图4和图5。从图中我们可以看出,图知道它所有的点和所有的线;点知道以它为起点的所有线,也知道以它为终点的所有线;线知道它的起点和终点。

(二)用继承方法描述有位置信息的图

让一个图在屏幕中显示出来,我们不仅需要知道图的拓扑结构,而且还需要知道点和线的位置信息。显然上面的三个类已经足以描述图的拓扑结构了。接下来,我们必须在已有的类中加入位置的信息。从而得到三个包含位置信息的类:PositionPointPositionLinePositionGraph。这三个类分别继承于上面的BasicPointBasicLineBasicGraph

对于点,我们使用setPosition方法添加点的位置信息,给点定位;对于线,我们使用setGradualPoint方法设置渐近点,这样,通过起点、终点以及渐近点,我们可以画出一条曲线出来。

(三)用关联方法在画板上显示有位置信息的图

我们的目的是能够在多个画板上显示图,因此我们使用简化的MVC模式。在这个简化的MVC模式里面,我们只有MModel)和VView),而不考虑CController)。我们这里的M就是一个PositionGraph类,而V是一种实现了Visuable接口的类,Visuable接口定义了一个方法repaint,使得每一次PositionGraph改变之后,可以调用V中的repaint函数,重新画图。

显然,PositionGraph也可以动态聚集多个实现了Visuable接口的类,以更在多个地方显示图。而单个实现了Visuable接口的类将通过关联的方法,访问PositionGraph的数据结构,并将其显示出来。我们这里给出了Visuable接口的一个实现类DrawingPanel。它们的关系见图6所示,这里,一个DrawingPanel只能画一个PositionGraph

(图6

(图7

(四)用依赖方法将一个无位置图转变成有位置图

通常,我们只关心图的拓扑结构,而并不关系它的位置。而我们最后又希望可以在程序中直接显示出来。因此,我们需要一个助手类,它可以根据一个BasicGraph,一个画板(DrawingPanel,提供高度和宽度信息),而自动的定位点以及线的位置,然后自动产生一个PositionGraph。这就是类PosGraphHelper的作用了。关于刚刚提及的几个类的关系,见图7所示。

对一个无位置的图的点和线进行定位是很困难的,我们只是给出一种较简单的实现。如果有更好的算法,我们只需要更新PosGraphHelper类即可。

三、           动态聚集的实现

动态聚集可以多种方法实现,Java2中给出了Collections框架,并提供了多个类可以让我们实现动态聚集。基本上它们分为四类:哈希表,可变长数组,平衡树和链表。Vector类是属于可变长数组;而TreeMapHashMap属于哈希表,TreeMapHashMap多了一个功能,可以对关键字进行排序。

BasicGraph描述的图当中,我们给每一个点一个唯一的ID,每条线也有唯一的ID。我们可能经常需要通过ID来访问图当中的点和线,因此,使用哈希表来保存图当中的点和线是最好的选择。经常我们给点的ID,可能是:123┉,或者P1、P2、P3┉。因此,点最好是可以按ID的次序进行排列的。因此,在BasicGraph中我们使用如下的语句来动态聚集点和线:

TreeMap points;

HashMap lines;

 

       一条线包含两个端点的信息,因此,BasicLine只需要包含如下的信息即可:

    BasicPoint startPoint,endPoint;

        而一个点会记录所有与它相邻的线,这些线分成两类,一类以该点为起点,一类以该点为终点。在BasicPoint中这些线使用下面的方面聚集起来:

    Vector linesAsStartPoint;
    Vector linesAsEndPoint; 

       现在的问题,是必须在BasicGraph中添加或删除点和线时自动维护这些信息。下面给出添加线的顺序图如下:

在这里,其它类必须对BasicGraph操作以实现对图的修改,首先,如果要添加一条线时,我们必须给出起点的IDsid)和始点的IDeid),还要给出所添加线的IDid)。当以这些参数调用addLine时,BasicGraph通过sideid从哈希表取出起点(sp)和终点(ep),用以新生成一个BasicLine的实例。在新生成一个BasicLine实例时,它会调用起点spaddLineAsStartPoint方法,将线自身注册为起点sp的一条邻接线;同时,它会调用终点epaddLineAsEndPoint方法,将线自身注册为终点ep的一条邻接线。最后,BasicGraph会调用自身的addLine方法,将新生成的一个BasicLine实例注册为自身的一条线。具体的程序见源码。

四、           关联的实现

在一个DrawingPanel类中,关联了一个有位置的图,即其拥有PositionGraph类的一个实例。当调用DrawingPanel类的setPosGraph方法时,DrawingPanely就初始化PositionGraph的这个实例。通过访问PositionGraph类的成员,DrawingPanely首先画点,接着画线,从而画出整个图。

一个图可以聚集多个视图,当只有一个视图且它就是一个DrawingPanel类的实例时,我们也可以看成从PositionGraph类可以关联到DrawingPanel。这样的一种关联的意义在于,PositionGraph一知道它自己的拓扑结构变的时候,它就可以调用PositionGraphrepaint方法进行重画。

五、           依赖的实现

PosGraphHelper中,我们提供了几个方法,可以实现从一个BasicGraph类生成一个PositionGraph的方法,或者自动生成一个完全图等等。比如,getPositionGraph可以根据DrawingPanel的大小,将BasicGraph转成一个PositionGraph。其源码如下:

public static PositionGraph getPositionGraph(DrawingPanel panel,BasicGraph bgraph){

  PositionGraph pgraph = new PositionGraph();

  reshape(panel,bgraph,pgraph);

  return pgraph;

}  

其中的reshape方法可以实现对点和线的重新定位,而最后生成PositionGraph,返回给调用者。Reshape方法还很不完善,有待进一步修改。

六、           用到的类列表

到目前,我们已经解说了使用到的大部分类。现在把所有的类列出来,如下表所示:

类名

作用

BasicPoint

实现基本点,基本点只有点线关系,没有位置信息

BasicLine

实现基本线,基本线只有点线关系,没有位置信息

BasicGraph

实现拓扑图,没有位置信息

PositionPoint

BasicPoint的子类,添加位置信息

PositionLine

BasicLine的子类,添加位置信息

PositionGraph

BasicGraph的子类,实现一类有位置的图

Visuable

接口,定义一类可以重画(repaint)的类

DrawingPanel

画板,实现Visuable接口,关联一个PositionGraph对象,实现画图的功能

PosGraphHelper

助手类,用于产生一些特殊的PositionGraph,或者从BasicGraph转换而来

Frame1

测试类,用于测试上面的其它类

我们把前面九个类放在包DynAggregate当中。以和其它无关的类区别开来。

七、           实例

我们产生两个图,一个是无向图中的完全图,一个是有向图中的完全图(不包括点自身到自身的线)。这两个图是分别通过:

1.  PosGraphHelper.produceFullGraph(5)

2.  PosGraphHelper.produceFullGraph2(5)

生成的。它们产生的结果如下:

有向图的完全图

无向图中的完全图

八、           部分源码(只包括一小部分)

1BasicPoint源码

class BasicPoint{

  protected String point_id;                 //唯一ID

  protected Vector linesAsStartPoint;   //保存所有以该点为起点的线

  protected Vector linesAsEndPoint;     //保存所有以该点为终点的线

 

  public BasicPoint(String id){

    point_id = id;

    linesAsStartPoint = new Vector();

    linesAsEndPoint = new Vector();

  }

 

  public void addLineAsStartPoint(BasicLine line){     //把线注册为以该点为起点的线

    linesAsStartPoint.add(line);

  }

 

  public void addLineAsEndPoint(BasicLine line){     //把线注册为以该点为终点的线

    linesAsEndPoint.add(line);

  }

}

2BasicLine源码

class BasicLine{

  protected BasicPoint startPoint,endPoint;

  protected String line_id;

  protected int powerValue = 0;

 

  public BasicLine(BasicPoint start,BasicPoint end,String id){    //线总由两个点组成

    startPoint = start;

    endPoint = end;

    start.addLineAsStartPoint(this);                                        //把该线与起点关联

    end.addLineAsEndPoint(this);                                          //把该线与终点关联

    line_id = id;

  }

 

  public void resetStartPoint(BasicPoint start){                        //有时可能需要改线的起点

    startPoint.removeLineAsStartPoint(this);

    start.addLineAsStartPoint(this);

    startPoint = start;

  }

 

  public void resetEndPoint(BasicPoint end){                           //有时可能需要改线的终点

    endPoint.removeLineAsEndPoint(this);

    end.addLineAsEndPoint(this);

    endPoint = end;

  }

}

3BasicGraph源码

public class BasicGraph{

  protected TreeMap points;

  protected HashMap lines;

 

  public BasicGraph() {                                                         //初始化

    points = new TreeMap();

    lines = new HashMap();

  }

 

  public Vector getPoints(){                                                   //得到图的所有点

    return new Vector(points.values());

  }

 

  public Vector getLines(){                                                    //得到图的所有线

    return new Vector(lines.values());

  }

 

  public BasicPoint addPoint(String id){                                  //对图加点

    return addPoint(new BasicPoint(id));

  }

 

  public BasicPoint addPoint(BasicPoint point){                       //对图加点

    if(point != null) points.put(point.getID(),point);

    return point;

  }

 

  public BasicLine addLine(String startID,String endID,String id){    //对图加线

    Object start = points.get(startID), end = points.get(endID);

    if(start == null) start = addPoint(startID);

    if(end == null) end = addPoint(endID);

    return addLine(new BasicLine((BasicPoint)start,(BasicPoint)end,id));

  }

 

  public BasicLine addLine(BasicLine line){                             //对图加线

    if(line != null) lines.put(line.getID(),line);

    return line;

  }

}

4PositionPoint源码

public class PositionPoint extends BasicPoint{

  private Point position;

 

  public PositionPoint(String id){

    this(id,new Point(0,0));

  }

 

  public PositionPoint(String id,Point pos){

    super(id);

    position = pos;

  }

 

  void setPosition(Point pos){                                                       //设置点的位置

    position = pos;

  }

}

5PositionLine源码

public class PositionLine extends BasicLine{

  private Point gradualPoint = null;      //线的位置已由起点和终点决定,但还可有渐近点

 

  public PositionLine(PositionPoint startPoint,PositionPoint endPoint,String id){

    super(startPoint,endPoint,id);

  }

 

  public void setGradualPoint(Point point){

    gradualPoint = point;

  }

 

  public Point getGradualPoint(){

    return gradualPoint;

  }

}

6PositionGraph源码

public class PositionGraph extends BasicGraph{

  private Vector viewers = null;                         //可以注册多个视图

 

  public PositionGraph() {

    super();

    viewers = new Vector();

  }

 

  public BasicPoint addPoint(String id) {            //重载BasicPoint,以成生成PositionPoint

    PositionPoint pp = (PositionPoint)addPoint(new PositionPoint(id));

    return pp;

  }

 

  public BasicPoint addPoint(BasicPoint point){  //重载,实现自动重画功能

    PositionPoint pp = (PositionPoint)super.addPoint(point);

    repaint();

    return pp;

  }

 

  //重载BasicLine,以成生PositionLine线

  public BasicLine addLine(String startID,String endID,String id){

    Object start = points.get(startID), end = points.get(endID);

    if(start == null || end == null) return null;

PositionLine pl =

(PositionLine)addLine(new PositionLine((PositionPoint)start,(PositionPoint)end,id));

    return pl;

  }

 

  public BasicLine addLine(BasicLine line){ //重载,实现自动重画功能

    PositionLine pl = (PositionLine)super.addLine(line);

    repaint();

    return pl;

  }

 

  public void addViewer(Visuable viewer){         //添加视图

    viewers.add(viewer);

    viewer.repaint();

  }

 

  public void repaint(){                                    //所有视图重画

    for(int i = 0; i < viewers.size(); i++){

      ((Visuable)viewers.elementAt(i)).repaint();

    }

  }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值