Javascript之數據結構與算法的圖(Graph)實現

簡介

廣度優先搜索算法會從指定的第一個頂點開始遍歷圖,先訪問其所有的相鄰點,就像一次訪
問圖的一層。換句話說,就是先寬後深地訪問頂點。如圖:
在這裏插入圖片描述
相關代碼:

bfs(v,callback){
    let color=this.initializeColor();
    let adjList=items.get(this).adjList;
    let queue=new Queue();
    queue.enqueue(v);
    while(!queue.empty()){
        let u=queue.dequeue();
        let neighbors=adjList.get(u);
        color[u]='grey';
        for(let i=0;i<neighbors.length;i++){
            let w=neighbors[i];
            if(color[w]=='white'){
                color[w]='grey';
                queue.enqueue(w);
            }
        }
        color[u]='black';
        if(callback){
            callback(u);
        }
    }
}

深度優先搜索將會從第一個指定的頂點開始遍歷圖,沿着路徑直到這條路徑最後一個頂
點被訪問了,接着原路回退並探索下一條路徑。換句話說,它是先深度後廣度地訪問頂點問所有節點。如圖:
在這裏插入圖片描述
相關代碼:

dfs(callback){
    let color=this.initializeColor();
    let vertices=items.get(this).vertices;
    for(let i=0;i<vertices.length;i++){
        if(color[vertices[i]]=='white'){
            items.get(this).dfsVisit(items.get(this).adjList,items.get(this).dfsVisit,vertices[i],color,callback);
        }
    }
}
dfsVisit(adjList,dfsVisit,u,color,callback){
    color[u]='grey';
    if(callback){
        callback(u);
    }
    let neighbors=adjList.get(u);
    for(let i=0;i<neighbors.length;i++){
        let w=neighbors[i];
        if(color[w]=='white'){
            dfsVisit(adjList,dfsVisit,w,color,callback);
        }
    }
    color[u]='black';
}

廣度優先搜索算法實際應用-最短路徑(非權值)

問題背景:
給定一個圖G和源頂點v,找出對每個頂點u, u和v之間最短路徑的距離(以邊的數量計)。
解決辦法:
改良bfs算法即BFS:

BFS(v){//-不加權最短路徑
    let color=this.initializeColor();
    let queue=new Queue();
    let d=[];//存路徑
    let pred=[];
    queue.enqueue(v);
    let vertices=items.get(this).vertices;
    let adjList=items.get(this).adjList;
    for(let i=0;i<vertices.length;i++){
        d[vertices[i]]=0;
        pred[vertices[i]]=null;
    }
    while(!queue.empty()){
        let u=queue.dequeue();
        let neighbors=adjList.get(u);
        color[u]='grey';
        for(let i=0;i<neighbors.length;i++){
            let w=neighbors[i];
            if(color[w]=='white'){
                color[w]='grey';
                d[w]=d[u]+1;
                pred[w]=u;
                queue.enqueue(w);
            }
        }
        color[u]='black';
    }
    return {distances:d,predecessors:pred}
}

測試代碼:

var graph = new Graph();
var myVertices = ['A','B','C','D','E','F','G','H','I']; //{7}
for (var i=0; i<myVertices.length; i++){ //{8}
graph.addVertex(myVertices[i]);
}
graph.addEdge('A', 'B'); //{9}
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');
var shortestPathA = graph.BFS(myVertices[0]);
console.log(shortestPathA);
//輸出結果:
distances: [A: 0, B: 1, C: 1, D: 1, E: 2, F: 2, G: 2, H: 2 , I: 3],
predecessors: [A: null, B: "A", C: "A", D: "A", E: "B", F: "B", G: "C", H: "D", I: "E"]
//顯示頂點到其他頂點的路徑
var fromVertex = myVertices[0];
for (var i=1; i<myVertices.length; i++){ 
	var toVertex = myVertices[i],
		path = new Stack(); 
	for (var v=toVertex; v!== fromVertex;v=shortestPathA.predecessors[v]) { 
		path.push(v); 
	}
	path.push(fromVertex); 
	var s = path.pop(); 
	while (!path.isEmpty()){ 
		s += ' - ' + path.pop(); 
	}
	console.log(s); 
}

深度優先搜索算法實際應用-拓撲排序(有向無環圖)

編排一些任務或步驟的執行順序時,這稱爲拓撲排序
例如
當我們開始學習一門計算機科學課程,在學習某些知識之前得按順序完成一些知識儲備(你不可以在上算法前先上算法)。當我們在開發一個項目時,需要按順序執行一些步驟,例如,首先我們得從客戶那裏得到需求,接着開發客戶要求的東西,最後交付項目。
解決辦法
改良版dfs即DFS:

DFS(){
   let color=this.initializeColor();
    let d=[];
    let f=[];
    let p=[];
    items.get(this).time=0;
    let vertices=items.get(this).vertices;
    for(let i=0;i<vertices.length;i++){
        f[vertices[i]]=0;
        d[vertices[i]]=0;
        p[vertices[i]]=null;
    }
    for(let i=0;i<vertices.length;i++){
        if(color[vertices[i]]=='white'){
            items.get(this).DFSVisit(items.get(this).time,items.get(this).adjList,items.get(this).DFSVisit,vertices[i],color,d,f,p);
        }
    }
    return {
        discovered:d,
        finished:f,
        predecessors:p
    }
}

Dijkstra 算法

Dijkstra算法是一種計算從單個源到所有其他源的最短路徑的貪心算法。
例如:
在這裏插入圖片描述
將圖上信息轉化爲鄰接矩陣:

graph.graph = [[0, 2, 4, 0, 0, 0],
[2, 0, 2, 4, 2, 0],
[4, 2, 0, 0, 3, 0],
[0, 4, 0, 0, 3, 2],
[0, 2, 3, 3, 0, 2],
[0, 0, 0, 2, 2, 0]];

相關代碼:

//Dijkstra算法
dijkstra(src){
    let dist=[];
    let INF=Number.MAX_SAFE_INTEGER;
    let visited=[];
    let length=this.graph.length;
    for(let i=0;i<length;i++){//把所有的距離( dist)初始化爲無限大( JavaScript最大的數INF = Number.MAX_SAFE_INTEGER),將visited[]初始化爲false
        dist[i]=INF;
        visited[i]=false;
    }
    dist[src]=0;//把源頂點到自己的距離設爲0
    for(let i=0;i<length-1;i++){
        let u=items.get(this).minDistance(dist,visited);//從尚未處理的頂點中選出距離最近的頂點
        visited[u]=true;//把選出的頂點標爲visited,以免重複計算
        for(let v=0;v<length;v++){//查找更短的路徑
            if(!visited[v]&&this.graph[u][v]!=0&&dist[u]!=INF&&dist[u]+this.graph[u][v]<dist[v]){
                dist[v]=dist[u]+this.graph[u][v];//更新最短路徑的值
            }
        }
    }
    return dist;
}
minDistance(dist,visited){
    let INF=Number.MAX_SAFE_INTEGER;
    let min=INF;
    let minIndex=-1;
    for(let v=0;v<dist.length;v++){
        if(visited[v]==false&&dist[v]<=min){
            min=dist[v];
            minIndex=v;
        }
    }
    return minIndex;
}

Floyd-Warshall 算法

Floyd-Warshall算法是一種計算圖中所有最短路徑的動態規劃算法

//Floyd-Warshall算法
floydWarshall(){
    let INF=Number.MAX_SAFE_INTEGER;
    let dist=[],
        length=this.graph.length;
    for(let i=0;i<length;i++){//首先,把dist數組初始化爲每個頂點之間的權值,因爲i到j可能的最短距離就是這些頂點間的權值。
        dist[i]=[];
        for(let j=0;j<length;j++){
            if(this.graph[i][j]==0&&i!=j){
                dist[i][j]=INF;
            }else{
                dist[i][j]=this.graph[i][j];
            }
            
        }
    }
    for(let k=0;k<length;k++){//通過k,得到i途徑頂點0至k,到達j的最短路徑。
        for(let i=0;i<length;i++){
            for(let j=0;j<length;j++){
                if(dist[i][k]+dist[k][j]<dist[i][j]){//通過k,得到i途徑頂點0至k,到達j的最短路徑。
                    dist[i][j]=dist[i][k]+dist[k][j];//如果是更短的路徑,則更新最短路徑的值
                }
            }
        }
    }
    return dist;
}

最小生成樹(MST)-Prim算法

實用背景
你的公司有幾間辦公室,要以最低的成本實現辦公室電話線路相互連通,以節省資金;
在n個島嶼之間建造橋樑,想用最低的成本實現所有島嶼相互連通。
圖結構如下
在這裏插入圖片描述
Prim算法是一種求解加權無向連通圖的MST問題的貪心算法。它能找出一個邊的子集,使得
其構成的樹包含圖中所有頂點,且邊的權值之和最小。
相關代碼

prim(){
    let parent=[],
        key=[],
        visited=[],
        length=this.graph.length,
        INF=Number.MAX_SAFE_INTEGER;//代表無窮大即無路
    for(let i=0;i<this.graph.length;i++){
        for(let j=0;j<this.graph[i].length;j++){
            if(this.graph[i][j]==0&&i!=j){
                this.graph[i][j]=INF;
            }
        }
    }
    for(let i=0;i<length;i++){
        key[i]=INF;
        visited[i]=false;
    }
    key[0]=0;
    parent[0]=-1;//選擇第一個key作爲第一個頂點,同時,因爲第一個頂點總是MST的根節點,所以parent[0] = -1。
    for(let i=0;i<length-1;i++){
        let u=items.get(this).minKey(key,visited);//從未處理的頂點集合中選出key值最小的頂點(與Dijkstra算法中使用的函數一樣,只是名字不同)
        visited[u]=true;
        for(let v=0;v<length;v++){
            if(this.graph[u][v]&&visited[v]==false&&this.graph[u][v]<key[v]){
                parent[v]=u;
                key[v]=this.graph[u][v];
            }
        }
    }
    //console.log(key)
    return {
        parent:parent,
        key:key
    };
}

全部代碼

let Dictionary=require('./Dictionary')
let Queue=require('./Queue')
let Stack=require('./Stack')
let items=new WeakMap();
/** 
* 白色:表示該頂點還沒有被訪問。
* 灰色:表示該頂點被訪問過,但並未被探索過。
* 黑色:表示該頂點被訪問過且被完全探索過
*/
class Graph{
    constructor(){
        items.set(this,{
            "vertices":[],
            "adjList":new Dictionary(),
            "time":0,
            "dfsVisit":function(adjList,dfsVisit,u,color,callback){
                color[u]='grey';
                if(callback){
                    callback(u);
                }
                let neighbors=adjList.get(u);
                for(let i=0;i<neighbors.length;i++){
                    let w=neighbors[i];
                    if(color[w]=='white'){
                        dfsVisit(adjList,dfsVisit,w,color,callback);
                    }
                }
                color[u]='black';
            },
            "DFSVisit":function(time,adjList,DFSVisit,u,color,d,f,p){
                console.log('discovered '+u);
                color[u]='grey';
                d[u]=++time;
                let neighbors=adjList.get(u);
                for(let i=0;i<neighbors.length;i++){
                    let w=neighbors[i];
                    if(color[w]=='white'){
                        p[w]=u;
                        DFSVisit(time,adjList,DFSVisit,w,color,d,f,p);
                    }
                }
                color[u]='black';
                f[u]=++time;
                console.log('explored '+u);
            },
            "minDistance":function(dist,visited){
                let INF=Number.MAX_SAFE_INTEGER;
                let min=INF;
                let minIndex=-1;
                for(let v=0;v<dist.length;v++){
                    if(visited[v]==false&&dist[v]<=min){
                        min=dist[v];
                        minIndex=v;
                    }
                }
                return minIndex;
            },
            "minKey":function(key,visited){
                let INF=Number.MAX_SAFE_INTEGER;
                let min=INF;
                let minIndex=-1;
                for(let v=0;v<key.length;v++){
                    if(visited[v]==false&&key[v]<=min){
                        min=key[v];
                        minIndex=v;
                    }
                }
                return minIndex;
            }
        });//私有屬性
        this.graph=[];//dijkstra算法使用
    }
    addVertex(v){
        items.get(this).vertices.push(v);
        items.get(this).adjList.set(v,[]);
    }
    addEdge(v,w){
        items.get(this).adjList.get(v).push(w);
        items.get(this).adjList.get(w).push(v);
    }
    toString(){
        let s='';
        let vertices=items.get(this).vertices;
        for(let i=0;i<vertices.length;i++){
            s+=vertices[i]+'->';
            let neighbors=items.get(this).adjList.get(vertices[i])
            for(let j=0;j<neighbors.length;j++){
                s+=neighbors[j]+' ';
            }
            s+='\n';
        }
        return s;
    }
    //初始化節點顏色
    initializeColor(){
        let color=[];
        let vertices=items.get(this).vertices;
        for(let i=0;i<vertices.length;i++){
            color[vertices[i]]='white';
        }
        return color;
    }
    //廣度優先搜索
    bfs(v,callback){
        let color=this.initializeColor();
        let adjList=items.get(this).adjList;
        let queue=new Queue();
        queue.enqueue(v);
        while(!queue.empty()){
            let u=queue.dequeue();
            let neighbors=adjList.get(u);
            color[u]='grey';
            for(let i=0;i<neighbors.length;i++){
                let w=neighbors[i];
                if(color[w]=='white'){
                    color[w]='grey';
                    queue.enqueue(w);
                }
            }
            color[u]='black';
            if(callback){
                callback(u);
            }
        }
    }
    BFS(v){//-不加權最短路徑
        let color=this.initializeColor();
        let queue=new Queue();
        let d=[];//存路徑
        let pred=[];
        queue.enqueue(v);
        let vertices=items.get(this).vertices;
        let adjList=items.get(this).adjList;
        for(let i=0;i<vertices.length;i++){
            d[vertices[i]]=0;
            pred[vertices[i]]=null;
        }
        while(!queue.empty()){
            let u=queue.dequeue();
            let neighbors=adjList.get(u);
            color[u]='grey';
            for(let i=0;i<neighbors.length;i++){
                let w=neighbors[i];
                if(color[w]=='white'){
                    color[w]='grey';
                    d[w]=d[u]+1;
                    pred[w]=u;
                    queue.enqueue(w);
                }
            }
            color[u]='black';
        }
        return {distances:d,predecessors:pred}
    }
    //深度優先搜索
    dfs(callback){
        let color=this.initializeColor();
        let vertices=items.get(this).vertices;
        for(let i=0;i<vertices.length;i++){
            if(color[vertices[i]]=='white'){
                items.get(this).dfsVisit(items.get(this).adjList,items.get(this).dfsVisit,vertices[i],color,callback);
            }
        }
    }
    //探索深度優先算法
    DFS(){
        let color=this.initializeColor();
        let d=[];
        let f=[];
        let p=[];
        items.get(this).time=0;
        let vertices=items.get(this).vertices;
        for(let i=0;i<vertices.length;i++){
            f[vertices[i]]=0;
            d[vertices[i]]=0;
            p[vertices[i]]=null;
        }
        for(let i=0;i<vertices.length;i++){
            if(color[vertices[i]]=='white'){
                items.get(this).DFSVisit(items.get(this).time,items.get(this).adjList,items.get(this).DFSVisit,vertices[i],color,d,f,p);
            }
        }
        return {
            discovered:d,
            finished:f,
            predecessors:p
        }
    }
    //Dijkstra算法
    dijkstra(src){
        let dist=[];
        let INF=Number.MAX_SAFE_INTEGER;
        let visited=[];
        let length=this.graph.length;
        for(let i=0;i<length;i++){//把所有的距離( dist)初始化爲無限大( JavaScript最大的數INF = Number.MAX_SAFE_INTEGER),將visited[]初始化爲false
            dist[i]=INF;
            visited[i]=false;
        }
        dist[src]=0;//把源頂點到自己的距離設爲0
        for(let i=0;i<length-1;i++){
            let u=items.get(this).minDistance(dist,visited);//從尚未處理的頂點中選出距離最近的頂點
            visited[u]=true;//把選出的頂點標爲visited,以免重複計算
            for(let v=0;v<length;v++){//查找更短的路徑
                if(!visited[v]&&this.graph[u][v]!=0&&dist[u]!=INF&&dist[u]+this.graph[u][v]<dist[v]){
                    dist[v]=dist[u]+this.graph[u][v];//更新最短路徑的值
                }
            }
        }
        return dist;
    }
    //Floyd-Warshall算法
    floydWarshall(){
        let INF=Number.MAX_SAFE_INTEGER;
        let dist=[],
            length=this.graph.length;
        for(let i=0;i<length;i++){//首先,把dist數組初始化爲每個頂點之間的權值,因爲i到j可能的最短距離就是這些頂點間的權值。
            dist[i]=[];
            for(let j=0;j<length;j++){
                if(this.graph[i][j]==0&&i!=j){
                    dist[i][j]=INF;
                }else{
                    dist[i][j]=this.graph[i][j];
                }
                
            }
        }
        for(let k=0;k<length;k++){//通過k,得到i途徑頂點0至k,到達j的最短路徑。
            for(let i=0;i<length;i++){
                for(let j=0;j<length;j++){
                    if(dist[i][k]+dist[k][j]<dist[i][j]){//通過k,得到i途徑頂點0至k,到達j的最短路徑。
                        dist[i][j]=dist[i][k]+dist[k][j];//如果是更短的路徑,則更新最短路徑的值
                    }
                }
            }
        }
        return dist;
    }
    //最小生成樹
    //Prim算法
    prim(){
        let parent=[],
            key=[],
            visited=[],
            length=this.graph.length,
            INF=Number.MAX_SAFE_INTEGER;
        for(let i=0;i<this.graph.length;i++){
            for(let j=0;j<this.graph[i].length;j++){
                if(this.graph[i][j]==0&&i!=j){
                    this.graph[i][j]=INF;
                }
            }
        }
        for(let i=0;i<length;i++){
            key[i]=INF;
            visited[i]=false;
        }
        key[0]=0;
        parent[0]=-1;//選擇第一個key作爲第一個頂點,同時,因爲第一個頂點總是MST的根節點,所以parent[0] = -1。
        for(let i=0;i<length-1;i++){
            let u=items.get(this).minKey(key,visited);//從未處理的頂點集合中選出key值最小的頂點(與Dijkstra算法中使用的函數一樣,只是名字不同)
            visited[u]=true;
            for(let v=0;v<length;v++){
                if(this.graph[u][v]&&visited[v]==false&&this.graph[u][v]<key[v]){
                    parent[v]=u;
                    key[v]=this.graph[u][v];
                }
            }
        }
        //console.log(key)
        return {
            parent:parent,
            key:key
        };
    }
}
let graph=new Graph();
// let myVertices=['A','B','C','E','D','F','G','H','I'];
// for(let i=0;i<myVertices.length;i++){
//     graph.addVertex(myVertices[i]);
// }
// graph.addEdge('A', 'B'); //{9}
// 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())
// graph.bfs(myVertices[0],function(result){
//     console.log('Visited vertex:',result)
// })
// let shortestPathA=graph.BFS(myVertices[0]);
// console.log(shortestPathA)
// let fromVertex=myVertices[0];
// for(let i=1;i<myVertices.length;i++){
//     let toVertex=myVertices[i];
//     let path=new Stack();
//     for(let v=toVertex;v!=fromVertex;v=shortestPathA.predecessors[v]){
//         path.push(v);
//     }
//     path.push(fromVertex);
//     let s=path.pop();
//     while(!path.empty()){
//         s+='-'+path.pop();
//     }
//     console.log(s)
// }

// graph.dfs(function(result){
//     console.log('visited vertex:',result)
// })

//拓撲排序
// let myVertices=['A','B','C','D','E','F'];
// for(let i=0;i<myVertices.length;i++){
//     graph.addVertex(myVertices[i]);
// }
// graph.addEdge('A', 'C');
// graph.addEdge('A', 'D');
// graph.addEdge('B', 'D');
// graph.addEdge('B', 'E');
// graph.addEdge('C', 'F');
// graph.addEdge('F', 'E');
// var result = graph.DFS();
// console.log(result)

//dijkstra算法
// graph.graph=[[0, 2, 4, 0, 0, 0],
// [0, 0, 1, 4, 2, 0],
// [0, 0, 0, 0, 3, 0],
// [0, 0, 0, 0, 0, 2],
// [0, 0, 0, 3, 0, 2],
// [0, 0, 0, 0, 0, 0]];
//console.log(graph.dijkstra(0))
//floyd-warshall算法
//console.log(graph.floydWarshall());

//prim
graph.graph = [[0, 2, 4, 0, 0, 0],
[2, 0, 2, 4, 2, 0],
[4, 2, 0, 0, 3, 0],
[0, 4, 0, 0, 3, 2],
[0, 2, 3, 3, 0, 2],
[0, 0, 0, 2, 2, 0]];
let json=graph.prim();
console.log("Edge\t"+"Weight");
let key=json.key;
let parent=json.parent;
for(let i=1;i<key.length;i++){
    console.log(parent[i]+"-"+i+"\t"+key[i]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章