拓撲排序原理分析及js實現

1. 偏序和全序的概念

  • 1.1. 偏序

設R是集合A上的一個二元關係,若R知足下列三個性質則稱R爲A上的偏序關係
自反性:對任意x∈A,有<x,x>∈R
反對稱性:對任意的x,y∈A,若是<x,y>∈R,且<y,x>∈R,則必有x=y
傳遞性:對任意x,y,z∈A,若<x,y>∈R,<y,z>∈R,則必有<x,z>∈Rnode


通俗解釋:天然數之間的"大於等於"是一個偏序關係,例如天然數的一個子集A={1,2,3}
"大於等於"的一個子集R={<1,1>,<2,2>,<3,3>,<3,2>,<2,1>,<3,1>}對於自反的解釋是1=1,2=2,3=3;對於反對稱性,<3,2>,<2,1>,<3,1>∈R,但關係R中不存在元素<2,3>,<1,2><1,3>由於2<3,1<2,1<3,對於傳遞<3,2>,<2,1>∈R3>2,2>1因此3>1<3,1>∈R算法

  • 1.2. 全序

全序是偏序的一個子集,即集合中任意兩個元素之間都有明確的"序"的關係也就是下面的性質
徹底性:集合中任意兩個元素之間都有明確的"序"的關係,因而可知徹底性包含了自反性,任意就包含了元素和元素本身shell


通俗解釋:是偏序但不是全序,設集合A={1,2,3,b},b=2i+1;因爲b是一個複數,因此其它的三個元素都不能夠和它來比較大小segmentfault

  • 1.3. 算法的穩定性

若是咱們要對下列數組的元素按照index的大小進行排序 [{id:a,index:1},{id:b,index:1},{id:c,index:2}],咱們設第一個爲A,第二個爲B,第三個爲C, 咱們應該如何肯定A和B之間的順序呢?
因爲ABindex值相同,但AB確實是不一樣的元素,所以咱們沒法肯定他們的前後順序,即AB不可比較,因此在A,B,C三個元素組成的關係不具有全序關係。可是一個偏序關係,若是咱們默認,先出現的排在前面,那麼咱們就能比較A,B的關係了。咱們排序就有C,A,B數組

穩定的算法:是對於存在上述狀況的元素總能按照元素出現的前後順序排列的算法數據結構

不穩定的算法:是對於上述狀況,不能保證先出現的排在前面由此咱們說,直接插入排序,冒泡排序,歸併排序,基數排序是穩定的而shell排序,堆排序,快速排序直接選擇排序是不穩定的this

2. 拓撲排序

說明:本文圖的構建方法及DFS算法能夠參考 BFS,DFS 算法原理及js實現spa

圖片描述

咱們天天早上起牀後穿衣的過程能夠分爲不少步驟,例如,穿內褲,穿褲子,穿內褲必須在穿褲子以前,一樣的穿襪子必須在穿鞋子以前等等,戴手錶和其它的任何一個動做之間都沒有明顯的關係,所以放在這個線性序列中的哪裏都無所謂prototype

  • 2.1. 拓撲排序定義

對於一個有向無環圖G來講,拓撲排序就是對圖G中的全部節點的一次線性排序,該排序知足以下條件,若是圖G中包含邊(u,v),則節點u必定在v的前面,能夠將拓撲排序看做是將圖的全部節點在一條直線上水平排開code

3. Kahn算法

  • 3.1. 算法原理

對於一個有向無環AOV(頂點表示活動,邊表示優先關係)圖,咱們重複執行如下兩個步驟,直到不存在入度爲0的頂點爲止

(1)先擇一個入度爲0的頂點並輸出

(2)從圖中刪除由該節點發出的全部邊

這樣獲得的序列就是該圖的拓撲排序,若是途中有環,則輸出的頂點的數目小於圖中節點的數目

  • 3.2. 算法描述

L一個用來存放已排序頂點的List
S一個用來存放如度爲0的頂點List  
當S不爲空時執行循環執行如下步驟
    從S中移除一個頂點(沒有順序要求,隨意移除)n
    將n插入到L中
    循環遍歷從n發出的邊,對於全部的孩子節點m
        移除邊<n,m>
        若是m的入度爲0,則將m放入S中
若是循環結束後圖中還有邊則說明圖中有環
不然L是拓撲排序的結果
  • 3.3. 算法的js實現

//數據結構 鄰接鏈表-頂點
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; //節點的標識
    this.data = null; //節點的數據
    this.incoming = 0; //節點的入度
}
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.dict = {}; //字典 用來映射標節點的識符和數組中的位置
}
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.value;
            vertex.data = value.data;
            this.graph.push(vertex);
        }
        //初始化 字典
        for (let i in this.graph) {
            this.dict[this.graph[i].value] = i;
        }
    },
    //創建圖中 邊 的關係
    initEdge: function(edges) {
        for (let field in edges) {
            let index = this.dict[field]; //從字典表中找出節點在 graph 中的位置
            let vertex = this.graph[index]; //獲取節點
            vertex.edges = createLink(0, edges[field].length, edges[field], this.dict, this.graph);
        }
    }
}

//建立鏈表,返回鏈表的第一個節點
function createLink(index, len, edges, dict, vertexs) {
    if (index >= len) return null;
    let edgeNode = Edge();
    edgeNode.index = dict[edges[index].id]; //邊鏈接的節點 用在數組中的位置表示 參照字典
    vertexs[edgeNode.index].incoming = vertexs[edgeNode.index].incoming + 1; //設置節點的入度
    edgeNode.w = edges[index].w; //邊的權值
    edgeNode.sibling = createLink(++index, len, edges, dict, vertexs); //經過遞歸實現 回溯
    return edgeNode;
}
// a內褲 b襪子 c手錶 d褲子 e鞋 f腰帶 g襯衣 h領帶 i 夾克
vertexs = [{value: 'a',    data: '內褲'}, {value: 'b',    data: '襪子'}, 
{value: 'c',data: '手錶'}, {value: 'd',    data: '褲子'}, 
{value: 'e',data: '鞋'}, {value: 'f',    data: '腰帶'}, 
{value: 'g',data: '襯衣'}, {value: 'h',    data: '領帶'}, 
{value: 'i',data: '夾克'}];

var edges = {
    a: [{id: 'd', w: 1 }, {id: 'e', w: 1 }],
    b: [{id: 'e', w: 1}],
    c: [],
    d: [{id: 'e', w: 1 }, {id: 'f', w: 1 }],
    e: [],
    f: [{id: 'i', w: 1}],
    g: [{id: 'f', w: 1 }, {id: 'h', w: 1 }],
    h: [{id: 'i', w: 1}],
    i: []
}

//kahn算法
function kahn(g) {
    let s = []; //用於存放入度爲0的頂點
    let l = []; //用來存放已經排好序的頂點
    //初始化set 將圖中全部入度爲0的節點加入到set中
    for(let v of g.graph) {
        if(v.incoming==0)
            s.push(v);
    }
    while(s.length > 0) {
        let u = s.shift();
        l.push(u);
        if (u.edges == null) continue;
        let sibling = u.edges;
        while (sibling != null) {
            let index = sibling.index;
            let n = g.getNode(index);
            n.incoming = n.incoming - 1; //刪除邊
            if(n.incoming == 0)    s.push(n); //入度爲0的放入s
            sibling = sibling.sibling;
        }
    }
    return l;
}

var g = Graph();
g.initVertex(vertexs);
g.initEdge(edges);
var r = kahn(g);
console.log(r);

運行結果

圖片描述

4. 基於DFS的拓撲排序算法

  • 4.1. 算法原理

原理:拓撲排序的次序與頂點的完成時間剛好相反

對於拓撲排序,咱們要作的是保證對於任意一條邊(u,v),節點u必定出如今節點v前面。
對於DFS算法的每一個節點,咱們都有一個發現時間d,一個訪問時間f,當DFS運行完成時,對於圖中的任意一條邊(u,v),都有 u.f>v.f,因此拓撲排序的次序與頂點的完成時間剛好相反。

DFS在圖上運行時,探索到任意一條邊(u,v)時,u爲灰色,因此v要麼是白色,要麼是黑色,若是v是白色,則v必定早於u被訪問,即 u.f>v.f,當v爲黑色時,此時v已經被訪問過,而u還爲灰色,即u沒有被訪問,因此v依然早於u被訪問,仍有 u.f>v.f,因而可知上述結論成立

  • 4.2. js實現

基於以上結論,咱們用DFS實現拓撲排序,只須要在節點 的f被設置值即節點被訪問後,將其加入一個後進先出隊列中(調用unshift方法始終向數組的頭部添加新元素)L中,當DFS運行結束後,L中的元素就是通過拓撲排序的結果

//數據結構 鄰接鏈表-頂點
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; //節點的標識
    this.data = null; //節點的數據
    this.incoming = 0;
}
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.dict = {}; //字典 用來映射標節點的識符和數組中的位置
}
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.value;
            vertex.data = value.data;
            this.graph.push(vertex);
        }
        //初始化 字典
        for (let i in this.graph) {
            this.dict[this.graph[i].value] = i;
        }
    },
    //創建圖中 邊 的關係
    initEdge: function(edges) {
        for (let field in edges) {
            let index = this.dict[field]; //從字典表中找出節點在 graph 中的位置
            let vertex = this.graph[index]; //獲取節點
            vertex.edges = createLink(0, edges[field].length, edges[field], this.dict, this.graph);
        }
    }
}

//建立鏈表,返回鏈表的第一個節點
function createLink(index, len, edges, dict, vertexs) {
    if (index >= len) return null;
    let edgeNode = Edge();
    edgeNode.index = dict[edges[index].id]; //邊鏈接的節點 用在數組中的位置表示 參照字典
    vertexs[edgeNode.index].incoming = vertexs[edgeNode.index].incoming + 1; //設置節點的入度
    edgeNode.w = edges[index].w; //邊的權值
    edgeNode.sibling = createLink(++index, len, edges, dict, vertexs); //經過遞歸實現 回溯
    return edgeNode;
}
// a內褲 b襪子 c手錶 d褲子 e鞋 f腰帶 g襯衣 h領帶 i 夾克
vertexs = [{value: 'a', data: '內褲'}, {value: 'b',   data: '襪子'}, 
{value: 'c',data: '手錶'}, {value: 'd',   data: '褲子'}, 
{value: 'e',data: '鞋'}, {value: 'f',    data: '腰帶'}, 
{value: 'g',data: '襯衣'}, {value: 'h',   data: '領帶'}, 
{value: 'i',data: '夾克'}];

var edges = {
    a: [{id: 'd', w: 1 }, {id: 'e', w: 1 }],
    b: [{id: 'e', w: 1}],
    c: [],
    d: [{id: 'e', w: 1 }, {id: 'f', w: 1 }],
    e: [],
    f: [{id: 'i', w: 1}],
    g: [{id: 'f', w: 1 }, {id: 'h', w: 1 }],
    h: [{id: 'i', w: 1}],
    i: []
}

function DFS(g) {
    let t = 0;
    let l =[];
    for (let v of g.graph) {
        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; //設置完成時間
        l.unshift(v); //拓撲排序的次序與頂點的完成時間剛好相反
    }
    console.log(l)
}

var g = Graph();
g.initVertex(vertexs);
g.initEdge(edges);
DFS(g);

運行結果

圖片描述

相關文章
相關標籤/搜索