js实现图的遍历之广度优先搜索

Posted 等不了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js实现图的遍历之广度优先搜索相关的知识,希望对你有一定的参考价值。

1.图

图是一种非线性数据结构,是网络模型的抽象模型,图是一组由边连接的节点。

2.图的组成

一个图G = (V,E),V:一组顶点,E:一组边

3.强连通图

任何两个节点,它们之间都有路径到达,称为强连通图

4.邻接矩阵

5.领接表

6.字典

我采用是领接表的方法,所以这里我采用字典来存储,每个顶点和每个顶点所对应的边。

function defaultToString(item){
    if(item == null){
        return \'null\';
    }
    if(item == undefined){
        return \'undefined\';
    }
    if(typeof item == \'string\' || item instanceof String){
        return item;
    }
    return `${item}`;
}
//这个类专门用来保存节点点值和相邻的节点
class ValuePair{
    constructor(key,value){
        this.key = key;
        this.value = value;
    }
}
//字典类
class Dictionary{
    constructor(toStrFn = defaultToString){//接受外面的函数
        this.toStrFn = toStrFn;//这里把所有的键名全部转换字符串,方便检索
        this.table = {};//专门存储数据
    }
    set(key,value){//设置节点和相邻的节点的方法
        if(key != null && value != null){
            const tableKey = this.toStrFn(key);
            this.table[tableKey] = new ValuePair(key,value);//{键名:{key:键名;value:相邻的节点}}
            return true;
        }
       return false;
    }
    get(key){//返回节点所相连的节点
        const valuePair = this.table[this.toStrFn(key)];
        return valuePair == null?undefined:valuePair.value;
    }
    hasKey(key){//判断字典中有没有这个节点
        return this.table[this.toStrFn(key)] != null;
    }
    remove(key){//移除这个节点
        if(this.hasKey(key)){
            delete this.table[this.toStrFn(key)];
            return true;
        }
        return false;
    }
    clear(){//清除字典的所有内容
        this.table = {};
    }
    size(){//返回字典的节点的个数
        return Object.keys(this.table).length;
    }
    isEmpty(){//判断字典是否为空
        return this.size() === 0;
    }
    keys(){//获取字典的所有的节点的方法
        return this.keyValues().map(valuePair => valuePair.key);
    }
    keyValues(){//获取字典的所有的边的方法
       const valuePair = [];
       for(let key in this.table){
           if(this.hasKey(key)){
                valuePair.push(key);
           }
       }
       return valuePair;
    }
}

7.创建图

class Graph{
    constructor(isDirected = false){
        this.isDirected = isDirected;//是否为有向图
        this.vertices = [];//存储所有节点
        this.adjList = new Dictionary();//用字典来存储邻接表
    }
    addVertex(v){//添加顶点
        if(!this.vertices.includes(v)){
            this.vertices.push(v);
            this.adjList.set(v,[]);//使用字典的set方法,来存储节点,和邻接节点,这里邻接节点会有很多,所以用数组来存储
        }
    }
    addEdge(v,w){//给节点添加它的邻接节点
        if(!this.vertices.includes(v)){
            this.addVertices(v);
        }
        if(!this.vertices.includes(w)){
            this.addVertices(w);
        }
        this.adjList.get(v).push(w);//{key:v;value:[]},在v所对应的数组里面push它的邻接节点
        if(!this.isDirected){//有向图就不添加
            this.adjList.get(w).push(v);
        }
    }
    getVertices(){//返回所有节点
        return this.vertices;
    }
    getAdjList(){//返回存储邻接表的字典
        return this.adjList;
    }
    toString(){//打印邻接表
        let s =\'\';
        for(let i = 0; i < this.vertices.length; i++){
            s+=`${this.vertices[i]}->`;
            let value = this.adjList.get(this.vertices[i]);
            for(let j =0; j < value.length;j++){
                s+=`${value[j]}`;
            }
            s+="\\n";
        }
        return s;
    }
}

8.图中添加节点

const printVertex = (value) => console.log(value);
const myVertices = [\'A\', \'B\', \'C\', \'D\', \'E\', \'F\', \'G\', \'H\', \'I\'];
const graph = new Graph();
for (let i = 0; i < myVertices.length; i++) {
    graph.addVertex(myVertices[i]);
  }
  graph.addEdge(\'A\', \'B\');
  graph.addEdge(\'A\', \'C\');
  graph.addEdge(\'A\', \'D\');
  graph.addEdge(\'C\', \'D\');
  graph.addEdge(\'C\', \'G\');
  graph.addEdge(\'D\', \'G\');
  graph.addEdge(\'D\', \'H\');
  graph.addEdge(\'B\', \'E\');
  graph.addEdge(\'B\', \'F\');
  graph.addEdge(\'E\', \'I\');

9.结果

10.广度优先搜索(BFS)

图遍历算法的思想是必须追踪每个第一次访问的节点,并且追踪有哪些节点还没有被完全探索。BFS和DFS都需要指出第一个被访问的节点

这里用三种颜色,来表示节点访问状态。

白色:表示节点还没有访问

灰色:节点访问过,但是还没有完全探索过

黑色:节点访问过,已完全探索过

使用一个Colors变量来存储三种颜色

Colors={
    WHITE:0,//还没有访问的
    GREY:1,//已经访问过的,但是还没有完全探索的
    BLACK:2 //已经访问过的,并且已经全部探索过的
}

初始化所有节点颜色,参数为一个数组,返回一个对象

const initializeColor = vertices =>{
    const color = {};
    for(let i =0; i < vertices.length; i++){
        color[vertices[i]] = Colors.WHITE; 
    }
    return color;
}

BFS代码

const breadthFirstSearch = (graph,startVertex,callback)=>{
    const vertices = graph.getVertices();
    const adjList  = graph.getAdjList();
    let color = initializeColor(vertices);
    const queue = new Queue();
    queue.enqueue(startVertex);
    while(!queue.isEmpty()){
        let v = queue.dequeue();
        color[v] = Colors.GREY;
        let neighbor = adjList.get(v);
        for(let i = 0; i < neighbor.length; i++){
            let w = neighbor[i];
            if(color[w] == Colors.WHITE){//还没有访问,就执行
                queue.enqueue(w);
                color[w] = Colors.GREY;
            }
        }
        color[v] = Colors.BLACK;
        if(callback){
            callback(v);
        }
    }
}

结果

11.改进版BFS

class Stack{
    constructor(){
        this.item = {};
        this.count = 0;
    }
    push(key){
        this.item[this.count] = key;
        this.count++;
    }
    pop(){
        if(this.isEmpty()){
            return \'stack is null\';
        }
        this.count--;
        let result = this.item[this.count];
        delete this.item[this.count];
        return result;
    }
    isEmpty(){
        return this.size() === 0;
    }
    size(){
        return this.count;
    }
    peek(){
        return this.item[this.count-1];
    }
}
const fromVertex = myVertices[0];

for(let i = 1; i < myVertices.length; i++){
    const vertice = myVertices[i];
    const path = new Stack();
    for(let v = vertice; v != fromVertex; v = shortestPathA.predecessors[v]){
        path.push(v);
    }
    path.push(fromVertex);
    let s = path.pop();
    while(!path.isEmpty()){
        s += `->${path.pop()}`;
    }
    console.log(s);
}

结果

12.所有代码

//用来表示每个节点的颜色
Colors={
    WHITE:0,//还没有访问的
    GREY:1,//已经访问过的,但是还没有完全探索的
    BLACK:2 //已经访问过的,并且已经全部探索过的
}
//初始化节点的颜色,让它们都为白色,vertices是一个数组,专门用来存储节点
const initializeColor = vertices =>{
    const color = {};
    for(let i =0; i < vertices.length; i++){
        color[vertices[i]] = Colors.WHITE; 
    }
    return color;
}

class Queue{
    constructor(){
        this.queue = {};
        this.lowerCast = 0;
        this.biggerCast = 0;
    }
    enqueue(key){
       this.queue[this.biggerCast] = key;
       this.biggerCast++;
    }
    dequeue(){
        if(this.isEmpty())return;
        let item = this.queue[this.lowerCast];
        delete this.queue[this.lowerCast];
        this.lowerCast++;
        return item;
    }
    isEmpty(){
        return this.size() === 0;
    }
    size(){
        return this.biggerCast - this.lowerCast;
    }
}
function defaultToString(item){
    if(item == null){
        return \'null\';
    }
    if(item == undefined){
        return \'undefined\';
    }
    if(typeof item == \'string\' || item instanceof String){
        return item;
    }
    return `${item}`;
}
//这个类专门用来保存节点点值和相邻的节点
class ValuePair{
    constructor(key,value){
        this.key = key;
        this.value = value;
    }
}
//字典类
class Dictionary{
    constructor(toStrFn = defaultToString){//接受外面的函数
        this.toStrFn = toStrFn;//这里把所有的键名全部转换字符串,方便检索
        this.table = {};//专门存储数据
    }
    set(key,value){//设置节点和相邻的节点的方法
        if(key != null && value != null){
            const tableKey = this.toStrFn(key);
            this.table[tableKey] = new ValuePair(key,value);//{键名:{key:键名;value:相邻的节点}}
            return true;
        }
       return false;
    }
    get(key){//返回节点所相连的节点
        const valuePair = this.table[this.toStrFn(key)];
        return valuePair == null?undefined:valuePair.value;
    }
    hasKey(key){//判断字典中有没有这个节点
        return this.table[this.toStrFn(key)] != null;
    }
    remove(key){//移除这个节点
        if(this.hasKey(key)){
            delete this.table[this.toStrFn(key)];
            return true;
        }
        return false;
    }
    clear(){//清除字典的所有内容
        this.table = {};
    }
    size(){//返回字典的节点的个数
        return Object.keys(this.table).length;
    }
    isEmpty(){//判断字典是否为空
        return this.size() === 0;
    }
    keys(){//获取字典的所有的节点的方法
        return this.keyValues().map(valuePair => valuePair.key);
    }
    keyValues(){//获取字典的所有的边的方法
       const valuePair = [];
       for(let key in this.table){
           if(this.hasKey(key)){
                valuePair.push(key);
           }
       }
       return valuePair;
    }
}
class Graph{
    constructor(isDirected = false){
        this.isDirected = isDirected;//是否为有向图
        this.vertices = [];//存储所有节点
        this.adjList = new Dictionary();//用字典来存储邻接表
    }
    addVertex(v){//添加顶点
        if(!this.vertices.includes(v)){
            this.vertices.push(v);
            this.adjList.set(v,[]);//使用字典的set方法,来存储节点,和邻接节点,这里邻接节点会有很多,所以用数组来存储
        }
    }
    addEdge(v,w){//给节点添加它的邻接节点
        if(!this.vertices.includes(v)){
            this.addVertices(v);
        }
        if(!this.vertices.includes(w)){
            this.addVertices(w);
        }
        this.adjList.get(v).push(w);//{key:v;value:[]},在v所对应的数组里面push它的邻接节点
        if(!this.isDirected){//有向图就不添加
            this.adjList.get(w).push(v);
        }
    }
    getVertices(){//返回所有节点
        return this.vertices;
    }
    getAdjList(){//返回存储邻接表的字典
        return this.adjList;
    }
    toString(){//打印邻接表
        let s =\'\';
        for(let i = 0; i < this.vertices.length; i++){
            s+=`${this.vertices[i]}->`;
            let value = this.adjList.get(this.vertices[i]);
            for(let j =0; j < value.length;j++){
                s+=`${value[j]}`;
            }
            s+="\\n";
        }
        return s;
    }
}

const myVertices = [\'A\', \'B\', \'C\', \'D\', \'E\', \'F\', \'G\', \'H\', \'I\'];
const graph = new Graph();
for (let i = 0; i < myVertices.length; i++) {
    graph.addVertex(myVertices[i]);
  }
  graph.addEdge(\'A\', \'B\');
  graph.addEdge(\'A\', \'C\');
  graph.addEdge(\'A\', \'D\');
  graph.addEdge(\'C\', \'D\');
  graph.addEdge(\'C\', \'G\');
  graph.addEdge(\'D\', \'G\');
  graph.addEdge(\'D\', \'H\');
  graph.addEdge(\'B\', \'E\');
  graph.addEdge(\'B\', \'F\');
  graph.addEdge(\'E\', \'I\');
 console.log( graph.toString());

const breadthFirstSearch = (graph,startVertex,callback)=>{
    const vertices = graph.getVertices();
    const adjList  = graph.getAdjList();
    let color = initializeColor(vertices);
    const queue = new Queue();
    queue.enqueue(startVertex);
    while(!queue.isEmpty()){
        let v = queue.dequeue();
        color[v] = Colors.GREY;
        let neighbor = adjList.get(v);
        for(let i = 0; i < neighbor.length; i++){
            let w = neighbor[i];
            if(color[w] == Colors.WHITE){//还没有访问,就执行
                queue.enqueue(w);
                color[w] = Colors.GREY;
            }
        }
        color[v] = Colors.BLACK;
        if(callback){
            callback(v);
        }
    }
}
const printVertex = (value) => console.log(value);
breadthFirstSearch(graph,myVertices[0],printVertex);





const BFS = (graph,startVertex) =>{
    const vertices = graph.getVertices();//获取图的所有节点
    const adjList  = graph.getAdjList();//获取图的字典
    const color = initializeColor(vertices);//初始化每个节点的颜色
    const queue = new Queue();
    const distance = {};
    const predecessors = {};
    queue.enqueue(startVertex);//把顶点放入队列
    for(let i = 0; i < Object.keys(color).length; i++){
        distance[vertices[i]] = 0;
        predecessors[vertices[i]] = null;
    }
    while(!queue.isEmpty()){
        let u = queue.dequeue();
        color[u] = Colors.GREY;
        let neighbor = adjList.get(u);
        for(let j =0; j < neighbor.length; j++){
            let w = neighbor[j]
            if(color[w] == Colors.WHITE){
                queue.enqueue(w);
                distance[w] = distance[u]+1;
                predecessors[w] = u;
            }
        }
        color[u] = Colors.BLACK;
    }
    return  {
        distance,
        predecessors
    }
}

const shortestPathA = BFS(graph,myVertices[0]);
console.log(shortestPathA);
class Stack{
    constructor(){
        this.item = {};
        this.count = 0;
    }
    push(key){
        this.item[this.count] = key;
        this.count++;
    }
    pop(){
        if(this.isEmpty()){
            return \'stack is null\';
        }
        this.count--;
        let result = this.item[this.count];
        delete this.item[this.count];
        return result;
    }
    isEmpty(){
        return this.size() === 0;
    }
    size(){
        return this.count;
    }
    peek(){
        return this.item[this.count-1];
    }
}
const fromVertex = myVertices[0];

for(let i = 1; i < myVertices.length; i++){
    const vertice = myVertices[i];
    const path = new Stack();
    for(let v = vertice; v != fromVertex; v = shortestPathA.predecessors[v]){
        path.push(v);
    }
    path.push(fromVertex);
    let s = path.pop();
    while(!path.isEmpty()){
        s += `->${path.pop()}`;
    }
    console.log(s);
}

 

以上是关于js实现图的遍历之广度优先搜索的主要内容,如果未能解决你的问题,请参考以下文章

C语言实现图的广度优先搜索遍历算法

图的遍历之深度优先搜索和广度优先搜索

算法专题 之 广度优先搜索

图的深度/广度优先遍历C语言程序

数据结构(三十二)图的遍历之广度优先遍历

数据结构—— 图:图的遍历