本节参考《Java数据结构与算法》 第二版
本节主要内容:
- 图介绍
- 搜索
- 最小生成树
- 有向图的拓扑顺序
- 有向图的连通性
1、图介绍
图是与树有些类似的数据结构,实际上从数学意义上,树是图的一种,然而在计算机程序设计中,图的应用方式与树不同。
图通常有一个固定的形状,这是由物理或者抽象问题决定的。例如:图中节点表示城市,边可以表示城市间的班机路线。另一个更抽象的例子是一个代表了几个单独的图,这些任务是完成一个项目所必需的,在图zh中,节点可能表示任务,有向边(一端带一个箭头)指示某个任务必须在某一个任务前完成。
讨论图时,通常称节点为顶点
介绍
图不是要反映地图上的地理位置,只是显示顶点和边的关系--即那些边连接着哪些顶点,它本身不涉及物理的远近和方向。两个交叉点间的连通性(或不连通性)是重要的,而实际的路线并不重要。
邻接
如果两个顶点被同一条边连接,则称这两个顶点是邻接的,如上图中顶点I和G是邻接的,但是I和F是不邻接的。和某个指定顶点链接的顶点有时称为邻居,例如G的邻居就是I、H、F。
路径
路径是边的序列,上图表示的一条从顶点B到顶点J的路径,这条路径通过顶点A和顶点E。这条路径称作BAEJ,这两个顶点之间还有其他路径:从B到J的另一条路径是BCDJ。
连通图
如果至少有一条路可以连接器所有的顶点,那么这个图被称做连通的,如果“不能从这里到哪里”那么就是非联通的
本章讨论的算法都是应用在连通图或者非连通图
有向图和带权图
上面的图是无向图,说明图中边没有方向:可以从一边到另一边可以从A到B,也可从B到A(高速路网)
有向图就是只能从A到B,就像单行道一样,在一些图中边被赋予权值,权值是一个数字,他能表示两个顶点间的物理距离,或者从一边到另一个顶点的时间,或者花费,这样的图被称为带权图。
本节主要讨论简单的无向、无权图;
在程序中表示图:
顶点
在处理一些实际问题时,需要一个顶点,一个标志位,显示状态
public class Vertex {
public char label; //label eg 'A'
public boolean wasVisited; //
public Vertex(char label) { //constructor
this.label = label;
wasVisited = false;
}
} //end close vertex
顶点对象能放在数组中,用下标表示,也可以放在链表中,主要是为了存储方便,vertexList[] 存储顶点对象
边
图的结构中,不像二叉树,它更自由,比树的表示方法更简单,一般使用两个方法表示:邻接矩阵和邻接表(一条边链接两个节点,这两个顶点就是邻接的)
邻接矩阵
邻接矩阵
顶点被作为行和列的标题,两顶点间有边则标识为1,没有则为0.也可以用true,false表示。顶点与自身的链接表示为0,因此左上角到右下角的对角线全部是0.
注意:这个矩阵上三角是下三角的镜像:包含了相同的信息,看似低效,但是在大多数计算机语言中,创造三角形数组比较困难,因此接受这种冗余,同时增加一条边时,邻接矩阵必须更新两部分。
邻接表
邻接表是一个链表数组或者链表的链表,每个单独的链表表示了有哪些顶点与当前顶点邻接
邻接表
顶点
| 包含邻接顶点的链表
|
A
| B->C->D
|
B
| A->D
|
C
| A
|
D
| A->B
|
上表中,符号->表示链表中的一个节点。链表中每个节点都是一个顶点,邻接表表示了当前顶点与那些顶点链接-两个顶点间存在边,而不是表示顶点间的路径。
在图中添加顶点和边
为向图中添加顶点,必须使用new 保留字生成一个新的顶点对象,然后插入到顶点数组vertexList中:
vertexList[nVerts++] = new Vertex("F");
邻接矩阵中添加新边,在顶点1和顶点3之间添加一条边,对应于vertexList数组的下标,首次创建邻接矩阵adjMat,初始值为0
adjMat[1][3] = 1;
adjMat[3][1] = 1;
邻接表添加,就把1加到3的链表中,然后把3加到1的链表中
Graph类
搜索
在图中实现的最基本操作之一就是搜索从一个指定顶点可以达到那些顶点:例如从苏州出发的火车可以到达哪些城市。从顶点(苏州)沿着(边)铁路线能够到达的其他顶点(其他城市)。
图的搜索可以通过两个方法来实现:深度优先搜索(DFS)、广度优先搜索(BFS),它们最终都会到达所有连通的顶点,深度优先通过栈来实现,广度优先通过队列实现。
深度优先搜索(DFS)
深度优先搜索的规则:
- 规则一:如果可能,访问一个邻接的未访问顶点,标记它,并把它放如栈中
- 规则二: 行规则一时,如果栈不为空,就从栈中弹出一个顶点
- 规则三:如果不能执行规则一和规则二,就完成了整个搜索过程
上题思想:从顶点开始,找一个起始点,假设选取 A 顶点为起始点,并且按照字母优先顺序进行访问,那么应用规则 1 ,接下来访问顶点 B,然后标记它,并将它放入栈中;再次应用规则 1,接下来访问顶点 F,再次应用规则 1,访问顶点 H。我们这时候发现,没有 H 顶点的邻接点了,这时候应用规则 2,从栈中弹出 H,这时候回到了顶点 F,但是我们发现 F 也除了 H 也没有与之邻接且未访问的顶点了,那么再弹出 F,这时候回到顶点 B,同理规则 1 应用不了,应用规则 2,弹出 B,这时候栈中只有顶点 A了,然后 A 还有未访问的邻接点,所有接下来访问顶点 C,但是 C又是这条线的终点,所以从栈中弹出它,再次回到 A,接着访问 D,G,I,最后也回到了 A,然后访问 E,但是最后又回到了顶点 A,这时候我们发现 A没有未访问的邻接点了,所以也把它弹出栈。现在栈中已无顶点,于是应用规则 3,完成了整个搜索过程。下图表示栈中数据的变化:
深度优先算法要得到距离起始点最远点的顶点,然后不能在继续前进的时候返回,使用深度优先这个术语表示与起始的距离
深度优先搜索的关键在于能够找到与某一个顶点邻接且没有访问过的顶点,邻接矩阵是关键,找到指定顶点所在行,从第一列开始向后寻找值为1的列:列号是邻接顶点的号码。检查这个顶点是否访问过,如果是这样,那么这就是要访问的下一个顶点。如果该行没有顶点即等于1(l邻接)且又是未访问的,那么与指定相邻的顶点就全部访问过了。
完整DFS代码:
// dfs.java
// demonstrates depth-first search
// to run this program: C>java DFSApp
class StackX
{
private final int SIZE = 20;
private int[] st;
private int top;
// ------------------------------------------------------------
public StackX() // constructor
{
st = new int[SIZE]; // make array
top = -1;
}
// ------------------------------------------------------------
public void push(int j) // put item on stack
{ st[++top] = j; }
// ------------------------------------------------------------
public int pop() // take item off stack
{ return st[top--]; }
// ------------------------------------------------------------
public int peek() // peek at top of stack
{ return st[top]; }
// ------------------------------------------------------------
public boolean isEmpty() // true if nothing on stack
{ return (top == -1); }
// ------------------------------------------------------------
} // end class StackX
class Vertex
{
public char label; // label (e.g. 'A')
public boolean wasVisited;
// ------------------------------------------------------------
public Vertex(char lab) // constructor
{
label = lab;
wasVisited = false;
}
// ------------------------------------------------------------
} // end class Vertex
class Graph
{
private final int MAX_VERTS = 20;
private Vertex vertexList[]; // list of vertices
private int adjMat[][]; // adjacency matrix
private int nVerts; // current number of vertices
private StackX theStack;
// ------------------------------------------------------------
public Graph() // constructor
{
vertexList = new Vertex[MAX_VERTS];
// adjacency matrix
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
for(int y=0; y<MAX_VERTS; y++) // set adjacency
for(int x=0; x<MAX_VERTS; x++) // matrix to 0
adjMat[x][y] = 0;
theStack = new StackX();
} // end constructor
// ------------------------------------------------------------
public void addVertex(char lab)
{
vertexList[nVerts++] = new Vertex(lab);
}
// ------------------------------------------------------------
public void addEdge(int start, int end)
{
adjMat[start][end] = 1;
adjMat[end][start] = 1;
}
// ------------------------------------------------------------
public void displayVertex(int v)
{
System.out.print(vertexList[v].label);
}
// ------------------------------------------------------------
public void dfs() // depth-first search
{ // begin at vertex 0
vertexList[0].wasVisited = true; // mark it
displayVertex(0); // display it
theStack.push(0); // push it
while( !theStack.isEmpty() ) // until stack empty,
{
// get an unvisited vertex adjacent to stack top
int v = getAdjUnvisitedVertex( theStack.peek() );
if(v == -1) // if no such vertex,
theStack.pop();
else // if it exists,
{
vertexList[v].wasVisited = true; // mark it
displayVertex(v); // display it
theStack.push(v); // push it
}
} // end while
// stack is empty, so we're done
for(int j=0; j<nVerts; j++) // reset flags
vertexList[j].wasVisited = false;
} // end dfs
// ------------------------------------------------------------
// returns an unvisited vertex adj to v
public int getAdjUnvisitedVertex(int v)
{
for(int j=0; j<nVerts; j++)
if(adjMat[v][j]==1 && vertexList[j].wasVisited==false)
return j;
return -1;
} // end getAdjUnvisitedVertex()
// ------------------------------------------------------------
} // end class Graph
class DFSApp
{
public static void main(String[] args)
{
Graph theGraph = new Graph();
theGraph.addVertex('A'); // 0 (start for dfs)
theGraph.addVertex('B'); // 1
theGraph.addVertex('C'); // 2
theGraph.addVertex('D'); // 3
theGraph.addVertex('E'); // 4
theGraph.addEdge(0, 1); // AB
theGraph.addEdge(1, 2); // BC
theGraph.addEdge(0, 3); // AD
theGraph.addEdge(3, 4); // DE
System.out.print("Visits: ");
theGraph.dfs(); // depth-first search
System.out.println();
} // end main()
} // end class DFSApp
广度优先搜索(BFS)
正如深度优先搜索DFS中看到的,算法表现得好像要尽快远离起点似的,相反在广度优先搜索中,算法好像要尽可能地靠近起始点,它首先访问起始顶点的所有邻接点,然后在访问比较较远的区域,这种搜索不能用栈,只能用队列。
和深度优先搜索一样,广度优先搜索也有规则:
- 规则一:访问下一个未访问的邻接点(如果存在),这个顶点必须是当前顶点的邻接点,标记它,并把它插入到队列中
- 规则二:如果因为已经没有未访问顶点而不能执行规划一,那么从队列头取一个顶点(如果存在),并使其成为当前顶点;
- 规则三:如果因为队列为空而不能执行规则而,则搜索结束
因此,需要对于上面的图,应用广度优先搜索:以A为起始点,首先访问所有与 A 相邻的顶点,并在访问的同时将其插入队列中,现在已经访问了 A,B,C,D和E。这时队列(从头到尾)包含 BCDE,已经没有未访问的且与顶点 A 邻接的顶点了,所以从队列中取出B,寻找与B邻接的顶点,这时找到F,所以把F插入到队列中。已经没有未访问且与B邻接的顶点了,所以从队列列头取出C,它没有未访问的邻接点。因此取出 D 并访问 G,D也没有未访问的邻接点了,所以取出E,现在队列中有 FG,在取出 F,访问 H,然后取出 G,访问 I,现在队列中有 HI,当取出他们时,发现没有其它为访问的顶点了,这时队列为空,搜索结束。
搜索过程如下:
完整BFS代码
// bfs.java
// demonstrates breadth-first search
// to run this program: C>java BFSApp
class Queue
{
private final int SIZE = 20;
private int[] queArray;
private int front;
private int rear;
// -------------------------------------------------------------
public Queue() // constructor
{
queArray = new int[SIZE];
front = 0;
rear = -1;
}
// -------------------------------------------------------------
public void insert(int j) // put item at rear of queue
{
if(rear == SIZE-1)
rear = -1;
queArray[++rear] = j;
}
// -------------------------------------------------------------
public int remove() // take item from front of queue
{
int temp = queArray[front++];
if(front == SIZE)
front = 0;
return temp;
}
// -------------------------------------------------------------
public boolean isEmpty() // true if queue is empty
{
return ( rear+1==front || (front+SIZE-1==rear) );
}
// -------------------------------------------------------------
} // end class Queue
class Vertex
{
public char label; // label (e.g. 'A')
public boolean wasVisited;
// -------------------------------------------------------------
public Vertex(char lab) // constructor
{
label = lab;
wasVisited = false;
}
// -------------------------------------------------------------
} // end class Vertex
class Graph
{
private final int MAX_VERTS = 20;
private Vertex vertexList[]; // list of vertices
private int adjMat[][]; // adjacency matrix
private int nVerts; // current number of vertices
private Queue theQueue;
// ------------------------------------------------------------
public Graph() // constructor
{
vertexList = new Vertex[MAX_VERTS];
// adjacency matrix
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
for(int j=0; j<MAX_VERTS; j++) // set adjacency
for(int k=0; k<MAX_VERTS; k++) // matrix to 0
adjMat[j][k] = 0;
theQueue = new Queue();
} // end constructor
// -------------------------------------------------------------
public void addVertex(char lab)
{
vertexList[nVerts++] = new Vertex(lab);
}
// -------------------------------------------------------------
public void addEdge(int start, int end)
{
adjMat[start][end] = 1;
adjMat[end][start] = 1;
}
// -------------------------------------------------------------
public void displayVertex(int v)
{
System.out.print(vertexList[v].label);
}
// -------------------------------------------------------------
public void bfs() // breadth-first search
{ // begin at vertex 0
vertexList[0].wasVisited = true; // mark it
displayVertex(0); // display it
theQueue.insert(0); // insert at tail
int v2;
while( !theQueue.isEmpty() ) // until queue empty,
{
int v1 = theQueue.remove(); // remove vertex at head
// until it has no unvisited neighbors
while( (v2=getAdjUnvisitedVertex(v1)) != -1 )
{ // get one,
vertexList[v2].wasVisited = true; // mark it
displayVertex(v2); // display it
theQueue.insert(v2); // insert it
} // end while
} // end while(queue not empty)
// queue is empty, so we're done
for(int j=0; j<nVerts; j++) // reset flags
vertexList[j].wasVisited = false;
} // end bfs()
// -------------------------------------------------------------
// returns an unvisited vertex adj to v
public int getAdjUnvisitedVertex(int v)
{
for(int j=0; j<nVerts; j++)
if(adjMat[v][j]==1 && vertexList[j].wasVisited==false)
return j;
return -1;
} // end getAdjUnvisitedVertex()
// -------------------------------------------------------------
} // end class Graph
class BFSApp
{
public static void main(String[] args)
{
Graph theGraph = new Graph();
theGraph.addVertex('A'); // 0 (start for bfs)
theGraph.addVertex('B'); // 1
theGraph.addVertex('C'); // 2
theGraph.addVertex('D'); // 3
theGraph.addVertex('E'); // 4
theGraph.addEdge(0, 1); // AB
theGraph.addEdge(1, 2); // BC
theGraph.addEdge(0, 3); // AD
theGraph.addEdge(3, 4); // DE
System.out.print("Visits: ");
theGraph.bfs(); // breadth-first search
System.out.println();
} // end main()
} // end class BFSApp
最小生成树
上图中,可能需要确定是否使用了最少的连线,也就是顶点之间连线最少,没有占据多余的空间。顶点和边之间只有
最少数量的边保证他们的联通,这就是最小生成树。
执行深度优先搜索的时候,记录走过的边,就可以生成一个最小生成树,两者唯一区别是最小生成树需要记录所走过的边
深度优先搜索访问所有的顶点,但是只访问一次,它绝不会两次访问同一个顶点,当它看到某条边将到达一个已访问的顶点,他就不会走这条边,它从不遍历那些不可能的边,因此DFS算法走过整个图的路径必定是最小生成树。
最小生成树源代码:
// mst.java
// demonstrates minimum spanning tree
// to run this program: C>java MSTApp
class StackX
{
private final int SIZE = 20;
private int[] st;
private int top;
// -------------------------------------------------------------
public StackX() // constructor
{
st = new int[SIZE]; // make array
top = -1;
}
// -------------------------------------------------------------
public void push(int j) // put item on stack
{ st[++top] = j; }
// -------------------------------------------------------------
public int pop() // take item off stack
{ return st[top--]; }
// -------------------------------------------------------------
public int peek() // peek at top of stack
{ return st[top]; }
// -------------------------------------------------------------
public boolean isEmpty() // true if nothing on stack
{ return (top == -1); }
// -------------------------------------------------------------
} // end class StackX
class Vertex
{
public char label; // label (e.g. 'A')
public boolean wasVisited;
// -------------------------------------------------------------
public Vertex(char lab) // constructor
{
label = lab;
wasVisited = false;
}
// -------------------------------------------------------------
} // end class Vertex
class Graph
{
private final int MAX_VERTS = 20;
private Vertex vertexList[]; // list of vertices
private int adjMat[][]; // adjacency matrix
private int nVerts; // current number of vertices
private StackX theStack;
// -------------------------------------------------------------
public Graph() // constructor
{
vertexList = new Vertex[MAX_VERTS];
// adjacency matrix
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
for(int j=0; j<MAX_VERTS; j++) // set adjacency
for(int k=0; k<MAX_VERTS; k++) // matrix to 0
adjMat[j][k] = 0;
theStack = new StackX();
} // end constructor
// -------------------------------------------------------------
public void addVertex(char lab)
{
vertexList[nVerts++] = new Vertex(lab);
}
// -------------------------------------------------------------
public void addEdge(int start, int end)
{
adjMat[start][end] = 1;
adjMat[end][start] = 1;
}
// -------------------------------------------------------------
public void displayVertex(int v)
{
System.out.print(vertexList[v].label);
}
// -------------------------------------------------------------
public void mst() // minimum spanning tree (depth first)
{ // start at 0
vertexList[0].wasVisited = true; // mark it
theStack.push(0); // push it
while( !theStack.isEmpty() ) // until stack empty
{ // get stack top
int currentVertex = theStack.peek();
// get next unvisited neighbor
int v = getAdjUnvisitedVertex(currentVertex);
if(v == -1) // if no more neighbors
theStack.pop(); // pop it away
else // got a neighbor
{
vertexList[v].wasVisited = true; // mark it
theStack.push(v); // push it
// display edge
displayVertex(currentVertex); // from currentV
displayVertex(v); // to v
System.out.print(" ");
}
} // end while(stack not empty)
// stack is empty, so we're done
for(int j=0; j<nVerts; j++) // reset flags
vertexList[j].wasVisited = false;
} // end mst()
// -------------------------------------------------------------
// returns an unvisited vertex adj to v
public int getAdjUnvisitedVertex(int v)
{
for(int j=0; j<nVerts; j++)
if(adjMat[v][j]==1 && vertexList[j].wasVisited==false)
return j;
return -1;
} // end getAdjUnvisitedVert()
// -------------------------------------------------------------
} // end class Graph
class MSTApp
{
public static void main(String[] args)
{
Graph theGraph = new Graph();
theGraph.addVertex('A'); // 0 (start for mst)
theGraph.addVertex('B'); // 1
theGraph.addVertex('C'); // 2
theGraph.addVertex('D'); // 3
theGraph.addVertex('E'); // 4
theGraph.addEdge(0, 1); // AB
theGraph.addEdge(0, 2); // AC
theGraph.addEdge(0, 3); // AD
theGraph.addEdge(0, 4); // AE
theGraph.addEdge(1, 2); // BC
theGraph.addEdge(1, 3); // BD
theGraph.addEdge(1, 4); // BE
theGraph.addEdge(2, 3); // CD
theGraph.addEdge(2, 4); // CE
theGraph.addEdge(3, 4); // DE
System.out.print("Minimum spanning tree: ");
theGraph.mst(); // minimum spanning tree
System.out.println();
} // end main()
} // end class MSTApp
有向图的拓扑顺序
拓扑顺序可以用图来模拟的另一种操作,它可以表示一种情况,即某些项目或者事件必须按照特定的顺序发生。例如:
例子:课程的优先顺序
在高中或者大学,一些课程有先修课程,下图中显示了要得到数学学位必须修的课程。排列顺序随意。为了得到学位,必须完成高级研讨会和比较文学课程,但是如果不选择高等代数和解析几何,就不能修高i及研讨会课程。同样不修英语写作也不能选比较文学课程,同时要修几何,以便学习解析几何,代数和高等代数和解析几何,
有向图
如上图所示,图可以表示这类关系,然而,图需要一种前面没有涉及的特性,就是方向,这种图就叫有向图。在有向图中只能沿着指定的方向移动,图中箭头表示边的方向,在程序中,有向图和无向图的区别是有向图的边在邻接矩阵中只有一项。
有向图的邻接矩阵
上表表示左侧有向图的邻接矩阵,1表示一条边,行标表示从哪里开始,列标表示边到哪里结束。
在上面的无向图中,邻接矩阵上下三角矩阵是对称的,但是有向图中所有行列值都包含必要的信息。
对于有向图,增加边的方法只需要一条语句:
public void addEdge(int start,int end){
adjMat[start][end]=1;
}
拓扑排序
假设存在一个课表,包含了要得到学位必修的课程,按照先后顺序排列,得到学位是列表的最后一项,就可以得到下面的序列:BAEDGCFH,这种方式的排序就称为拓扑排序,实际上还有其他的排序满足优先条件。例如可以先修C和F:CFBAEDGH。
拓扑排序的算法思想有两个步骤是必须的:
- 步骤一:找到一个没有后继的顶点
- 步骤二:从图中删除这个顶点,在列表的前面插入顶点的标记
重复步骤一,步骤二,直到所有顶点都从图中删除,这个时候列表显示的顶点顺序就是拓扑顺序的结果。
环和树
有一种图是拓扑排序算法不能处理的,就是有环图,什么是环?它是一条路径,路径的起点和终点都是同一个顶点。
在左图中B-C-D-B就是一个环(注A-B-C-A不是环,因为C不能到A)
不包含环的图称为树,二叉树,多叉树都是这个意义上的树,然后在图中提出的树比二叉树和多叉树更具有一般意义,因为二叉树和多叉树定死了子节点的最大个数,在图中,树的顶点可以连接任意数量的顶点,只有不存在环即可。
计算无向图中是否存在环,如果有N个顶点的图超过N-1条边,那么它就必定存在环,拓扑排序必须在无环的有向图进行,这样的图被称为无环图,缩写DAG。
拓扑排序源码如下:
// topo.java
// demonstrates topological sorting
// to run this program: C>java TopoApp
class Vertex
{
public char label; // label (e.g. 'A')
// -------------------------------------------------------------
public Vertex(char lab) // constructor
{ label = lab; }
} // end class Vertex
class Graph
{
private final int MAX_VERTS = 20;
private Vertex vertexList[]; // list of vertices
private int adjMat[][]; // adjacency matrix
private int nVerts; // current number of vertices
private char sortedArray[];
// -------------------------------------------------------------
public Graph() // constructor
{
vertexList = new Vertex[MAX_VERTS];
// adjacency matrix
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
for(int j=0; j<MAX_VERTS; j++) // set adjacency
for(int k=0; k<MAX_VERTS; k++) // matrix to 0
adjMat[j][k] = 0;
sortedArray = new char[MAX_VERTS]; // sorted vert labels
} // end constructor
// -------------------------------------------------------------
public void addVertex(char lab)
{
vertexList[nVerts++] = new Vertex(lab);
}
// -------------------------------------------------------------
public void addEdge(int start, int end)
{
adjMat[start][end] = 1;
}
// -------------------------------------------------------------
public void displayVertex(int v)
{
System.out.print(vertexList[v].label);
}
// -------------------------------------------------------------
public void topo() // toplogical sort
{
int orig_nVerts = nVerts; // remember how many verts
while(nVerts > 0) // while vertices remain,
{
// get a vertex with no successors, or -1
int currentVertex = noSuccessors();
if(currentVertex == -1) // must be a cycle
{
System.out.println("ERROR: Graph has cycles");
return;
}
// insert vertex label in sorted array (start at end)
sortedArray[nVerts-1] = vertexList[currentVertex].label;
deleteVertex(currentVertex); // delete vertex
} // end while
// vertices all gone; display sortedArray
System.out.print("Topologically sorted order: ");
for(int j=0; j<orig_nVerts; j++)
System.out.print( sortedArray[j] );
System.out.println("");
} // end topo
// -------------------------------------------------------------
public int noSuccessors() // returns vert with no successors
{ // (or -1 if no such verts)
boolean isEdge; // edge from row to column in adjMat
for(int row=0; row<nVerts; row++) // for each vertex,
{
isEdge = false; // check edges
for(int col=0; col<nVerts; col++)
{
if( adjMat[row][col] > 0 ) // if edge to
{ // another,
isEdge = true;
break; // this vertex
} // has a successor
} // try another
if( !isEdge ) // if no edges,
return row; // has no successors
}
return -1; // no such vertex
} // end noSuccessors()
// -------------------------------------------------------------
public void deleteVertex(int delVert)
{
if(delVert != nVerts-1) // if not last vertex,
{ // delete from vertexList
for(int j=delVert; j<nVerts-1; j++)
vertexList[j] = vertexList[j+1];
// delete row from adjMat
for(int row=delVert; row<nVerts-1; row++)
moveRowUp(row, nVerts);
// delete col from adjMat
for(int col=delVert; col<nVerts-1; col++)
moveColLeft(col, nVerts-1);
}
nVerts--; // one less vertex
} // end deleteVertex
// -------------------------------------------------------------
private void moveRowUp(int row, int length)
{
for(int col=0; col<length; col++)
adjMat[row][col] = adjMat[row+1][col];
}
// -------------------------------------------------------------
private void moveColLeft(int col, int length)
{
for(int row=0; row<length; row++)
adjMat[row][col] = adjMat[row][col+1];
}
// -------------------------------------------------------------
} // end class Graph
class TopoApp
{
public static void main(String[] args)
{
Graph theGraph = new Graph();
theGraph.addVertex('A'); // 0
theGraph.addVertex('B'); // 1
theGraph.addVertex('C'); // 2
theGraph.addVertex('D'); // 3
theGraph.addVertex('E'); // 4
theGraph.addVertex('F'); // 5
theGraph.addVertex('G'); // 6
theGraph.addVertex('H'); // 7
theGraph.addEdge(0, 3); // AD
theGraph.addEdge(0, 4); // AE
theGraph.addEdge(1, 4); // BE
theGraph.addEdge(2, 5); // CF
theGraph.addEdge(3, 6); // DG
theGraph.addEdge(4, 6); // EG
theGraph.addEdge(5, 7); // FH
theGraph.addEdge(6, 7); // GH
theGraph.topo(); // do the sort
} // end main()
} // end class TopoApp
有向图的连通性
有向图的搜索只能从指定 顶点开始,连通性表,就是从每个顶点开始搜索,从第一个字母开始,接下来的字母显示能够到达的顶点(直接或者通过其他顶点)
AC | BACE | C | DEC | EC
由修改过的邻接矩阵表示的图叫做原图的传递闭包。在原来的邻接矩阵中,行号表示边从哪里开始,到哪里结束。(在连通表中排列是类似的)行C和行D的交叉点为1,表示从顶点C到顶点D有一条边,可以一步从一个顶点到另一个顶点,上图的邻接矩阵
| A
| B
| C
| D
| E
|
A
| 0
| 0
| 1
| 0
| 0
|
B
| 1
| 0
| 0
| 0
| 1
|
C
| 0
| 0
| 0
| 0
| 0
|
D
| 0
| 0
| 0
| 0
| 1
|
E
| 0
| 0
| 1
| 0
| 0
|
Warshall算法
如果能从顶点L到M,并且只能从顶点M到N,那么可以从L到N。
已经从两个一步路径得到一个两步路径,邻接矩阵显示了所有的一步路径,所以它可以很好的应用这个规则;
实现warshall算法的一个方法是用三层嵌套,外层循环考察每一行,称它为变量y,它里面的一层循环考察行中的每个单元,它使用变量x,如果单元(x,y)发现1,那么表明有一条边从y到x,这时执行最里层循环;
第三个循环检查列y的每个单元,看是否有边以Y为重点,如果行z列y值为1,说明有一条边从Z到Y,另一条从y到x,就可以说有一条路径从z到x,所以把(z,x)置1,至于算法的细节将作为保留。可以百度到
小结
- 图包含由边连接的顶点
- 图可以表示很多真实世界情况,包括飞机航线,电子线路和工作调度
- 搜索算法以一种系统的方式访问图中的每个顶点,搜索是其他行为的基础
- 两个主要的搜索算法是深度优先搜索(DFS)和广度优先搜索(BFS)
- 深度优先搜索通过栈实现,广度优先搜索通过队列实现
- 最小生成树(MST)包含连接图中所有顶点所需要的最小数量的 边
- 在不带权的图中, 简单修改深度优先搜索算法就可以生成它的最小生成树
- 在有向图中,边有方向(通常用箭头表示)
- 拓扑排序算法创建这样一个顶点的排列列表:如果从A到B有一条边,那么在列表中顶点A在顶点B前面
- 拓扑排序只能在DAG中执行,DAG表示 有向无环图
- 拓扑排序的典型应用是复杂 项目的调度,它包含了任务与任务之间的前后关系
- Warshall算法能够找到任意 顶点和顶点之间是否有连接,不管是通过一步还是任意步得到的