拓撲排序詳解與實現

@(目錄)java

介紹

拓撲排序,不少人均可能據說可是不瞭解的一種算法。或許不少人只知道它是圖論的一種排序,至於幹什麼的不清楚。又或許不少人可能還會認爲它是一種啥排序。而實質它是對有向圖的頂點排成一個線性序列node

至於定義,百科上是這麼說的:算法

對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中全部頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊<u,v>∈E(G),則u在線性序列中出如今v以前。一般,這樣的線性序列稱爲知足拓撲次序(Topological Order)的序列,簡稱拓撲序列。簡單的說,由某個集合上的一個偏序獲得該集合上的一個全序,這個操做稱之爲拓撲排序spring

爲何會有拓撲排序?拓撲排序有何做用?數組

舉個例子,學習java系列的教程
代號 | 科目| 學前需掌握|
-------- | ----- | --|
A1 | javaSE|
A2 | html|
A3 | Jsp|A1,A2
A4 | servlet|A1
A5 | ssm|A3,A4
A6 | springboot|A5
就好比學習java系類(部分)從java基礎,到jsp/servlet,到ssm,到springboot,springcloud等是個按部就班且有依賴的過程。在jsp學習要首先掌握java基礎html基礎。學習框架要掌握jsp/servlet和jdbc之類才行。那麼,這個學習過程即構成一個拓撲序列。固然這個序列也不惟一你能夠對不關聯的學科隨意選擇順序(好比html和java能夠隨便先開始哪個。)springboot

那上述序列能夠簡單表示爲:框架

在這裏插入圖片描述

其中五種均爲能夠選擇的學習方案,對課程安排能夠有參考做用,固然,五個都是拓撲序列。只是選擇的策略不一樣!jsp

一些其餘注意:學習

DGA:有向無環圖
AOV網:數據在頂點 能夠理解爲面向對象
AOE網:數據在邊上,能夠理解爲面向過程!

而咱們通俗一點的說法,就是按照某種規則將這個圖的頂點取出來,這些頂點可以表示什麼或者有什麼聯繫

規則

  • 圖中每一個頂點只出現一次
  • A在B前面,則不存在B在A前面的路徑。(不能成環!!!!)
  • 頂點的順序是保證全部指向它的下個節點在被指節點前面!(例如A—>B—>C那麼A必定在B前面,B必定在C前面)。因此,這個核心規則下只要知足便可,因此拓撲排序序列不必定惟一

拓撲排序算法分析

在這裏插入圖片描述
正常步驟爲(方法不必定惟一)

  • 從DGA圖中找到一個沒有前驅的頂點輸出。(能夠遍歷,也能夠用優先隊列維護)
  • 刪除以這個點爲起點的邊。(它的指向的邊刪除,爲了找到下個沒有前驅的頂點)
  • 重複上述,直到最後一個頂點被輸出。若是還有頂點未被輸出,則說明有環!

對於上圖的簡單序列,能夠簡單描述步驟爲:

  • 1:刪除1或2輸出
    在這裏插入圖片描述
  • 2:刪除2或3以及對應邊
    在這裏插入圖片描述
  • 3:刪除3或者4以及對應邊在這裏插入圖片描述
  • 3:重複以上規則步驟
    在這裏插入圖片描述

這樣就完成一次拓撲排序,獲得一個拓撲序列,可是這個序列並不惟一!從過程當中也看到有不少選擇方案,具體獲得結果看你算法的設計了。但只要知足便是拓撲排序序列。

另外觀察 1 2 4 3 6 5 7 9這個序列知足咱們所說的有關係的節點指向的在前面,被指向的在後面。若是徹底不要緊那不必定先後(例如1,2)

拓撲排序代碼實現

對於拓撲排序,如何用代碼實現呢?對於拓撲排序,雖然在上面詳細介紹了思路和流程,也很通俗易懂。可是實際上代碼的實現仍是很須要斟酌的,如何在空間和時間上可以獲得較好的平衡且取得較好的效率?

首先要考慮存儲。對於節點,首先他有聯通點這麼多屬性。遇到稀疏矩陣仍是用鄰接表比較好。由於一個節點的指向節點較少,用鄰接矩陣較浪費資源

另外,若是是1,2,3,4,5,6這樣的序列求拓撲排序,咱們能夠考慮用數組,可是若是遇到1,2,88,9999相似數據,能夠考慮用map中轉一下。那麼,

咱們具體的代碼思想爲:

  • 新建node類,包含節點數值和它的指向(這裏直接用list集合替代鏈表了)
  • 一個數組包含node(這裏默認編號較集中)。初始化,添加每一個節點指向的時候同時被指的節點入度+1!(A—>C)那麼C的入度+1;
  • 掃描一遍全部node。將全部入度爲0的點加入一個棧(隊列)
  • 當棧(隊列)不空的時候,拋出其中任意一個node(棧就是尾,隊就是頭,順序無所謂,上面分析了只要同時入度爲零能夠隨便選擇順序)。將node輸出,而且node指向的全部元素入度減一若是某個點的入度被減爲0,那麼就將它加入棧(隊列)
  • 重複上述操做,直到棧爲空。

這裏主要是利用棧或者隊列儲存入度只爲0的節點,只須要初次掃描表將入度爲0的放入棧(隊列)中。

  • 這裏你或許會問爲何。
  • 由於節點之間是有相關性的,一個節點若想入度爲零,那麼它的父節點(指向節點)確定在它爲0前入度爲0,拆除關聯箭頭。從父節點角度,它的此次拆除聯繫,可能致使被指向的入讀爲0,也可能不爲0(還有其餘節點指向兒子)

至於具體demo:

package 圖論;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Stack;

public class tuopu {
    static class node
    {
        int value;
        List<Integer> next;
        public node(int value) {
            this.value=value;
            next=new ArrayList<Integer>();
        }
        public void setnext(List<Integer>list) {
            this.next=list;
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        node []nodes=new node[9];//儲存節點
        int a[]=new int[9];//儲存入度
        List<Integer>list[]=new ArrayList[10];//臨時空間,爲了存儲指向的集合
        for(int i=1;i<9;i++)
        {
            nodes[i]=new node(i);
            list[i]=new ArrayList<Integer>();
        }
        initmap(nodes,list,a);
        
        //主要流程
        //Queue<node>q1=new ArrayDeque<node>();
        Stack<node>s1=new Stack<node>();
        for(int i=1;i<9;i++)
        {
            //System.out.print(nodes[i].next.size()+" 55 ");
            //System.out.println(a[i]);
            if(a[i]==0) {s1.add(nodes[i]);}
            
        }
        while(!s1.isEmpty())
        {
            node n1=s1.pop();//拋出輸出
            
            System.out.print(n1.value+" ");
            
            List<Integer>next=n1.next;
            for(int i=0;i<next.size();i++)
            {
                a[next.get(i)]--;//入度減一
                if(a[next.get(i)]==0)//若是入度爲0
                {
                    s1.add(nodes[next.get(i)]);
                }
            }
        }
    }

    private static void initmap(node[] nodes, List<Integer>[] list, int[] a) {
        list[1].add(3);
        nodes[1].setnext(list[1]);
        a[3]++;
        list[2].add(4);list[2].add(6);
        nodes[2].setnext(list[2]);
        a[4]++;a[6]++;
        list[3].add(5);
        nodes[3].setnext(list[3]);
        a[5]++;
        list[4].add(5);list[4].add(6);
        nodes[4].setnext(list[4]);
        a[5]++;a[6]++;
        list[5].add(7);
        nodes[5].setnext(list[5]);
        a[7]++;
        list[6].add(8);
        nodes[6].setnext(list[6]);
        a[8]++;
        list[7].add(8);
        nodes[7].setnext(list[7]);
        a[8]++;
        
    }
}

輸出結果

2 4 6 1 3 5 7 8

在這裏插入圖片描述
固然,上面說過用棧和隊列均可以!若是使用隊列就會獲得1 2 3 4 5 6 7 8的拓撲序列

至於圖的構造,由於沒有條件可能效率並不高,算法也可能不太完美,若有優化錯誤還請大佬指正!

另外,還請各位大佬動動小手 點贊、關注(bigsai) 一波啊!謝謝🙏

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息