有向图:由一组顶点和一组有方向的边组成,每条有方向的边都连接一组有序的顶点
顶点出度:指出的边的总数 顶点入度:指向该顶点边的总数
有向路径:一系列顶点,每个顶点存在一条有向边又它指向下一个顶点
有向环:至少含有一条边,并且起点和终点相同的有向路径
单点有向环:起点和终点相同不含有其他重复顶点和边的环
有向图数据结构
有向图的表示与无向图基本类似,包含顶点数,边数以及邻接表数组用来存储边的关系。当向图中添加一条边时,由于边是有序的,只需要添加依次即可。
private void addEdge(int w, int v) {
adj[w].add(v);
E++;
}
具体代码实现如下:
//有向图数据结构表示
public class Digraph {
private final int V;
private int E;
private Bag<Integer>[] adj;
//创建一个只含有顶点的有向图
public Digraph(int V){
this.V = V;
this.E = 0;
adj = new Bag[V];
for(int i=0; i<V; i++){
adj[i] = new Bag<Integer>();
}
}
//创建有向图 wr起点 vr终点
public Digraph(int V, int E, int[] wr, int[] vr){
this(V);
for(int i=0; i<E; i++){
addEdge(wr[i], vr[i]);
}
}
//添加一条有向边 w-->v
private void addEdge(int w, int v) {
adj[w].add(v);
E++;
}
public int V(){
return this.V;
}
public int E(){
return this.E;
}
//返回某个顶点指出的顶点连接
public Iterable<Integer> adj(int v){
return adj[v];
}
//有向图反转
public Digraph reverse(){
Digraph R = new Digraph(V);
for(int v=0; v<E; v++){
for(int w : adj[v]){
R.addEdge(w, v);
}
}
return R;
}
public String toString(){
String s = V + " vertices, " + E + " edges\n";
for(int v =0; v<V; v++){
s += v + ": ";
Iterator<Integer> it = this.adj(v).iterator();
while(it.hasNext()){
int w = it.next();
if(it.hasNext())
s += w + "--->";
else
s += w;
}
s += "\n";
}
return s;
}
public static void main(String[] args) {
int V = 13;
int E = 22;
int[] wr = {4,2,3,6,0,2,11,12,9,9,8,10,11,4,3,7,8,5,0,6,6,7};
int[] vr = {2,3,2,0,1,0,12,9,10,11,9,12,4,3,5,8,7,4,5,4,9,6};
Digraph d = new Digraph(V, E, wr, vr);
System.out.println(d.toString());
}
}
有向图的SWT图形化显示
为了便于显示有向图性质,利用SWT来绘制有向图,边是有序的,利用箭头来表示方向。
SWT箭头绘制方法参考http://www.blogjava.net/senlin-blog/archive/2007/09/21/146974.html
绘制一个箭头可以看成是绘制三条线段,AB,BC,BD
假定箭头的高度为H,宽度为2L,则可以求出ABC角度,BC可以看成是BA经过顺时针矢量旋转ABC得到,BD可以看成BA经过顺时针旋转负ABC角度得到,由矢量旋转公式,可以得到旋转后的坐标。
具体旋转矢量最终坐标公式:https://www.cnblogs.com/hongsheng001/p/4127458.html
x0=|R|*cosA
y0=|R|*sinA
x1 =|R|*cos(A +B)
y1=|R|*sin(A+B)
所以将x1,y1展开,有:
x1=|R|*(cosAcosB-sinAsinB)
y1=|R|*(sinAcosB+cosAsinB)
把 cosA = x0/|R| sinA = y0/|R| 代入上面的式子,得到
x1 = |R|*(x0*cosB/|R|-y0*sinB/|R|)
y1 = |R|*(y0*cosB/|R|+x0*sinB/|R|)
最终结果:
x1 = x0 * cosB - y0 * sinB
y1 = x0 * sinB + y0 * cosB
最终得到的绘制箭头SWT代码如下:
/**
* 画带箭头的线
*/
public void paintk(GC g, int x1, int y1, int x2, int y2) {
double H = 10; // 箭头高度
double L = 7; // 底边的一半
int x3 = 0;
int y3 = 0;
int x4 = 0;
int y4 = 0;
double awrad = Math.atan(L / H); // 箭头角度
double arraow_len = Math.sqrt(L * L + H * H); // 箭头的长度
double[] arrXY_1 = rotateVec(x2 - x1, y2 - y1, awrad, true, arraow_len);
double[] arrXY_2 = rotateVec(x2 - x1, y2 - y1, -awrad, true, arraow_len);
double x_3 = x2 - arrXY_1[0]; // (x3,y3)是第一端点
double y_3 = y2 - arrXY_1[1];
double x_4 = x2 - arrXY_2[0]; // (x4,y4)是第二端点
double y_4 = y2 - arrXY_2[1];
Double X3 = new Double(x_3);
x3 = X3.intValue();
Double Y3 = new Double(y_3);
y3 = Y3.intValue();
Double X4 = new Double(x_4);
x4 = X4.intValue();
Double Y4 = new Double(y_4);
y4 = Y4.intValue();
// g.setColor(SWT.COLOR_WHITE);
// 画线
g.drawLine(x1, y1, x2, y2);
// 画箭头的一半
g.drawLine(x2, y2, x3, y3);
// 画箭头的另一半
g.drawLine(x2, y2, x4, y4);
}
/**
* 取得箭头的绘画范围
*/
public double[] rotateVec(int px, int py, double ang, boolean isChLen, double newLen) {
double mathstr[] = new double[2];
// 矢量旋转函数,参数含义分别是x分量、y分量、旋转角、是否改变长度、新长度
double vx = px * Math.cos(ang) - py * Math.sin(ang);
double vy = px * Math.sin(ang) + py * Math.cos(ang);
if (isChLen) {
double d = Math.sqrt(vx * vx + vy * vy);
vx = vx / d * newLen;
vy = vy / d * newLen;
mathstr[0] = vx;
mathstr[1] = vy;
}
return mathstr;
}
为了便于显示,需要考虑起始点,终点相对位置,来设置终点的坐标,以免button的本身形状影响箭头显示。
//绘制有向图
private void drawDiGraph(List<PointG> lp, DiGraphPoint gp){
for(int i=0; i<lp.size(); i++){
Button lname = new Button(group, SWT.NONE|SWT.CENTER);
lname.setBounds(startX+lp.get(i).x*dilableOffsetX, startY+lp.get(i).y*dilableOffsetY, labelW, labelH);
lname.setText(String.valueOf(i));
lname.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
lname.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
}
gc = new GC(group);
gc.setLineWidth(2);
gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
int[] vr = gp.getVr();
int[] wr = gp.getWr();
for(int i=0; i<gp.getG().E(); i++){
PointG p1 = lp.get(vr[i]);
PointG p2 = lp.get(wr[i]);
int x1 = startX+p1.x*dilableOffsetX+labelW/2;
int y1 = startY+p1.y*dilableOffsetY+labelH/2;
int x2 = 0;
int y2 = 0;
//由于箭头绘制特殊性,坐标需要考虑起始点以及终点位置
//终点在起始点左边 y<--x
if(p2.x<=p1.x && p2.y==p1.y){
x2 = startX+p2.x*dilableOffsetX+labelW;
y2 = startY+p2.y*dilableOffsetY+labelH/2;
}
//终点在起始点右边 x-->y
else if(p2.x>=p1.x && p2.y==p1.y){
x2 = startX+p2.x*dilableOffsetX;
y2 = startY+p2.y*dilableOffsetY+labelH/2;
}
//终点在起始点上边方 x-->y
else if(p2.x==p1.x && p2.y<=p1.y){
x2 = startX+p2.x*dilableOffsetX+labelW/2;
y2 = startY+p2.y*dilableOffsetY+labelH;
}
//终点在起始点下边方 x-->y
else if(p2.x==p1.x && p2.y>=p1.y){
x2 = startX+p2.x*dilableOffsetX+labelW/2;
y2 = startY+p2.y*dilableOffsetY;
}
//终点在起始点左上方 x-->y
else if(p2.x<=p1.x && p2.y<=p1.y){
x2 = startX+p2.x*dilableOffsetX+labelW;
y2 = startY+p2.y*dilableOffsetY+labelH;
}
//终点在起始点左下方 x-->y
else if(p2.x<=p1.x && p2.y>=p1.y){
x2 = startX+p2.x*dilableOffsetX+labelW;
y2 = startY+p2.y*dilableOffsetY;
}
//终点在起始点右上方 x-->y
else if(p2.x>=p1.x && p2.y<=p1.y){
x2 = startX+p2.x*dilableOffsetX;
y2 = startY+p2.y*dilableOffsetY+labelH;
}
//终点在起始点右下方 x-->y
else if(p2.x>=p1.x && p2.y>=p1.y){
x2 = startX+p2.x*dilableOffsetX;
y2 = startY+p2.y*dilableOffsetY;
}
paintk(gc, x1, y1, x2, y2);
}
gc.dispose();
}
最终效果如下所示;
代码位置:https://github.com/ChenWenKaiVN/TreeSWT/blob/master/src/com/swt/GraphSWT.java
http://www.blogjava.net/senlin-blog/archive/2007/09/21/146974.html