14.1 Kahn算法

1 拓扑排序的实用性

  生活中,事物之间总存在着“依赖”关系。而怎么计算依赖关系,这就是拓扑排序的研究内容。我先举个例子(图片来源于网络)在这里插入图片描述
  这和先有鸡还是先有蛋是同样的性质。我们用图算法来表示就是:
在这里插入图片描述
  这一类问题是无法拓扑排序的。只要有环的存在,就无法计算依赖关系。我们再来看另一个过程:
在这里插入图片描述
  这个过程顺序就明确了,先赚钱,再买房、买车、准备彩礼,最后结婚。上述的婚姻流程图由于不存在环,所以可以计算执行顺序,也就是说,可以计算拓扑排序。此外,在计算依赖的应用中,比如maven、工作流、决策引擎等应用中,拓扑排序都十分重要。

2 入度表算法(也叫BFS算法或Kahn算法)

  Kahn算法又叫做入度表算法。这个算法是要找出入度为0的点。入度为0,就是没有有任何节点指向它。也就是只做from,不做to的节点。用大白话说,就是第一步要执行的节点。当处理完了入度为0的点,之后在将这些点删除,于是就有了新的入度为0的点,然后再执行。直到所有点处理完毕,拓扑排序结束。以上述的婚姻流程图为例子:
  Step1, 处理入度为0的点:
在这里插入图片描述
  Step2, 删除为0的点:
在这里插入图片描述
  Step3, 再处理入度为0的点:
在这里插入图片描述
  Step4, 再删除入度为0的点:
在这里插入图片描述
  后续的步骤我就不画图了。所以拓扑排序是:赚钱、买房、买车、彩礼、结婚。可以看出来这是一种BFS搜索算法,所以也叫BFS拓扑排序

3 Java实现

  可以使用一个hashmap来表示入度表。因为hashmap key可以为null。
  所以可以这样写入度表:

public class DagJoinTable extends HashMap<Integer, List<Integer>> {

    public void add(Integer key, Integer value) {
        List<Integer> integers = this.computeIfAbsent(key, k -> new ArrayList<>());
        // 这里需要避免重复添加
        if (!integers.contains(value)) {
            integers.add(value);
        }
    }

    public void remove(Integer key, Integer value) {
        final List<Integer> integers = get(key);
        if (integers != null && !integers.isEmpty()) {
            while (integers.remove(value)) {
                // 循环删除
            }
        }
    }
}

  迭代器的代码:

public class DagIterator<E> implements Iterator<E> {
    /**
     * key 为 from value 为 to
     */
    private DagJoinTable joinTable;
    private DirectedAcyclicGraph<E> graph;

    /**
     * 假设无环图
     * 逆连接表为
     * A -> B
     * B -> C
     * C -> D
     * 虚构 NULL -> A
     *
     * @param graph
     */
    public DagIterator(DirectedAcyclicGraph<E> graph) {
        this.joinTable = new DagJoinTable();
        // build逆连接表, 逆连接表里null key的创建特别难啊
        //  A -> B 这个边,A 放入null KEY B 移出null key

        final List<? extends Edge>[] edges = graph.edges;
        for (List<? extends Edge> edgePerVertex : edges) {
            // 每个点的边
            for (Edge edge : edgePerVertex) {
                final int from = edge.getFrom();
                joinTable.add(null, from);
            }
        }
        for (List<? extends Edge> edgePerVertex : edges) {
            for (Edge edge : edgePerVertex) {
                final int from = edge.getFrom();
                final int to = edge.getTo();
                joinTable.add(from, to);
                joinTable.remove(null, to);
            }
        }
        this.graph = graph;
    }

    @Override
    public boolean hasNext() {
        final List<Integer> integers = joinTable.get(null);
        return integers != null && !integers.isEmpty();
    }

    /**
     * NULL -> A
     * A -> B,C
     * B -> C
     * C -> D
     * 如果移出A 后
     * A 所指向的B 放入NULL KEY
     * 变成
     * NULL -> B, C
     * B -> C
     * C -> D
     * 这里对于C,因为C存在B->C,所以不能有null -> C
     * 所以算法是
     *
     * @return
     */
    @Override
    public E next() {
        final List<Integer> integers = joinTable.get(null);
        if (integers == null || integers.isEmpty()) {
            throw new NoSuchElementException();
        }
        final Integer remove = integers.remove(0);
        // 移除之后
        final List<Integer> candidates = joinTable.get(remove);
        if (candidates != null) {
            for (Integer candidate : candidates) {
                joinTable.add(null, candidate);
            }

            for (Integer candidate : candidates) {
                final List<Integer> candidateValues = joinTable.get(candidate);
                if (candidateValues != null) {
                    for (Integer candidateValue : candidateValues) {
                        joinTable.remove(null, candidateValue);
                    }
                }
            }
        }

        if (integers.isEmpty()) {
            joinTable.remove(null);
        }
        return graph.vertices[remove];
    }
}

4 Python实现

  Python的实现,我没有使用hashmap,而是直接用一个布尔数组连存储入度为0的元素,然后使用双色BFS进行拓扑排序。为了简单,我图的实现是用的邻接矩阵。

# _*_ coding:utf-8 _*_

class UnweightedGraph:
    def __init__(self):
        self.__vertices = []
        self.__edges = []

    @property
    def vertices(self):
        return self.__vertices

    @vertices.setter
    def vertices(self, value):
        self.__vertices = value

    @property
    def edges(self):
        return self.__edges

    @edges.setter
    def edges(self, value):
        self.__edges = value

    white = 0
    gray = 1
    black = 2

    def topological_sort(self):
        result = []
        # 0入度元素
        s = [True for _ in self.__vertices]
        for edges in self.__edges:
            for e in edges:
                if e and i in vertices:
                    circle_exists = False
        # 简单BFS
        visited = [False for _ in self.__vertices]
        queue = [i for i, e in enumerate(s) if e]
        while len(queue) > 0:
            e = queue.pop()
            # 元素
            if not visited[e]:
                for edge in self.__edges[e]:
                    if not visited[edge]:
                        queue.append(edge)
            result.append(self.__vertices[e])
            visited[e] = True
        return result

  测试数据:

# _*_ coding:utf-8 _*_


class UnweightedGraph:
    def __init__(self):
        self.__vertices = []
        self.__edges = []

    @property
    def vertices(self):
        return self.__vertices

    @vertices.setter
    def vertices(self, value):
        self.__vertices = value

    @property
    def edges(self):
        return self.__edges

    @edges.setter
    def edges(self, value):
        self.__edges = value

    white = 0
    gray = 1
    black = 2

    def topological_sort(self):
        result = []
        # copy一份点集合,存索引
        vertices = [i for i, _ in enumerate(self.__vertices)]
        # 当集合内还有元素
        while len(vertices) > 0:
            # 0入度元素
            zero_in_degrees = [True for _ in self.__vertices]
            for vertex in vertices:
                for e in self.__edges[vertex]:
                    if zero_in_degrees[e]:
                        zero_in_degrees[e] = False
            # 如果没有0入度表元素则表示存在环
            circle_exists = True
            for e in zero_in_degrees:
                if e:
                    circle_exists = False
            if circle_exists:
                raise RuntimeError('存在环')
            # 按顺序加入result
            for i, e in enumerate(zero_in_degrees):
                if e and i in vertices:
                    # 删除元素
                    vertices.remove(i)
                    result.append(self.__vertices[i])
        return result

  测试结果:

['赚钱', '彩礼', '结婚', '买车', '买房']
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

醒过来摸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值