數據結構 - JS實現圖數據結構

圖的數據結構,操作;數據結構用途。

結構說明

由多個頂點的結合及和點之間的關係集合組成的數據結構。

概念:

  • 頂點的度 - 指與該頂點相關聯的邊的條數。
  • 路徑 - 從一個頂點到另一個頂點的路徑,存在0個或多個。
  • 權 - 路徑上附屬攜帶了數據信息。
  • 連通圖指任意兩個頂點之間存在關係邊;

基本數據結構:

設計實現

圖的存儲結構主要有鄰接矩陣、鄰接表。

  • 圖中頂點數小且邊較多時,採用鄰接矩陣存儲,即使用二維數組存儲
  • 頂點數多且邊較少時,採用鄰接表存儲,即三元組鏈表存儲

實現鄰接矩陣存儲結構

結果說明:

  1. 頂點信息存儲在順序表中;JS 鏈表表實現 - 順序表部分,查看此地址

  2. 圖的邊信息存儲在二維數組中;

  3. 測試:
    在這裏插入圖片描述
    測試代碼:

    // 測試
    let graph = new Graph();
    
    graph.init(5);
    graph.inserPoint(1);
    graph.inserPoint(2);
    graph.inserPoint(3);
    graph.inserPoint(4);
    graph.inserPoint(5);
    graph.insertBorder(0,2,1);
    graph.insertBorder(0,1,1);
    graph.insertBorder(0,4,1);
    graph.insertBorder(0,3,1);
    graph.insertBorder(1,3,1);
    graph.insertBorder(2,4,1);
    console.log(graph.borderArr,graph.pointList);
    
function Graph(){
    // 存儲頂點的順序表
    this.pointList = new queueList();
    // 存儲邊的二維數組
    this.borderArr = new Array();
    //
    this.borderLen = 0;
}
Graph.prototype = {
    init:function(n){
        // 需要初始化二維數組
        this.borderArr = new Array(n);
        for(let start=0;start<n;start++){
            // 如果默認 0 爲兩個頂點之間無關係邊時的標識;後續不拿 0 作爲權值
            this.borderArr[start] = new Array(n).fill(0);
        }
        // 初始化 n 個頂點的
        // for(let i=0;i<n;i++){
        //     for(let j=0;j<n;j++){
        //         if(i == j){
        //             this.borderArr[i][j] = 0;
        //         }
        //     }
        // }
        this.borderLen = 1;
    },
    inserPoint:function(p){
        // 新增一個頂點
        this.pointList.insert(this.pointList.size,p);
    },
    insertBorder:function(p1,p2,val){
        // 插入一條有向邊;插入無向邊,插入兩條有向邊操作;
        // p1 p2 指定一條邊,有關係連接
        // val 爲這條邊的權值
        if(p1<0||p1>this.pointList.size || p2<0 || p2>this.pointList.size){
            console.error("參數不合法!");
            return;
        }
        this.borderArr[p1][p2] = val;
        this.borderArr[p2][p1] = val;
        this.borderLen++;
    },
    deleteBorder:function(p1,p2){
        // 刪除一條邊
        if(p1<0||p1>this.pointList.size || p2<0 || p2>this.pointList.size || p1 ===p2){
            console.error("參數合法!");
            return;
        }
        if(this.borderArr[p1][p2] == Infinity || p1 ==p2){
            console.error("該邊不存在!");
            return;
        }
        this.borderArr[p1][p2] = Infinity;
        this.borderLen--;
    },
    getFirstPoint:function(p){
        // 獲取指定點的鄰接點
        if(p<0 || p>=this.pointList.size){
            console.error("參數不合法!");
            return;
        }
        for(let i=0;i<this.pointList.size;i++){
            if(this.borderArr[p][i]>0 && this.borderArr[p][i]<Infinity){
                return i;
            }
        }
        return -1;
    }
}

實現鄰接表存儲結構:

結構說明:

  1. 頂點信息存儲在數組中;

  2. 頂點數組元素爲一個指定的數據對象;

    function Point(){
    	// 頂點
    	this.point = null;
    	// 下標值,指定
    	this.index = null;
    	// 指向邊,爲一個鏈表結構
    	this.border = null;
    }
    
  3. 數組元素中執向邊爲一個鏈表結構,連接有關係的頂點;JS 實現鏈表 - 單鏈表部分,點此查看代碼

  4. 測試:無向圖可更改插入的方法,將 p2 和 p1 也建立關係。
    在這裏插入圖片描述

    // 測試
    let graph = new GraphList();
    // 初始化  默認大小 爲 10 ;可改爲動態傳入大小
    graph.init();
    // 插入頂點
    graph.insertPoint(0,"A");
    graph.insertPoint(1,"B");
    graph.insertPoint(2,"C");
    graph.insertPoint(3,"D");
    graph.insertPoint(4,"E");
    graph.insertPoint(5,"F");
    // 插入關係邊
    graph.insertBorder(0,2);
    graph.insertBorder(0,1);
    graph.insertBorder(0,3);
    graph.insertBorder(2,4);
    graph.insertBorder(3,5);
    graph.insertBorder(0,5);
    console.log(JSON.stringify(graph.pointArr));
    

代碼實現:邊的關係單鏈表是帶有頭部head

// 定義頂點的最大個數
const MAX_POINT = 10;
// 鄰接表存儲結構
function GraphList(){
    // 頂點信息存儲的數組
    this.pointArr = [];
    // 頂點個數
    this.pointLen = 0;
    // 邊的個數
    this.borderLen = 0;
}
GraphList.prototype = {
    init(){
        // 初始化鄰接表結構
        for(let i=0;i<MAX_POINT;i++){
            let point = new Point();
            // 頂點下標
            point.index = i;
            // 頂點邊初始化爲單鏈表結構對象
            point.border = new MyLinkedList();
            point.border.init();
            this.pointArr.push(point);
        }
    },
    insertPoint(i,p){
        // 在指定位置 i 插入頂點 p 
        if(i>=0&&i<MAX_POINT){
            this.pointArr[i].point = p;
            this.pointLen++; 
        }else{
            console.error("參數不合法!");
        }
    },
    insertBorder(p1,p2){
        // 指定頂點 p1 p2 建立邊關係
        if(p1<0 || p1>=this.pointLen || p2<0 || p2>=this.pointLen){
            console.error("參數不合法!");
            return;
        }
        // let border = new Node();
        // border.val = p2;
        // border.next = this.pointArr[p1].border;
        // this.pointArr[p1].border = border;
        
        // 調用鏈表自己的方法,實現頭部插入
        this.pointArr[p1].border.addAtIndex(0,p2);

        this.borderLen++;
    },
    deleteBorder(p1,p2){
        // 刪除一條邊 p1 p2 的頂點關係
        if(p1<0 || p1>this.pointLen || p2<0 || p2>this.pointLen){
            console.error("參數不合法!");
            return;
        }
        // 當前的頂點關係邊的 鏈表結構
        let pre = null;
        let curr = this.pointArr[p1].border;
        while(curr!=null && curr.val!==p2){
            // 無關係邊,或者 curr就是需要處理的邊
            pre = curr;
            curr = curr.next;
        }
        // 刪除操作
        if(curr!==null && curr.val === p2 && pre ==null){
            // p2 是 p1 的鄰接邊 ,且是第一個元素
            this.pointArr[p1].border = curr.next;
            this.borderLen--;
        }else if(curr!==null && curr.val===p2 && pre!==null){
            // p2 不是 p1 的鏈表裏的第一個元素
            pre.next = curr.next;
            this.borderLen--;
        }else{
            console.error(" p1/p2 無關係邊");
        }

    },
    getFirstBorder(p){
        // 獲取頂點 p 的第一個鄰接邊
        if(p<0 || p>this.pointLen){
            console.error("參數不合法!");
            return;
        }
        let p = this.pointArr[p].border;
        if(p!==null){
            return p.val;
        }
        return false;
    }
}

結構應用與算法

需要注意的問題:

  1. 圖的結構沒有首尾之分,初始是 需要 指定訪問的第一個節點。
  2. 需要處理死循環的問題,頂點關係之間可能會存在迴路;
  3. 確保定點的鄰接節點可被訪問;
深度優先遍歷(DFS)

指定訪問的頂點,並遞歸訪問他的鄰接點。
說明:

  1. 訪問頂點 p , 標記頂點p已被訪問。
  2. 訪問頂點 p的第一個鄰接節點 pn;
  3. pn 存在,繼續執行;不存在則結束;
  4. pn 未被訪問,則遞歸遍歷訪問 pn;
  5. 查找 p 的下一個鄰接點 pm; 轉步驟 3
廣度優先遍歷(BFS)

分層搜索的過程;按照路徑長度,從指定頂點開始訪問。
說明:

  1. 訪問指定頂點 p,標記已被訪問;頂點 p 入隊列;
  2. 若隊列非空,則繼續執行;否則結束;
  3. 取隊列數據 p1;查找 p1 的第一個鄰接點 pn
  4. 如果 pn 不存在,則轉到 3;否則循環執行:
    1. pn違背訪問,則訪問 pn ,標記已被訪問;
    2. pn 加入隊列。
    3. 查找p1 的下一個頂點pn,轉到 步驟 6
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章