Java 有向图排序算法

拓扑排序 

      对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列
 

有向图能被拓扑排序的充要条件就是它必须是一个有向无环图(DAG:Directed Acyclic Graph)。

实现算法

卡恩算法:卡恩于1962年提出的算法。

Kahn算法维基百科上关于Kahn算法的伪码描述:

L ← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edges
while S is non-empty do
    remove a node n from S
    add n to tail of L
    for each node m with an edge e from n to m do
        remove edge e from the graph
        if m has no other incoming edges then
            insert m into S
if graph has edges then
    return error (graph has at least one cycle)
else
    return L (a topologically sorted order)

此算法关键在于需要维护一个入度为0的顶点的集合:

假设L是存放结果的列表,先找到那些入度为零的节点,把这些节点放到L中,因为这些节点没有任何的父节点。
然后把与这些节点相连的边从图中去掉,再寻找图中的入度为零的节点。对于新找到的这些入度为零的节点来说,
他们的父节点已经都在L中了,所以也可以放入L。重复上述操作,直到找不到入度为零的节点。
如果此时L中的元素个数和节点总数相同,说明排序完成;如果L中的元素个数和节点总数不同,说明原图中存在环,无法进行拓扑排序。

  1. 如图,初始化后,S集合中存在node A 和 node C,因为A和C的入度为0
  2. 取出S中的node A,将其存入有序的集合L,然后循环遍历由node A引出的边
  3. 移除A引出的边AB,并可以得到node B ,如果node B的入度在减去AB边之后为0,那么也将node B放到入度为0的集合S中
  4. 继续从S中取出node C,重复前三步操作~~~
  5. 当集合S为空之后,检查图中是否还存在任何边,如果存在的话,说明图中至少存在一条环路。不存在的话则返回结果L,此L中的顺序就是对图进行拓扑排序的结果。

Node节点对象:用于记录节点和边数量

NodeGroup对象:用于记录两节点关系


public class Node {
    private Object source;
    private int indegreeNum = 0;

    public Node(Object source) {
        this.source = source;
    }

    public Object getSource() {
        return source;
    }

    public int getIndegreeNum() {
        return indegreeNum;
    }

    public void addIndegreeNum() {
        indegreeNum++;
    }

    public void subIndegreeNum() {
        indegreeNum--;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((source == null) ? 0 : source.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Node other = (Node) obj;
        if (source == null) {
            if (other.source != null)
                return false;
        } else if (!source.equals(other.source))
            return false;
        return true;
    }


}


public class NodeGroup {

	private Node startNode; // A->B : A=startNode B=endNode
	private Node endNode;
	private String value;

	public NodeGroup(Node startNode, Node endNode) {
		this.startNode = startNode;
		this.endNode = endNode;
	}

	public NodeGroup(Node startNode, Node endNode, String value) {
		this.startNode = startNode;
		this.endNode = endNode;
		this.value = value;
	}

	public boolean containNode(Node node) {
		return startNode.equals(node) || endNode.equals(node);
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof NodeGroup) {
			NodeGroup group = (NodeGroup) obj;
			return this.getStartNode().equals(group.getStartNode())
					&& this.getEndNode().equals(group.getEndNode());
		}
		return false;
	}

	@Override
	public int hashCode() {
		return super.hashCode();
	}

	public Node getStartNode() {
		return startNode;
	}

	public void setStartNode(Node startNode) {
		this.startNode = startNode;
	}

	public Node getEndNode() {
		return endNode;
	}

	public void setEndNode(Node endNode) {
		this.endNode = endNode;
	}

	public String getValue() {
		return value;
	}

	public void setValue(String value) {
		this.value = value;
	}

}

 Graph图对象,主要是通过addNode方法实现将节点添加进图中

public class Graph {

    private Set<Node> graphNodes = new HashSet<Node>();
    private Map<Node, Set<Node>> nodeEdge = new HashMap<Node, Set<Node>>();
    private List<NodeGroup> nodeGroups = new ArrayList<NodeGroup>();

    public NodeGroup findNodeGroup(NodeGroup group) {
        for (NodeGroup nodeGroup : nodeGroups) {
            if (nodeGroup.equals(group)) {
                return nodeGroup;
            }
        }
        return null;
    }

    public boolean addNode(NodeGroup nodeGroup) {

        Node startNode = addToGraphNodes(nodeGroup.getStartNode());
        Node endNode = addToGraphNodes(nodeGroup.getEndNode());

        if (nodeEdge.containsKey(startNode)
                && nodeEdge.get(startNode).contains(endNode)) {
            return false;
        } else {
            addNodeEdge(startNode, endNode);
            addNodeGroup(nodeGroup);
            return true;
        }
    }

    private void addNodeGroup(NodeGroup nodeGroup) {
        NodeGroup findWeight = findNodeGroup(nodeGroup);
        if (findWeight == null) {
            nodeGroups.add(nodeGroup);
        }
    }

    private void addNodeEdge(Node startNode, Node endNode) {
        if (nodeEdge.containsKey(startNode)) {
            nodeEdge.get(startNode).add(endNode);
        } else {
            Set<Node> temp = new HashSet<Node>();
            temp.add(endNode);
            nodeEdge.put(startNode, temp);
        }
        endNode.addIndegreeNum();
    }

    private Node addToGraphNodes(Node node) {
        Node graphNode = findGraphNode(node);
        if (graphNode != null) {
            return graphNode;
        } else {
            graphNodes.add(node);
            return node;
        }
    }

    private Node findGraphNode(Node node) {
        for (Node graphNode : graphNodes) {
            if (graphNode.equals(node)) {
                return graphNode;
            }
        }
        return null;
    }

    public Set<Node> getGraphNodes() {
        return graphNodes;
    }

    public Map<Node, Set<Node>> getNodeEdge() {
        return nodeEdge;
    }

    public boolean contain(Node node) {
        return graphNodes.contains(node);
    }

    public boolean isEmptyNode() {
        return graphNodes.isEmpty();
    }

}

 凯恩算法实现,通过Graph初始化对象,然后通过topoSort实现排序,getResult得到排序后的结果

public class KahnTopo {
    private List<Node> sortResult; // 用来存储结果集
    private Queue<Node> zeroIndegreeNodes; // 用来存储入度为0的顶点
    private Graph graph;

    // 构造函数,初始化
    public KahnTopo(Graph di) {
        this.graph = di;
        this.sortResult = new ArrayList<Node>();
        this.zeroIndegreeNodes = new LinkedList<Node>();
        // 对入度为0的集合进行初始化
        for (Node node : graph.getGraphNodes()) {
            if (node.getIndegreeNum() == 0) {
                this.zeroIndegreeNodes.add(node);
            }
        }
    }

    // 拓扑排序处理过程
    public void topoSort() {
        while (!zeroIndegreeNodes.isEmpty()) {
            Node zeroIndegreeNode = zeroIndegreeNodes.poll();
            // 将当前顶点添加到结果集中
            sortResult.add(zeroIndegreeNode);

            if (graph.getNodeEdge().keySet().isEmpty()) {
                sortResult.addAll(zeroIndegreeNodes);
                return;
            }

            // 遍历由node引出的所有边
            Set<Node> nodes = graph.getNodeEdge().get(zeroIndegreeNode);
            if (nodes != null) {
                for (Node node : nodes) {
                    node.subIndegreeNum();// 将该边从图中移除,通过减少边的数量来表示
                    if (0 == node.getIndegreeNum()) {
                        zeroIndegreeNodes.add(node);// 如果入度为0,那么加入入度为0的集合

                    }
                }

            }

            graph.getGraphNodes().remove(zeroIndegreeNode);
            graph.getNodeEdge().remove(zeroIndegreeNode);
        }

        // 如果此时图中还存在边,那么说明图中含有环路
        if (!graph.getGraphNodes().isEmpty()) {
            throw new IllegalArgumentException("Has Cycle !");
        }
    }

    public List<Node> getResult() {
        return sortResult;
    }

}

基于DFS的拓扑排序

借助深度优先遍历来实现拓扑排序, 维基百科上的伪码:

L ← Empty list that will contain the sorted nodes
S ← Set of all nodes with no outgoing edges
for each node n in S do
    visit(n) 
function visit(node n)
    if n has not been visited yet then
        mark n as visited
        for each node m with an edgefrom m to ndo
            visit(m)
        add n to L
 

DFS的实现使用递归实现。需要注意的是:add n to L,将顶点添加到结果List中的时机是在visit方法即将退出之时。

添加顶点到集合中的时机是在visit方法即将退出之时,而visit方法本身是个递归方法,只要当前顶点还存在边指向其它任何顶点,它就会递归调用visit方法,而不会退出。因此,退出visit方法,意味着当前顶点没有指向其它顶点的边了,即当前顶点是一条路径上的最后一个顶点。

上一篇:Nacos 简介与应用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

=PNZ=BeijingL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值