js實現圖的遍歷之廣度優先搜尋
阿新 • • 發佈:2020-07-25
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); }