BFS,DFS 算法原理及js實現

1. 說明

本文全部的算法嚴格按照《算法導論》,本文將詳細的對BFSDFS進行分析,並提供算法的 js 實現,同時會對建立鏈表的方式進行優化node

2. 圖的表示

圖的表示分爲對頂點集 V 的表示和對邊集 E 的表示,這裏的重點是如何表示邊,邊的表示分爲鄰接矩陣鄰接鏈表這兩種表示方法,鄰接矩陣適合表示邊稠密的圖,其消耗空間爲|V|*|V|,若是是無向圖,則能夠用上三角矩陣或者下三角矩陣來表示,是空間消耗變爲|V|*|V|/2鄰接鏈表適合表示邊稀疏的圖,其消耗的空間爲 O(|V|+|E|),用鄰接鏈表表示圖很緊湊,沒有空間浪費,用《算法導論》中的原話就是,鄰接鏈表表示圖,魯棒性很高。本文涉及的圖,所有用鄰接鏈表表示。算法

  • 2.1. 本文的算法都是對該圖的操做

圖片描述

  • 2.2. 對上圖進行鄰接鏈表的轉化

圖片描述

從上圖能夠看到咱們將圖的分爲兩部分,頂點和邊,咱們分別對這兩部分進行表示,咱們用數組去存放頂點,用鏈表去描述邊。A-E 作爲節點的標識。數字表示頂點在數組中的位置。由這幅圖能夠看到從節點 A 發出的邊有兩條,分別是 <A,C>,和 <A,D>數組

3. BFS 廣度優先搜索

廣度優先搜索的思想是,對於圖G和給定的節點s,廣度優先搜索須要一個輔助的先進先出的隊列 Q瀏覽器

  1. s加入到Q
  2. sQ總移出,用臨時變量接受s,若是s沒有被訪問過,從s出發,發現s的全部鄰接節點並放入Q
  3. 訪問s
  4. Q隊列的第一個元素移除隊列做爲新的s執行2-4過程直到隊列Q爲空
  • 3.1 表示頂點的數據結構

function Vertex() {
    if (!(this instanceof Vertex))
        return new Vertex();
    this.color = this.WHITE; //初始爲 白色
    this.pi = null; //初始爲 無前驅
    this.d = this.INFINITY; //初始爲 無窮大
    this.edges = null; //由頂點發出的全部邊
    this.value = null; //節點的值 默認爲空
}
Vertex.prototype = {
    constructor: Vertex,
    WHITE: 'white', //白色
    GRAY: 'gray', //灰色
    BLACK: 'black', //黑色
    INFINITY: null, //d 爲 null 時表示無窮大
}

爲了跟蹤算法的進展,咱們對圖進行搜索的時候會對圖中的頂點進行塗色,圖初始化是頂點所有爲白色,當第一次發現某個節點時,咱們將他塗爲灰色,當對某個節點訪問完成後,咱們將它塗爲黑色。在這裏咱們看到每一個節點都有 五個 屬性,color表示節點的顏色,pi 表示前驅結點,d 表示廣度優先搜索中從源節點到當前節點的距離,edges 表示從當前節點發出的全部邊,value 表示節點存放的數據數據結構

  • 3.2 表示邊的數據結構

function Edge() {
    if (!(this instanceof Edge))
        return new Edge();
    this.index = null; //邊所依附的節點的位置
    this.sibling = null;
}

能夠看到,邊包含兩個兩個屬性,index,和siblingindex表示這條邊鏈接的節點在頂點數組中的位置,sibling只想下一個鏈接兄弟節點的邊。優化

  • 3.3 表示圖的數據結構

function Graph() {
    if (!(this instanceof Graph))
        return new Graph();
    this.graph = []; //存放頂點的數組
}
Graph.prototype = {
    constructor: Graph,
    addNode: function (node) {
        this.graph.push(node);
    },
    getNode: function (index) {
        return this.graph[index];
    }
}
  • 3.4 構建圖

//建立 頂點
var vA = Vertex();
var vB = Vertex();
var vC = Vertex();
var vD = Vertex();
var vE = Vertex();
var vF = Vertex();
vA.value = 'A';
vB.value = 'B';
vC.value = 'C';
vD.value = 'D';
vE.value = 'E';
vF.value = 'F';

//構建由 A 節點發出的邊集
var eA1 = Edge();
var eA2 = Edge();
eA1.index = 1;
eA2.index = 3;
eA1.sibling = eA2;
vA.edges = eA1;

//構建有 B 節點發出的邊集
var eB1 = Edge();
var eB2 = Edge();
var eB3 = Edge();
eB1.index = 0;
eB2.index = 4;
eB3.index = 2;
eB1.sibling = eB2;
eB2.sibling = eB3;
vB.edges = eB1;

//構建由 C 節點發出的邊
var eC1 = Edge();
var eC2 = Edge();
var eC3 = Edge();
eC1.index = 1;
eC2.index = 4;
eC3.index = 5;
eC1.sibling = eC2;
eC2.sibling = eC3;
vC.edges = eC1;

//構建由 D 節點發出的邊
var eD1 = Edge();
eD1.index = 0;
vD.edges = eD1;

//構建由 E 節點發出的邊
var eE1 = Edge();
var eE2 = Edge();
var eE3 = Edge();
eE1.index = 1;
eE2.index = 2;
eE3.index = 5;
eE1.sibling = eE2;
eE2.sibling = eE3;
vE.edges = eE1;

//構建由 F 節點發出的邊
var eF1 = Edge();
var eF2 = Edge();
eF1.index = 2;
eF2.index = 4;
eF1.sibling = eF2;
vF.edges = eF1;

//構建圖
var g = Graph();
g.addNode(vA);
g.addNode(vB);
g.addNode(vC);
g.addNode(vD);
g.addNode(vE);
g.addNode(vF);
  • 3.5 BFS算法

//廣度優先搜索
function BFS(g, s) {
    let queue = []; //輔助隊列 Q
    s.color = s.GRAY; //首次發現s塗爲灰色
    s.d = 0; //距離爲0
    queue.push(s); //將s放入隊列 Q
    while (queue.length > 0) { //當隊列Q中有頂點時執行搜索
        let u = queue.shift(); //將Q中的第一個元素移出
        if (u.edges == null) continue; //若是從當前頂點沒有發出邊
        let sibling = u.edges; //獲取表示鄰接邊的鏈表的頭節點
        while (sibling != null) { //當鏈表不爲空
            let index = sibling.index; //當前邊所鏈接的頂點在隊列中的位置
            let n = g.getNode(index); //獲取頂點
            if (n.color == n.WHITE) { //若是沒有被訪問過
                n.color = n.GRAY; //塗爲灰色
                n.d = u.d + 1; //距離加1
                n.pi = u; //設置前驅節點
                queue.push(n); //將 n 放入隊列 Q
            }
            sibling = sibling.sibling; //下一條邊
        }
        u.color = u.BLACK; //當前頂點訪問結束 塗爲黑色
    }
}
  • 3.6 完整代碼可粘貼到瀏覽器控制檯運行

//數據結構 鄰接鏈表-頂點
function Vertex() {
    if (!(this instanceof Vertex))
        return new Vertex();
    this.color = this.WHITE; //初始爲 白色
    this.pi = null; //初始爲 無前驅
    this.d = this.INFINITY; //初始爲 無窮大
    this.edges = null; //由頂點發出的全部邊
    this.value = null; //節點的值 默認爲空
}
Vertex.prototype = {
    constructor: Vertex,
    WHITE: 'white', //白色
    GRAY: 'gray', //灰色
    BLACK: 'black', //黑色
    INFINITY: null, //d 爲 null 時表示無窮大
}

//數據結構 鄰接鏈表-邊
function Edge() {
    if (!(this instanceof Edge))
        return new Edge();
    this.index = null; //邊所依附的節點的位置
    this.sibling = null;
}

//數據結構 圖-G
function Graph() {
    if (!(this instanceof Graph))
        return new Graph();
    this.graph = [];
}
Graph.prototype = {
    constructor: Graph,
    //這裏加進來的已經具有了邊的關係
    addNode: function (node) {
        this.graph.push(node);
    },
    getNode: function (index) {
        return this.graph[index];
    }
}

//廣度優先搜索
function BFS(g, s) {
    let queue = [];
    s.color = s.GRAY;
    s.d = 0;
    queue.push(s);
    while (queue.length > 0) {
        let u = queue.shift();
        if (u.edges == null) continue;
        let sibling = u.edges;
        while (sibling != null) {
            let index = sibling.index;
            let n = g.getNode(index);
            if (n.color == n.WHITE) {
                n.color = n.GRAY;
                n.d = u.d + 1;
                n.pi = u;
                queue.push(n);
            }
            sibling = sibling.sibling;
        }
        u.color = u.BLACK;
        console.log(u);
    }
}

//建立 頂點
var vA = Vertex();
var vB = Vertex();
var vC = Vertex();
var vD = Vertex();
var vE = Vertex();
var vF = Vertex();
vA.value = 'A';
vB.value = 'B';
vC.value = 'C';
vD.value = 'D';
vE.value = 'E';
vF.value = 'F';

//構建由 A 節點發出的邊集
var eA1 = Edge();
var eA2 = Edge();
eA1.index = 1;
eA2.index = 3;
eA1.sibling = eA2;
vA.edges = eA1;

//構建有 B 節點發出的邊集
var eB1 = Edge();
var eB2 = Edge();
var eB3 = Edge();
eB1.index = 0;
eB2.index = 4;
eB3.index = 2;
eB1.sibling = eB2;
eB2.sibling = eB3;
vB.edges = eB1;

//構建由 C 節點發出的邊
var eC1 = Edge();
var eC2 = Edge();
var eC3 = Edge();
eC1.index = 1;
eC2.index = 4;
eC3.index = 5;
eC1.sibling = eC2;
eC2.sibling = eC3;
vC.edges = eC1;

//構建由 D 節點發出的邊
var eD1 = Edge();
eD1.index = 0;
vD.edges = eD1;

//構建由 E 節點發出的邊
var eE1 = Edge();
var eE2 = Edge();
var eE3 = Edge();
eE1.index = 1;
eE2.index = 2;
eE3.index = 5;
eE1.sibling = eE2;
eE2.sibling = eE3;
vE.edges = eE1;

//構建由 F 節點發出的邊
var eF1 = Edge();
var eF2 = Edge();
eF1.index = 2;
eF2.index = 4;
eF1.sibling = eF2;
vF.edges = eF1;

//構建圖
var g = Graph();
g.addNode(vA);
g.addNode(vB);
g.addNode(vC);
g.addNode(vD);
g.addNode(vE);
g.addNode(vF);

BFS(g, vB);

頂點的訪問順序爲 B->A->E->C->D->Fthis

4. DFS 深度優先搜索

  • 特色
    深度優先搜索通常默認的源點有多個,搜索時的前驅子圖會構成一個深度優先森林,這是依據深度優先搜索的搜索結果的使用深度優先搜索算法經常做爲另外一個算法的一個子程序被使用深度優先搜索在節點中增長了一個發現的時間戳,一個訪問的時間戳,一般能幫助咱們推斷算法的行爲,在d-f之間是灰色,在f以後是黑色,時間戳爲12*|v|之間的整數
  • 算法思想
    只要有可能,就在圖中儘可能「深刻」,老是對最近才發現的節點v的出發邊進行探索,知道該節點的全部出發邊都被發現爲止。一旦v的全部發出的邊都被發現,搜索則「回溯」到v的前驅節點,該過程一直持續到源節點可達的全部節點都被發現爲止,若是還有未發現的節點,則深度優先搜索將從這些未被發現的節點中任選一個做爲新的源節點,並重復一樣的搜索過程

  • 4.1 算法數據結構

深度優先搜索的數據結構只有在表示頂點時稍有不一樣,其它的都相同,這裏給出表示頂點的數據結構spa

function Vertex() {
    if (!(this instanceof Vertex))
        return new Vertex();
    this.color = this.WHITE; //初始爲 白色
    this.pi = null; //初始爲 無前驅
    this.d = null; //時間戳 發現時
    this.f = null; //時間戳 鄰接鏈表掃描完成時
    this.edges = null; //由頂點發出的全部邊
    this.value = null; //節點的值 默認爲空
}
Vertex.prototype = {
    constructor: Vertex,
    WHITE: 'white', //白色
    GRAY: 'gray', //灰色
    BLACK: 'black', //黑色
}

能夠看到頂點數據結構中的多了一個f,同時d的含義也發生了變化df做爲發現和訪問完成的時間戳,取值爲從12*|v|prototype

  • 4.2 DFS算法

function DFS(g) {
    let t = 0; //時間戳
    for (let v of g.vertexs) { //讓每一個節點都做爲一次源節點
        if (v.color == v.WHITE) DFSVisit(g, v);
    }
    function DFSVisit(g, v) {
        t = t + 1; //時間戳加一
        v.d = t;
        v.color = v.GRAY;
        let sibling = v.edges;
        while (sibling != null) {
            let index = sibling.index;
            let n = g.getNode(index);
            if (n.color == n.WHITE) {
                n.pi = v;
                DFSVisit(g, n); //先縱向找
            }
            sibling = sibling.sibling; //利用遞歸的特性來回溯
        }
        v.color = v.BLACK;
        t = t + 1; //時間戳加一
        v.f = t;
    }
}
  • 4.3 DFS完整代碼

function Vertex() {
    if (!(this instanceof Vertex))
        return new Vertex();
    this.color = this.WHITE; //初始爲 白色
    this.pi = null; //初始爲 無前驅
    this.d = null; //時間戳 發現時
    this.f = null; //時間戳 鄰接鏈表掃描完成
    this.edges = null; //由頂點發出的全部邊
    this.value = null; //節點的值 默認爲空
}
Vertex.prototype = {
    constructor: Vertex,
    WHITE: 'white', //白色
    GRAY: 'gray', //灰色
    BLACK: 'black', //黑色
}

//數據結構 圖-G
function Graph() {
    if (!(this instanceof Graph))
        return new Graph();
    this.vertexs = [];
}
Graph.prototype = {
    constructor: Graph,
    addNode: function (node) {
        this.vertexs.push(node);
    },
    getNode: function (index) {
        return this.vertexs[index];
    }
}

//這裏 t 做爲全局變量和參數時結果不同 由於 js 對於基本類型的參數採用的是值捕獲,對於對象類型的參數採用的是引用捕獲
function DFS(g) {
    let t = 0;
    for (let v of g.vertexs) {
        if (v.color == v.WHITE) DFSVisit(g, v);
    }
    function DFSVisit(g, v) {
        t = t + 1;
        v.d = t;
        v.color = v.GRAY;
        let sibling = v.edges;
        while (sibling != null) {
            let index = sibling.index;
            let n = g.getNode(index);
            if (n.color == n.WHITE) {
                n.pi = v;
                DFSVisit(g, n); //先縱向找
            }
            sibling = sibling.sibling; //利用遞歸的特性來回溯
        }
        v.color = v.BLACK;
        t = t + 1;
        v.f = t;
        console.log(v);
    }
}

//數據結構 鄰接鏈表-邊
function Edge() {
    if (!(this instanceof Edge))
        return new Edge();
    this.index = null; //邊所依附的節點的位置
    this.sibling = null;
}

//建立 頂點
var vA = Vertex();
var vB = Vertex();
var vC = Vertex();
var vD = Vertex();
var vE = Vertex();
var vF = Vertex();
vA.value = 'A';
vB.value = 'B';
vC.value = 'C';
vD.value = 'D';
vE.value = 'E';
vF.value = 'F';

//構建由 A 節點發出的邊集
var eA1 = Edge();
var eA2 = Edge();
eA1.index = 1;
eA2.index = 3;
eA1.sibling = eA2;
vA.edges = eA1;

//構建有 B 節點發出的邊集
var eB1 = Edge();
var eB2 = Edge();
var eB3 = Edge();
eB1.index = 0;
eB2.index = 4;
eB3.index = 2;
eB1.sibling = eB2;
eB2.sibling = eB3;
vB.edges = eB1;

//構建由 C 節點發出的邊
var eC1 = Edge();
var eC2 = Edge();
var eC3 = Edge();
eC1.index = 1;
eC2.index = 4;
eC3.index = 5;
eC1.sibling = eC2;
eC2.sibling = eC3;
vC.edges = eC1;

//構建由 D 節點發出的邊
var eD1 = Edge();
eD1.index = 0;
vD.edges = eD1;

//構建由 E 節點發出的邊
var eE1 = Edge();
var eE2 = Edge();
var eE3 = Edge();
eE1.index = 1;
eE2.index = 2;
eE3.index = 5;
eE1.sibling = eE2;
eE2.sibling = eE3;
vE.edges = eE1;

//構建由 F 節點發出的邊
var eF1 = Edge();
var eF2 = Edge();
eF1.index = 2;
eF2.index = 4;
eF1.sibling = eF2;
vF.edges = eF1;

//構建圖
var g = Graph();
g.addNode(vA);
g.addNode(vB);
g.addNode(vC);
g.addNode(vD);
g.addNode(vE);
g.addNode(vF);

DFS(g);

節點訪問順序爲 F->C->E->B->D->Acode

5. 對構建鏈表的方式進行優化

咱們發現構建圖的操做過於繁瑣,因而想簡化圖的構建方式,簡化後以下

var vertexs = ['A', 'B', 'C', 'D', 'E', 'F'];
var edges = {
    A: [{ id: 'B', w: 1 }, { id: 'D', w: 2 }],
    B: [{ id: 'A', w: 3 }, { id: 'E', w: 3 }, { id: 'C', w: 7 }],
    C: [{ id: 'B', w: 5 }, { id: 'E', w: 3 }, { id: 'F', w: 4 }],
    D: [{ id: 'A', w: 2 }],
    E: [{ id: 'B', w: 3 }, { id: 'C', w: 7 }, { id: 'F', w: 3 }],
    F: [{ id: 'C', w: 6 }, { id: 'E', w: 9 }]
}
var g = Graph();
g.initVertex(vertexs);
g.initEdge(edges);

咱們想用這種方式初始化一個圖,w爲邊的權值

這裏的改進只是針對圖的構建,全部不管時BFS,仍是DFS,表示頂點和邊的數據結構都沒有變,只有對錶示圖的數據結構 Graph進行改進

  • 5.1 改進以後的Graph

//數據結構 圖-G

//數據結構 圖-G
function Graph() {
    if (!(this instanceof Graph))
        return new Graph();
    this.graph = [];
    this.refer = new Map(); //字典 用來映射標節點的識符和數組中的位置
}
Graph.prototype = {
    constructor: Graph,
    //這裏加進來的已經具有了邊的關係
    addNode: function(node) {
        this.graph.push(node);
    },
    getNode: function(index) {
        return this.graph[index];
    },
    //建立圖的 節點
    initVertex: function(vertexs) {
        //建立節點並初始化節點屬性 value
        for (let value of vertexs) {
            let vertex = Vertex();
            vertex.value = value;
            this.graph.push(vertex);
        }
        //初始化 字典
        for (let i in this.graph) {
            this.refer.set(this.graph[i].value,i);
        }
    },
    //創建圖中 邊 的關係
    initEdge: (function(){
        //建立鏈表,返回鏈表的第一個節點
        function createLink(index, len, edges, refer) {
            if (index >= len) return null;
            let edgeNode = Edge();
            edgeNode.index = refer.get(edges[index].id); //邊鏈接的節點 用在數組中的位置表示 參照字典
            edgeNode.w = edges[index].w; //邊的權值
            edgeNode.sibling = createLink(++index, len, edges, refer); //經過遞歸實現 回溯
            return edgeNode;
        }
        return function(edges) {
            for (let field in edges) {
                let index = this.refer.get(field); //從字典表中找出節點在 graph 中的位置
                let vertex = this.graph[index]; //獲取節點
                vertex.edges = createLink(0, edges[field].length, edges[field], this.refer);
            }
        }
    }())
}
  • 5.2 改進以後的BFS完整代碼

DFS相同

function Vertex() {
    if (!(this instanceof Vertex))
        return new Vertex();
    this.color = this.WHITE; //初始爲 白色
    this.pi = null; //初始爲 無前驅
    this.d = this.INFINITY; //初始爲 無窮大
    this.edges = null; //由頂點發出的全部邊
    this.value = null; //節點的值 默認爲空
}
Vertex.prototype = {
    constructor: Vertex,
    WHITE: 'white', //白色
    GRAY: 'gray', //灰色
    BLACK: 'black', //黑色
    INFINITY: null, //d 爲 null 時表示無窮大
}

//數據結構 鄰接鏈表-邊
function Edge() {
    if (!(this instanceof Edge))
        return new Edge();
    this.index = null; //邊所依附的節點的位置
    this.sibling = null;
    this.w = null; //保存邊的權值
}

//數據結構 圖-G
function Graph() {
    if (!(this instanceof Graph))
        return new Graph();
    this.graph = [];
    this.refer = new Map(); //字典 用來映射標節點的識符和數組中的位置
}
Graph.prototype = {
    constructor: Graph,
    //這裏加進來的已經具有了邊的關係
    addNode: function(node) {
        this.graph.push(node);
    },
    getNode: function(index) {
        return this.graph[index];
    },
    //建立圖的 節點
    initVertex: function(vertexs) {
        //建立節點並初始化節點屬性 value
        for (let value of vertexs) {
            let vertex = Vertex();
            vertex.value = value;
            this.graph.push(vertex);
        }
        //初始化 字典
        for (let i in this.graph) {
            this.refer.set(this.graph[i].value,i);
        }
    },
    //創建圖中 邊 的關係
    initEdge: (function(){
        //建立鏈表,返回鏈表的第一個節點
        function createLink(index, len, edges, refer) {
            if (index >= len) return null;
            let edgeNode = Edge();
            edgeNode.index = refer.get(edges[index].id); //邊鏈接的節點 用在數組中的位置表示 參照字典
            edgeNode.w = edges[index].w; //邊的權值
            edgeNode.sibling = createLink(++index, len, edges, refer); //經過遞歸實現 回溯
            return edgeNode;
        }
        return function(edges) {
            for (let field in edges) {
                let index = this.refer.get(field); //從字典表中找出節點在 graph 中的位置
                let vertex = this.graph[index]; //獲取節點
                vertex.edges = createLink(0, edges[field].length, edges[field], this.refer);
            }
        }
    }())
}

//廣度優先搜索
function BFS(g, s) {
    let queue = [];
    s.color = s.GRAY;
    s.d = 0;
    queue.push(s);
    while (queue.length > 0) {
        let u = queue.shift();
        if (u.edges == null) continue;
        let sibling = u.edges;
        while (sibling != null) {
            let index = sibling.index;
            let n = g.getNode(index);
            if (n.color == n.WHITE) {
                n.color = n.GRAY;
                n.d = u.d + 1;
                n.pi = u;
                queue.push(n);
            }
            sibling = sibling.sibling;
        }
        u.color = u.BLACK;
        console.log(u)
    }
}

var vertexs = ['A', 'B', 'C', 'D', 'E', 'F'];
var edges = {
    A: [{ id: 'B', w: 1 }, { id: 'D', w: 2 }],
    B: [{ id: 'A', w: 3 }, { id: 'E', w: 3 }, { id: 'C', w: 7 }],
    C: [{ id: 'B', w: 5 }, { id: 'E', w: 3 }, { id: 'F', w: 4 }],
    D: [{ id: 'A', w: 2 }],
    E: [{ id: 'B', w: 3 }, { id: 'C', w: 7 }, { id: 'F', w: 3 }],
    F: [{ id: 'C', w: 6 }, { id: 'E', w: 9 }]
}
//構建圖
var g = Graph();
g.initVertex(vertexs);
g.initEdge(edges);
//調用BFS
BFS(g, g.graph[1]);

6. 總結

着重體會

  • 1 如何用鄰接鏈表表示圖的邊
  • 2 如何用遞歸的特性實現回溯
相關文章
相關標籤/搜索