javascript中的yield&&深度优先遍历

279 阅读3分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战摘要:本文主要讲了用javascript中的生成器去实现随机生成图的深度优先遍历,来判断该图是否连通。

实现代码如下,代码1-12行,声明了一个Node类,Node有两个属性id和它的相邻点集合neighbors,一个方法为connect(node),功能为使当前节点this和参数node节点连通。代码13-57行,声明了一个随机图RandomGraph类,它的构造函数为生成一个大小为size的随机图,主要步骤为:先生成一个size大小的Node对象集合,然后集合里的node两两比对,当随机生成的0-1直接的值小于1/size时,则当前的两个node对象直接连接,这样就生成了一个大小为size的随机图。代码31-36行,对于刚才生成的随机图中的每个节点,打印出与它直连的节点,输出可参考输出结果62-67行。代码38-57,则为判断生成的随机图是否连通,采用的算法为深度优先遍历算法,主要步骤为:先声明一个遍历过节点的集合visitedNodes,然后遍历-调用生成器函数产生的-迭代器,传进去的参数为只有随机图的第一个节点的数组,到生成器函数里,遍历传进去的数组,如果当前节点没有在visitedNodes里,则先返回此节点,再依次往深一次遍历它的相临点给生成器函数,依次递归直到最深处的节点都已遍历过,它的打印结果可参考输出结果68-90,可以看到visitedNodes集合的变化情况符合深度优先遍历。isConnected()函数最后返回为visitedNodes集合的大小和随机生成图集合的大小是否相等来判断该图是否为连通图,如果相等,表明所有点都遍历到了是连通的,如果不相等,则表明该图不连通。(我代码中随机生成的图明显不是连通的)

class Node {
    constructor(id) {
        this.id = id;
        this.neighbors = new Set();
    }
    connect(node) {
        if (node !== this) {
            this.neighbors.add(node);
            node.neighbors.add(this);
        }
    }
}
class RandomGraph {
    constructor(size) {
        this.nodes = new Set();
        // 创建节点
        for (let i = 0; i < size; ++i) {
            this.nodes.add(new Node(i));
        }
        // 随机连接节点
        const threshold = 1 / size;
        for (const x of this.nodes) {
            for (const y of this.nodes) {
                if (Math.random() < threshold) {
                    x.connect(y);
                }
            }
        }
    }
    // 这个方法仅用于调试
    print() {
        for (const node of this.nodes) {
            const ids = [...node.neighbors].map((n) => n.id).join(',');
            console.log(`${node.id}: ${ids}`);
        }
    }

    isConnected() {
        const visitedNodes = new Set();
        function* traverse(nodes) {
            for (const node of nodes) {
                if (!visitedNodes.has(node)) {
                    yield node;
                    yield* traverse(node.neighbors);
                }
            }
        }
        // 取得集合中的第一个节点
        const firstNode = this.nodes[Symbol.iterator]().next().value;
        // 使用递归生成器迭代每个节点
        for (const node of traverse([firstNode])) {
            visitedNodes.add(node);
            console.log(visitedNodes);
        }
        return visitedNodes.size === this.nodes.size;
    }
}
const g = new RandomGraph(6);
g.print(); 
console.log(g.isConnected());
/*输出结果为:
0: 2,1
1: 0,2,5
2: 0,1
3: 5
4: 
5: 1,3
Set(1) { Node { id: 0, neighbors: Set(2) { [Node], [Node] } } }
Set(2) {
  Node { id: 0, neighbors: Set(2) { [Node], [Node] } },
  Node { id: 2, neighbors: Set(2) { [Node], [Node] } }
}
Set(3) {
  Node { id: 0, neighbors: Set(2) { [Node], [Node] } },
  Node { id: 2, neighbors: Set(2) { [Node], [Node] } },
  Node { id: 1, neighbors: Set(3) { [Node], [Node], [Node] } }
}
Set(4) {
  Node { id: 0, neighbors: Set(2) { [Node], [Node] } },
  Node { id: 2, neighbors: Set(2) { [Node], [Node] } },
  Node { id: 1, neighbors: Set(3) { [Node], [Node], [Node] } },
  Node { id: 5, neighbors: Set(2) { [Node], [Node] } }
}
Set(5) {
  Node { id: 0, neighbors: Set(2) { [Node], [Node] } },
  Node { id: 2, neighbors: Set(2) { [Node], [Node] } },
  Node { id: 1, neighbors: Set(3) { [Node], [Node], [Node] } },
  Node { id: 5, neighbors: Set(2) { [Node], [Node] } },
  Node { id: 3, neighbors: Set(1) { [Node] } }
}
false
 */

图结构如下图:

h8.jpeg