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