啟動優化 - 有向無環圖
前言
說到 Android 啟動優化,大家第一時間可能會想到非同步載入。將耗時任務放到子執行緒載入,等到所有載入任務載入完成之後,再進入首頁。
多執行緒非同步載入方案確實是 ok 的。但如果遇到前後依賴的關係呢。比如任務2 依賴於任務 1,這時候要怎麼解決呢。
最簡單的方案是將任務1 丟到主執行緒載入,然後再啟動多執行緒非同步載入。
如果遇到更復雜的依賴呢。
任務3 依賴於任務 2, 任務 2 依賴於任務 1 呢,這時候你要怎麼解決。更復雜的依賴關係呢
總不能將任務 2,任務 3 都放到主執行緒載入吧,這樣多執行緒載入的意義就不大了。
有沒有更好的方案呢?
答案肯定是有的,使用有向無環圖。它可以完美解決先後依賴關係。
重要概念
有向無環圖(Directed Acyclic Graph, DAG)是有向圖的一種,字面意思的理解就是圖中沒有環。常常被用來表示事件之間的驅動依賴關係,管理任務之間的排程。
頂點:圖中的一個點,比如頂點 1,頂點 2。
邊:連線兩個頂點的線段叫做邊,edge。
入度:代表當前有多少邊指向它。
在上圖中,頂掉 1 的入度是 0,因為沒有任何邊指向它。頂掉 2 的入度是 1, 因為 頂掉 1 指向 頂掉 2. 同理可得出 5 的入度是 2,因為頂掉 4 和頂點 3 指向它
拓撲排序:拓撲排序是對一個有向圖構造拓撲序列的過程。它具有如下特點。
- 每個頂點出現且只出現一次。
- 若存在一條從頂點 A 到頂點 B 的路徑,那麼在序列中頂點 A 出現在頂點 B 的前面
由於有這個特點,因此常常用有向無環圖的資料結構用來解決依賴關係。
上圖中,拓撲排序之後,任務2肯定排在任務1之後,因為任務2依賴 任務1, 任務3肯定在任務2之後,因為任務3依賴任務2。
拓撲排序一般有兩種演算法,第一種是入度表法,第二種是 DFS 方法。下面,讓我們一起來看一下怎麼實現它。
入度表法
入度表法是根據頂點的入度來判斷是否有依賴關係的。若頂點的入度不為 0,則表示它有前置依賴。它也常常被稱作 BFS 演算法
演算法思想
- 建立入度表,入度為 0 的節點先入隊
- 當佇列不為空,進行迴圈判斷
- 節點出隊,新增到結果 list 當中
- 將該節點的鄰居入度減 1
- 若鄰居節點入度為 0,加入佇列
- 若結果 list 與所有節點數量相等,則證明不存在環。否則,存在環
例項講解
下圖所示的有向無環圖,採用入度表的方法獲取拓撲排序過程。
首先,我們選擇入度為 0 的頂點,這裡頂點 1 的入度為 0,刪除頂點 1 之後,圖變成如下。
這時候,頂點 2 和頂點 4 的入度都為 0,我們可以隨便刪除一個頂點。(這也就是為什麼圖的拓撲排序不是唯一的原因)。這裡我們刪除頂點 2,圖變成如下:
這時候,我們再刪除頂點 4,圖變成如下:
選擇入度為 0 的頂點 3,刪除頂點 3 之後,圖示稱如下,
最後剩餘頂點5,輸出頂點5,拓撲排序過程結束。最終的輸出結果為:
到此,優先無環圖的入度法的流程已經講解完畢。你清楚了嘛。
程式碼的話,下期會一起給出。
時間複雜度
設 AOE 網有 n 個事件,e 個活動,則演算法的主要執行是:
- 求每個事件的ve值和vl值:時間複雜度是O(n+e) ;
- 根據ve值和vl值找關鍵活動:時間複雜度是O(n+e) ;
因此,整個演算法的時間複雜度是O(n+e)
DFS 演算法
從上面的入度表法,我們可以知道,要得到有向無環圖的拓撲排序,我們的關鍵點要找到入度為 0 的頂點。然後接著刪除該結點的相鄰所有邊。再遍歷所有結點。直到入度為 0 的佇列為空。這種方法其實是 BFS。
說到 BFS,我們第一時間就想到 DFS。與 BFS 不同的是,DFS 的關鍵點在於找到,出度為0的頂點。
總結如下,深度優先搜尋過程中,當到達出度為0的頂點時,需要進行回退。在執行回退時記錄出度為0的頂點,將其入棧。則最終出棧順序的逆序即為拓撲排序序列。
演算法思想
- 對圖執行深度優先搜尋。
- 在執行深度優先搜尋時,若某個頂點不能繼續前進,即頂點的出度為0,則將此頂點入棧。
- 最後得到棧中順序的逆序即為拓撲排序順序。
例項講解
同樣,以下圖講解 DFS 演算法的過程。
(1) 從頂點 1 開始出發,開始執行深度優先搜尋。順序為1->2->3->5。
(2)深度優先搜尋到達頂點5時,頂點5出度為0。將頂點5入棧。
(3)深度優先搜尋執行回退,回退至頂點3。此時頂點3的出度為0,將頂點3入棧。
(4)回退至頂點2,頂點2出度為0,頂點2入棧。
(5)回退至頂點1,頂點1可以前進位置為頂點4,順序為1->4。
(6)頂點4出度為0,頂點4入棧。
(7)回退至頂點1,頂點1出度為0,頂點1入棧。
(8)棧的逆序為1->4->2->3->5。此順序為拓撲排序結果。
時間複雜度
時間複雜度分析:首先深度優先搜尋的時間複雜度為O(V+E),而每次只需將完成訪問的頂點存入陣列中,需要O(1),因而總複雜度為O(V+E)。
小結
有向無環圖的拓撲排序其實並不難,難度中等。通常,我們一般使用 BFS 演算法來解決,DFS 演算法比較少用。
對於 BFS(入度表法),它的核心思想是
- 選擇一個沒有輸入邊(入度為0)的源頂點(若有多個則任選一個),
- 將它和它的輸出邊刪除。重複源頂點的刪除操作,直到不存在入度為0的源頂點為止。
- 最終,檢測圖中的頂點個數,若還有頂點存在則演算法無解,否則頂點的刪除順序就是拓撲排序的輸出順序。
https://github.com/gdutxiaoxu/AnchorTask
Android高階開發系統進階筆記、最新面試複習筆記PDF,我的GitHub
文末
您的點贊收藏就是對我最大的鼓勵!
歡迎關注我,分享Android乾貨,交流Android技術。
對文章有何見解,或者有何技術問題,歡迎在評論區一起留言討論!