poj1094 拓扑排序

问题重述:
所谓不同值的递增排序的序列,是通过一个小于号的运算符,找出从最小的到最大的元素。例如,有一个有序的序列A,B,C,D。这就意味着,A<B,B<C,C<D。对于这一道问题,我们将为您提供一系列式如A<B的序列,想请您确定序列能不能确定下来
输入
输入包含多个问题的实例。每个实例的第一行都包含两个正整数n和m,第一个值n表明了排序对象的数量,其中2=<n<=26。这些排序对象将是大写字母表里的前n个字母。第二个值m表示有m个类似于A<B的关系。接下来的m行,每行包含一个类似这样的关系A<B。这些字母不会超出字母表的前n个字母的范围。当n=0,m= 0时表示输入结束。
输出
对于每个问题实例,都应输出下面三行之一:
Sorted sequence determined after xxx relations: yyy...y. Sorted sequence cannot be determined. Inconsistency found after xxx relations. 其中xxx表示排序顺序或者出现矛盾在xxx个关系后能够判定,以先到者为准,而yyy ...y是一个递增的序列。
Sample Input
4 6
A<B
A<C
B<C
C<D
B<D
A<B
3 2
A<B
B<A
26 1
A<Z
0 0
Sample Output
Sorted sequence determined after 4 relations: ABCD.
Inconsistency found after 2 relations.
Sorted sequence cannot be determined.

2.解题过程:
分析过程:
当读题目后,对于输出有点模糊。因为考虑所有给出的关系,如果输入的数据能够保证序列只能是三者之一,当然没问题啦,否则会有许多交叉情况。在确保输入没问题的情况下,这三种种情况的判断应该有一个先后的顺序。应该先判断矛盾,一旦出现矛盾,后面的关系就不用考虑;其次再判断是否有序,同样,一旦有序了,后面的关系也不用考虑;最后,当所有关系都考虑完后,仍然无矛盾,就判断无法确定(陷井!刚开始,这里想得不全面)。数据的输入输出很容易解决,但存储与处理就比较难了。这道题是考拓扑排序的应用,而实现拓扑排序由尝过的知识可知,有无前趋的顶点优先的拓扑排序方法,无后继的顶点优先拓扑排序方法和利用深度优先遍历对DAG拓扑排序。而这道题我决定选择比较熟悉的无前趋的顶点优先的拓扑排序方法,其抽象算法描述如下:
Poj1904(G){//优先输出无前趋的顶点
while(G中有入度为0的顶点)do{
从G中选择一个入度为0的顶点v且输出之;
从G中删去v及其所有出边;
}
if(输出的顶点数目<|V(G)|)
//若此条件不成立,则表示所有顶点均已输出,排序成功。
Error("G中存在有向环,排序失败!");
}
此外,这道题有隐含这一信息,每输入一对关系,如果判定有结果,则可以忽略后面输入数据,即使后面输入数据能改变结果,也不用管。所以应该每输入一个关系就去更新当前的图,然后进行一趟拓扑排序。一旦产生结果,再对后面的数据处理下,就可以输出结果。
编程过程:
考虑再三后,决定建立一张图,并且用邻接表去存储,该图存储了顶点和弧结点等相关信息,所有的结点的信息包括入度,出度,顶点内容,是否已访问过。初始化时,只始初化相关顶点的信息,然后每输入一条边,就去更新那张图,并且再拓扑排序一次,然后就判断,
针对三种输出,有以下三种判断:
当找不到入度为0的顶点时,但所有项点还没有访问完,则说明出现了回路,也就是有矛盾。(这里还少了一种情况没有考虑,看了别人的讨论才想到的,下面补充。)
当出现入度为0的项点不唯一时,或者是边都输入完毕但顶点还没访问完,则说明序列无法确定(分析时,没有考虑到入度为0的顶点的唯一性)。
当访问完所有顶点,则说明序列能够确定下来。
做完判断后,无论后面还有没有边再输入,都进行空操作,然后输出判断。
每一次拓扑排序,如果没法做出判断,都要把图的相关信息还原。原先拓扑排序时,只是在顶点上做出入度和访问标志的修改,故现在可以利用弧结点去还原项点的原信息。
花了很长时间,终于把代码写出来了,之后又怕超内存,又把代码优化了,比如对于顶点和图的封装,省去了很多不太必要的东西。这道题写的代码有点多,所以有很些地方很容易因为粗心造成错误,这些也调试了挺多时间。最后把测试数据通过,自己的测试也通过了,但上交时却wrong answer。于是,在这里调试了很多次,很多次都差点都放弃了。接着,上网找了个别人能够ac的代码,运行了一下,和自己的输出作比较,也改正了,但提交后还是wa。最后,在讨论区看到别人的讨论后才知道自己想漏了一种情况,即多个0入度顶点和环可以同时存在,但我判断时,却只考虑0入度顶点不唯一。
做题收获:
做这道题,可以说,心情是大起大落啊。唉,当在测试了很久找出了错误,很是兴奋,却发现还wa,有时却是改对了这边,却把另一边改错了,很是郁闷。很庆幸,因为这道题,我对netbeans的调试渐渐熟练起来(以前没用过调试)。可以说,这道题是修改出来的。刚开始,整个程序的框架都很整洁的,可以算法还没完全细想就开始去编程了,以致后来,不断地发现自己有些地方想多了,有些想少了,然后就不断地修改,使行代码又长,又难看。这点以后得注意下。
程序源码
package middle;


import java.io.BufferedInputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Scanner;


/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
/**poj1094 拓扑排序
* 绝对是呕心沥血之作,太惨了。总是wa,不知道调试了多久,唉。。。
* @author NC
*/
public class Poj1094 {

public static void main(String[] args) {
Scanner scanner = new Scanner(new BufferedInputStream(System.in));
int n;//n个字母
int m;//m对关系
int flag = 0;//输出结果类型的标志
while (true) {
int i = 0;
String printString = null;
String[] s = scanner.nextLine().split(" ");
n = Integer.parseInt(s[0]);
m = Integer.parseInt(s[1]);
if (m == 0 && n == 0) {
break;
}
Graph graph = new Graph(n);
for (i = 0; i < m; i++) {
flag = 0;
s = scanner.nextLine().split("<");//严格输入才不会有异常
char a = s[0].charAt(0);
char b = s[1].charAt(0);
graph.addCurrentNumberOfVertex(a, b);//先添加结点,之后再添加边
graph.addArcNode(a, b);
printString = topologicalOrder(graph, i + 1);
//可能是零入度顶点不唯一,也可能是没有零入度的顶点,也可能能确定序列
//注意,这些只是针对于当前已经输入的边来说,有些点还没有输入
if (printString.startsWith("*")) {//如果有序
if (printString.length() - 1 == graph.getNumberOfVertex()) {
//针对所有顶点,有序
flag = 2;
break;
}
//虽然针对当前顶点是有续的,但输入的边不够的话,还是无法确定
if (i == m - 1) {
printString = "Sorted sequence cannot be determined.";
flag = 1;
break;
}
}
if (printString.equals("Sorted sequence cannot be determined.")) {
//如里无法确定序列
//针对所有输入的边,还是没法确定,因为有可能有很多独立点
if (graph.hasLoop()) {
printString = "Inconsistency found after " + (i + 1) + " relations.";
flag = 1;
break;
} else if (i == m - 1) {
//此时,已经输入了所有的边了
flag = 1;
break;
}
}
if (printString.startsWith("Inconsistency")) {
//如果有矛盾,无论是否已经输入完,就可以下判断
flag = 1;
break;
}
recoverGraph(graph);//每一次拓扑完后都得恢复原图
}
int j;//输出时用的
if (i == m) {
j = i;
} else {
j = i + 1;
i++;
}
while (i < m) {//处理空操作
scanner.nextLine();
i++;
}
if (flag == 1) {
System.out.println(printString);
} else if (flag == 2) {
String ss = "Sorted sequence determined after "
+ j + " relations: "
+ printString.substring(1, printString.length());
System.out.println(ss + ".");
}
}
}

static String topologicalOrder(Graph graph, int m) {
int index = graph.judge0InDegree();
String print = "";
while (index >= 0) {
print += graph.deleteNode(index);//删除并输出顶点
graph.deleteSide(index);//删除与其相关的边,这些边相连的顶点的入度减1
index = graph.judge0InDegree();//获取0入度的顶点的下标
}
if (print.length() < graph.getCurrentNumberOfVertex()) {
String s = null;
if (index == -2)//0入度的顶点不唯一
{
s = "Sorted sequence cannot be determined.";
}
if (index == -1)//没有0入度的顶点
{
s = "Inconsistency found after " + m + " relations.";
}
return s;
} else {
String s = "*";//有序的标志
return s + print;
}
}

static void recoverGraph(Graph graph) {
ArrayList<Vertex> al = graph.getVertexes();
for (Vertex v : al) {
if (v.isVisited()) {
v.setVisited(false);
v.setExisted(true);
LinkedList<Vertex> lk = v.getArcNode();
if (lk != null) {
int in = (lk.getFirst()).getInDegree();
for (Vertex vv : lk) {
graph.getVertexes().get(vv.getData() - 'A').addInDegree();
}
}
}
}
}
}

//顶点
class Vertex {

private boolean existed;
private boolean visited;
private int inDegree;
private char data;
private LinkedList<Vertex> arcNode;

public boolean isExisted() {
return existed;
}

public void setExisted(boolean existed) {
this.existed = existed;
}

public boolean isVisited() {
return visited;
}

public void setVisited(boolean visited) {
this.visited = visited;
}

public Vertex(int inDegree, char data, LinkedList arcNode,
boolean visited, boolean existed) {
this.inDegree = inDegree;
this.data = data;
this.arcNode = arcNode;
this.visited = visited;
this.existed = existed;
}

public LinkedList<Vertex> getArcNode() {
return arcNode;
}

public void setArcNode(LinkedList arcNode) {
this.arcNode = arcNode;
}

public char getData() {
return data;
}

public void setData(char data) {
this.data = data;
}

public int getInDegree() {
return inDegree;
}

public void setInDegree(int inDegree) {
this.inDegree = inDegree;
}

public void addInDegree() {
this.inDegree++;
}

public void reduceInDegree() {
this.inDegree--;
}
}

//用邻接矩阵存储图
class Graph {

//每一趟,所有的顶点A--Z之间
private ArrayList<Vertex> vertexes;
//总顶点数
private int numberOfVertex;
//每输入一条边后产生的总顶点数
private int currentNumberOfVertex;

public Graph(int n) {
this.numberOfVertex = n;
this.vertexes = new ArrayList(n);
this.currentNumberOfVertex = 0;
init();
}

public int getCurrentNumberOfVertex() {
return currentNumberOfVertex;
}

public void addCurrentNumberOfVertex(char a, char b) {
if (!this.vertexes.get(a - 'A').isExisted()) {
this.currentNumberOfVertex++;
this.vertexes.get(a - 'A').setExisted(true);
}
if (!this.vertexes.get(b - 'A').isExisted()) {
this.currentNumberOfVertex++;
this.vertexes.get(b - 'A').setExisted(true);
}
}

public int getNumberOfVertex() {
return numberOfVertex;
}

public void setNumberOfVertex(int numberOfVertex) {
this.numberOfVertex = numberOfVertex;
}

public ArrayList<Vertex> getVertexes() {
return vertexes;
}

public void setVertexes(ArrayList<Vertex> vertexes) {
this.vertexes = vertexes;
}
//初始化图的所有顶点,用ArrayList,查询快一点。

void init() {
for (int i = 0; i < this.numberOfVertex; i++) {
char c = (char) ('A' + i);
Vertex v = new Vertex(0, c, null, false, false);
vertexes.add(v);
}
}

public void addArcNode(char end, char head) {
//重复边不能再增加入度
//a->b 弧尾指向弧头,出度不管
boolean isAdd = true;
LinkedList<Vertex> lk = this.vertexes.get(end - 'A').getArcNode();
if (lk != null) {
for (Vertex v : lk) {
if (v.getData() == head) {
isAdd = false;
}
}
}

if (isAdd) {
this.vertexes.get(head - 'A').addInDegree();//弧头,故入度加1
//新添一个边结点(用弧头来建),
Vertex v = new Vertex(this.vertexes.get(head - 'A').getInDegree(),
head, null, false, true);//这个结点用来还原图的
//如果边接点不存了,只存入度,应该也行的

LinkedList firstArcNode = this.vertexes.get(end - 'A').getArcNode();
if (firstArcNode == null) {
firstArcNode = new LinkedList();
}
firstArcNode.addLast(v);
this.vertexes.get(end - 'A').setArcNode(firstArcNode);//要设进去才行
}
}

public int judge0InDegree() {
int flag = 0;
int index = 0;
for (int i = 0; i < this.numberOfVertex; i++) {
//图的弧结点,我一旦加了进去就不再改变,所以不能直接利用弧结点去判断
if (this.vertexes.get(i).isExisted() == true
&& this.vertexes.get(i).isVisited() == false
&& this.vertexes.get(i).getInDegree() == 0) {
flag++;
index = i;
}
}
switch (flag) {
case 0:
return -1;//表示没有入度为0的顶点
case 1:
return index;//返回入度为0的项点(唯一的)的下标
default:
return -2;//表示零度入度不唯一
}
}

public int getNext0InDegree() {
boolean hasVertex = false;//表示不存在顶点
int i;
//当前有n个点的话,不一定是前n个点,这里错了
for (i = 0; i < this.numberOfVertex; i++) {
if (this.vertexes.get(i).isExisted() == true) {
hasVertex = true;
if (this.vertexes.get(i).isVisited() == false
&& this.vertexes.get(i).getInDegree() == 0) {
return i;//零度顶点的下标
}
}
}
//判断有环,所有点都还没有遍历完,但却找不到0度顶点
if (hasVertex && this.numberOfVertex == i) {
return -1;//有环
} else {
return -2;//没有顶点
}
}

public boolean hasLoop() {
int ind;
int i = this.getCurrentNumberOfVertex();
while (i > 0) {
ind = this.getNext0InDegree();
if (ind == -1) {
//如果有环
return true;
}
if (ind == -2) {
//所有顶点都已经访问过了了
return false;
}
this.deleteNode(ind);
this.deleteSide(ind);
i--;
}
return false;
}

public void deleteSide(int index) {
LinkedList<Vertex> lk = this.vertexes.get(index).getArcNode();
if (lk != null) {
for (Vertex arcV : lk) {
this.vertexes.get(arcV.getData() - 'A').reduceInDegree();
}
}
}

public char deleteNode(int index) {
char c = 0;
this.vertexes.get(index).setExisted(false);//删除顶点
this.vertexes.get(index).setVisited(true);//删掉也表明是访问过的,可以作为需要还原的顶点的标志
c = this.vertexes.get(index).getData();
return c;
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值