拓扑排序
先穿袜子才能穿鞋子:袜子->鞋子
秋裤->裤子 裤子->鞋子 秋衣->外套
整体穿衣顺序:
秋衣->外套->秋裤->裤子->袜子->鞋子 或
秋裤->裤子->袜子->鞋子-->秋衣->外套
问题 --> 数据结构 --> 算法
拓扑排序算法:
如果s需要先于t执行,那就添加一条s指向t的边。
1 Kahn算法:如果某个节点的入度为零,则说明没有节点需要先于该节点执行,可以直接执行,并删除该节点。所以解决步骤为:
1)统计每个节点及其入度,即边指向该节点的次数
2)删除入度为0的节点,同时删除该节点导致的入度
3)如节点1和2关系为:1<->2,两节点入度均为1,不存在入度为0的节点。
2 DFS算法的变形解决
假设存在,s->t,a->t;需要的拓扑排序结果是sat或ast
正常的DFS是先获取顶点,然后根据顶点遍历指向的节点。即访问顺序是sta或ats。
所以我们采用遍历逆邻接表的方式:
1)s->t,a->t 转化为 t->s,t->a
2) 访问的方式也需要修改,一定是先访问节点t指向的节点s和a,然后再访问t
此时的结果是sat或ast
好麻烦,还是采用kahn算法吧。根据入度,很清晰,同时解决环的问题
邻接表:顶点值,顶点对应的链表中存储顶点指向的点的值
逆邻接表:顶点值,顶点对应的链表中存储指向该顶点的值
判断图是否有环
首先我们介绍一个对于无向图和有向图通用的算法,先讲算法思路:
1.统计各个图中各个点的入度数(能够到达这个点的点的数量)。
2.然后找出入度数为0的点(无向图找入度数为1的点)。
3.删除入度数为0的点,将其边也删除。
4.重复2,直到所有点入度都为0,则为无环图,如果找不到入度为0的点,则为有环图。
参考:https://www.cnblogs.com/cmai/p/7517729.html
public class TopologicalSort {
public static void main(String[] args) {
LinkedList<Integer>[] list = new LinkedList[7];
LinkedList list0 = new LinkedList<>();
LinkedList list1 = new LinkedList<>();
list0.add(1);
list1.add(2);
LinkedList list2 = new LinkedList<>();
LinkedList list3 = new LinkedList<>();
list2.add(3);
list2.add(6);
LinkedList list4 = new LinkedList<>();
LinkedList list5 = new LinkedList<>();
list3.add(5);
list4.add(5);
LinkedList list6 = new LinkedList<>();
list6.add(4);
list[0]=list0;
list[1]=list1;
list[2]=list2;
list[3]=list3;
list[4]=list4;
list[5]=list5;
list[6]=list6;
Graphic graph = new Graphic(7, list);
kahn(graph);
boolean[] vistied = new boolean[graph.getNodeNum()];
reverseGraph(graph);
dfsGraph(graph, vistied);
}
// 对图进行拓扑排序,同时判断是否有环
// 1 统计每个节点的入度,即边指向该节点的次数
// 2 删除入度为0的节点,同时删除该节点导致的入度
// 3 如果存在有节点,而且所有节点入度不为0,那一定是有环,
// 如节点1和2关系为:1<->2,两节点入度均为1,不存在入度为0的节点。
public static void kahn(Graphic g){
int num = g.getNodeNum();
LinkedList<Integer>[] list = g.getList();
// 用于存储节点入度,index为节点的值,value为入度
int[] enter = new int[num];
// 统计节点入度
// 某个节点的指向
LinkedList<Integer> nodeList ;
LinkedList<Integer> nodes = new LinkedList();
for(int i=0; i<num; i++){
// 记录所有顶点
nodes.addLast(i);
nodeList = list[i];
for(int j=0; j<nodeList.size(); j++){
enter[nodeList.get(j)]++;
}
}
// 假设有环,每次循环,如果存在入度为0的节点,即有节点没有被指向,说明整体不是环
boolean circle = true;
// 删除入度为0的节点
while(nodes.size()>0){
circle = true;
// 遍历入度为0的直接移除
for(int i =0; i<num; i++){
if(enter[i] == 0){
// 如果存在入度为0的节点,即有节点没有被指向,说明整体不是环
circle = false;
System.out.print("-->"+i);
nodes.remove(new Integer(i));
nodeList = list[i];
// 处理被移除节点
for(int j=0; j<nodeList.size(); j++){
enter[nodeList.get(j)]--;
}
// 这里代表移除
enter[i]--;
}
}
if(circle){
System.out.println("有环,失败。。。。");
break;
}
}
System.out.println();
}
// 与一般的深度搜索不同,这里要求要打印当前节点,必须先打印该节点指向的节点
public static void dfsGraph(Graphic g, boolean[] vistied) {
int nodeNum = g.getNodeNum();
LinkedList[] list = g.getList();
// 所有图的顶点都需要访问到,因为可能存在节点独立的场景
for(int i=0; i<nodeNum; i++){
// i节点可能因为访问i之前的节点已经被访问到,所以需要判断
if(!vistied[i]){
dfsNode(i, list, vistied);
}
}
}
// 指向顶点指向的所有节点都要提前访问
private static void dfsNode(int node, LinkedList<Integer>[] list, boolean[] vistied) {
// 指向node的所有节点都需要提前访问,所以这里遍历所有
for(int j=0; j<list[node].size(); j++){
int temp = list[node].get(j);
if(vistied[temp] == true){
continue;
}
dfsNode(temp, list, vistied);
}
vistied[node] =true;
System.out.print("-->"+node);
}
/**
* desc:从邻接表转化为逆向邻接表,即原本1—>2,执行后:2->1
*/
public static void reverseGraph(Graphic g){
LinkedList<Integer>[] list = g.getList();
int nodeNum = g.getNodeNum();
LinkedList<Integer>[] newLists = new LinkedList[nodeNum];
for(int i=0; i<nodeNum; i++){
newLists[i] = new LinkedList<>();
}
for(int i=0; i<nodeNum; i++){
LinkedList<Integer> integers = list[i];
for(int j=0; j<integers.size(); j++){
newLists[integers.get(j)].add(i);
}
}
g.setList(newLists);
}
static class Graphic{
int nodeNum;
LinkedList<Integer>[] list;
public Graphic(int nodeNum, LinkedList<Integer>[] list){
this.nodeNum = nodeNum;
this.list = list;
}
public int getNodeNum() {
return nodeNum;
}
public void setNodeNum(int nodeNum) {
this.nodeNum = nodeNum;
}
public LinkedList[] getList() {
return list;
}
public void setList(LinkedList[] list) {
this.list = list;
}
}
}