有向图的拓扑排序算法-从部分字符的序对恢复字符串

这是一道Codewar上的题,地址为

https://www.codewars.com/kata/53f40dff5f9d31b813000774

大致题目为从一个字符串中按顺序抽取一些单字符,给出一些这样的排列,然后让你恢复这个隐藏的字符串,给出的排列是能确定一个唯一的字符串的。

如给出

      {'t','u','p'},
      {'w','h','i'},
      {'t','s','u'},
      {'a','t','s'},
      {'h','a','p'},
      {'t','i','s'},
      {'w','h','s'}

这样一些序对,然后恢复出字符串whatisup


 

可以把给出的每个序对看成一些约束,从而构建一个有向图,如给出{'t','u','p'},则约束为字符串中t必须在u,p前面,u必须在p前面,从而构建三个顶点,如果a字符必须要在b字符前出现,则从a连接一条到b的边。

知道这道题考的是什么时候就很简单了,接下来直接套用图的拓扑排序算法就可以了。

拓扑排序在数学中可以看成一种给出偏序关系然后输出任意一个全序关系,主要有两种算法,一种是一直删除没有入度的节点,一种是一直删除没有出度的顶点。

两者大同小异,原理也很简单,如果一个节点没有入度,那么他排在第一个当然没有什么问题,因为他前面没有东西,然后这样递归思考,每次删掉没有入度的节点以及他往外连接的边,这样一直运行直到没有节点,就给出了一个可能的全序排列,或者该图存在一个环则无法给出答案。

接下来上代码

/**
 * 一个简易的代表有向图节点的类
 */
class LetterNode {
    public Character letter;
    //所有的连接到该节点的节点
    public LinkedList parents = new LinkedList<>();
    //所有的从该节点连出去的节点
    public LinkedList childs = new LinkedList<>();

    public LetterNode(Character letter) {
        this.letter = letter;
    }

    public void linkTo(LetterNode letterNode) {
        letterNode.parents.add(this);
        this.childs.add(letterNode);
    }
}
public static String recoverSecret(char[][] triplets) {

    //从输入的三元组建立有向图,同时为了快速从字符查询到对应的节点,使用hashmap保存字符到节点的映射
    HashMap map = new HashMap<>();
    for (int y = 0; y < triplets.length; y++) {
        for (int x = 0; x < triplets[y].length; x++) {
            Character c = triplets[y][x];
            //如果该字符表示的节点不存在则创建他
            map.putIfAbsent(c, new LetterNode(c));
        }
        //按照三元组的顺序进行连接,第一个节点连到第二个节点,第二个节点连到第三个节点,同时因为传递关系这里已经隐含了第一个节点要排在第三个节点前面了
        map.get(triplets[y][0]).linkTo(map.get(triplets[y][1]));
        map.get(triplets[y][1]).linkTo(map.get(triplets[y][2]));
    }

    //用StringBuilder来连接单字符返回需要输出的字符串
    StringBuilder builder = new StringBuilder();
    //循环直到删完所有的节点
    while (map.keySet().size() > 0)
        for (Character character : map.keySet()) {
            LetterNode node = map.get(character);
            //找到一个没有parent,即没有入度的顶点
            if (node.parents.size() == 0) {
                //追加到需要输出的字符串后面
                builder.append(node.letter);
                //删除他到所有从这出去的顶点的链接
                node.childs.forEach(i -> {
                    i.parents.remove(node);
                });
                //删除这个顶点
                map.remove(character);
                break;
            }
        }
    return builder.toString();

}

 

算法过程已经注释的很清楚了,由于题目已经确定这些三元组会确定一个且只有一个隐藏的字符串,所以可以不用做一些找不到没有入度的顶点,图出现环怎么办之类的处理。

说实话第一眼没有把他和图的拓扑排序联系起来,这就是为什么我觉得不能光看一些算法书不上手实际做一些题目的原因,你没法把一个基础的算法和实际结合起来,就没有办法在看到实际应用时把这个东西套进去。

 

你可能感兴趣的:(算法与数据结构,算法,拓扑排序,图论)