1. 程式人生 > 實用技巧 >js實現圖的遍歷之廣度優先搜尋

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);
}