文章目录
- 有向图
- 拓扑排序
- 实现思路
- Java代码实现
- 封装顶点的类
- 封装用于topo排序的队列的类
- 封装图的类
- 测试图的类
有向图
有向图在计算机中有广泛的应用。有向图的顶点之间的联系是描述现实世界的有利工具,如 计算机的任务调度系统,根据多任务之间的先后关联需要给出任务的执行顺序,拓扑排序就是可以得到这一顺序的算法。
拓扑排序
给定一幅有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素(或者说明无法做到这一点)。
注意项:还是以任务调度系统为例,假设一个任务x必须在任务y之前完成,任务y必须在任务z之前完成,任务z又必须在任务x之前完成,这样的问题肯定是无解的。也就是说,拓扑排序能得出结果的前提是图必须是有向无环图(Directed Acyclic Graph,DAG),即一幅不含有环路的有向图。
实现思路
- 遍历图中所有的顶点,将入度为0的顶点如队列(如果没有这样的顶点,就说明存在环)。
- 从队列中出一个出一个顶点,打印该顶点,并更新该顶点指向的邻接节点的入度(减1)。如果邻接节点的入度更新之后变为了0,就将邻接节点放入队列。
- 循环步骤2,直至队列为空。
Java代码实现
封装顶点的类
package graph;
/**
* 顶点的类
*/
public class Vertex {
public char label;
public boolean isVisited;
public int degree;//拓扑排序入度
public Vertex(char label){
this.label = label;
isVisited = false;//默认没有被访问
degree = 0;
}
}
封装用于topo排序的队列的类
package graph;
/**
* 实现广度优先搜索或者topo排序的队列
*/
public class Queue {
private final int SIZE = 20; //final 定义的变量只能在定义的时候初始化一次,以后不能再做初始化操作。不能再被改变。
private int[] queArray;//存放数据的数组
private int front;//队头
private int rear;//队尾
public Queue(){
queArray = new int[SIZE];
front = 0;
rear = -1;
}
public void insert(int j){
if (rear == SIZE-1){
rear = -1;
}
queArray[++rear] = j;
}
public int remove(){
int temp = queArray[front++];
if (front == SIZE){
front = 0;
}
return temp;
}
public boolean isEmpty(){
return (rear+1 == front || front+SIZE-1 == rear);
}
}
封装图的类
package graph;
public class GraphTopo {
//图的基本属性
private Vertex[] vertexList;//保存顶点的数组
private int[][] adjMat;//邻接矩阵
private int nVerts;//图中存在的节点数量
private final int MAX_VERTS = 20;//初始化一个图中的顶点最大的个数
private Queue topoQueue;//用于拓扑排序的队列
//构造方法
public GraphTopo(){
vertexList = new Vertex[MAX_VERTS];
adjMat = new int[MAX_VERTS][MAX_VERTS];
for (int i=0;i<MAX_VERTS;i++){
for (int j=0;j<MAX_VERTS;j++){
adjMat[i][j] = 0;
}
}
nVerts = 0;//初始状态,图内没有节点
topoQueue = new Queue();
}
//向图中插入新的顶点
public void insert(char label){
vertexList[nVerts++] = new Vertex(label);
}
//更新边,设置顶点的连接关系
public void addEdge(int start, int end){
//有向图只更新一个部分
adjMat[start][end] = 1;
}
//打印指定的顶点中的label
public void displayVertex(int v){
System.out.print(vertexList[v].label);
}
/**
* 实现有向图的拓扑排序
*/
public void topo(){
//计算各个顶点的入度
setDegree();
//1.找到图中入度为0的顶点
int[] zeroDegreeNodes = getZeroDegree();
for (int i:zeroDegreeNodes){
if (i==-1){
//表示数组的该位置没有元素
break;
}
//顶点的序号放入队列
topoQueue.insert(i);
}
int popNodeIndex;
int printNum = 0;//记录已经打印的接地那个数
char[] printNode = new char[nVerts];
while (!topoQueue.isEmpty()){
//从队列中出一个出一个顶点,打印该顶点
popNodeIndex = topoQueue.remove();
// System.out.print(vertexList[popNodeIndex].label + " ");
printNode[printNum++]=vertexList[popNodeIndex].label;
//更新该顶点指向的邻接节点的入度(减1)
for (int i=0;i<nVerts;i++){
if (adjMat[popNodeIndex][i] == 1){
vertexList[i].degree--;
if (vertexList[i].degree == 0){
//如果邻接节点的入度更新之后变为了0,就将邻接节点放入队列。
topoQueue.insert(i);
}
}
}
}
if (printNum < nVerts){
System.out.println("有向图中存在环,无法进行拓扑排序");
}else {
//打印拓扑排序结果
for(char j:printNode){
System.out.print(j+" ");
}
System.out.println();
}
}
//设置各节点的入度
public void setDegree(){
int i;
for (i=0;i<nVerts;i++){
for (int j=0;j<nVerts;j++){
if (adjMat[i][j] == 1){
vertexList[j].degree++;
}
}
}
}
//找到图中入度为0的顶点
public int[] getZeroDegree(){
int[] zeroDegreeNodes = new int[nVerts];
int zeroNum = 0;
int i;
for (i=0;i<nVerts;i++){
if (vertexList[i].degree == 0){
zeroDegreeNodes[zeroNum++]=i;
}else {
zeroDegreeNodes[zeroNum++]=-1;//表示该位置没有数据
}
}
return zeroDegreeNodes;
}
}
测试图的类
package graph;
public class GraphMain {
public static void main(String[] args) {
GraphTopo graph = new GraphTopo();
graph.insert('A');//0
graph.insert('B');//1
graph.insert('C');//2
graph.insert('D');//3
graph.insert('E');//4
graph.insert('F');//5
graph.addEdge(0,1);
graph.addEdge(1,2);
graph.addEdge(2,3);
graph.addEdge(3,4);
graph.addEdge(4,5);
graph.addEdge(0,5);
graph.addEdge(1,5);
graph.addEdge(1,3);
graph.addEdge(3,5);
// graph.insert('0');
// graph.insert('1');
// graph.insert('2');
// graph.insert('3');
// graph.insert('4');
// graph.insert('5');
//
// graph.addEdge(0,2);
// graph.addEdge(0,3);
// graph.addEdge(1,3);
// graph.addEdge(2,4);
// graph.addEdge(3,4);
// graph.addEdge(4,5);
graph.topo();
}
}