<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 二、圖 > 原文:[Chapter 2 Graphs](http://greenteapress.com/complexity2/html/thinkcomplexity2003.html) > 譯者:[飛龍](https://github.com/wizardforcel) > 協議:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) > 自豪地采用[谷歌翻譯](https://translate.google.cn/) 本書的前三章有關一些模型,它們描述了由組件和組件之間的連接組成的系統。例如,在生態食物網中,組件是物種,連接代表捕食者和獵物的關系。 在本章中,我介紹了 NetworkX,一個用于構建和研究這些模型的 Python 包。我們從 Erd?s-Rényi 模型開始,它具有一些有趣的數學屬性。在下一章中,我們將介紹更有用的,解釋現實系統的模型。 本章的代碼在本書倉庫中的`chap02.ipynb`中。使用代碼的更多信息請參見第(?)章。 ## 2.1 圖是什么? ![](https://img.kancloud.cn/4b/57/4b57f8a6d223c77c3557bc9e9f6f7483_354x238.png) > 圖 2.1:表示社交網絡的有向圖 對于大多數人來說,圖是數據集的視覺表示,如條形圖或股票價格對于時間的繪圖。這不是本章的內容。 在本章中,圖是一個系統的表示,它包含離散的互連元素。元素由節點表示,互連由邊表示。 例如,你可以表示一個路線圖,每個城市都是一個節點,每個城市之間的路線是一條邊。或者你可以表示一個社交網絡,每個人是節點,如果他們是朋友,兩個人之間有邊,否則沒有。 在某些圖中,邊具有長度,成本或權重等屬性。例如,在路線圖中,邊的長度可能代表兩個城市之間的距離,或旅行時間。在社交網絡中,可能會有不同的邊來表示不同種類的關系:朋友,商業伙伴等。 邊可以是有向或無向的,這取決于它們表示的關系是不對稱的還是對稱的。在路線圖中,你可能會使用有向邊表示單向街道,使用無向邊表示雙向街道。在某些社交網絡,如 Facebook,好友是對稱的:如果 A 是 B 的朋友,那么 B 也是 A 的朋友。但在 Twitter 上,“關注”關系并不對稱;如果 A 關注了 B,這并不意味著 B 關注 A。因此,你可以使用無向邊來表示 Facebook 網絡,并將有向邊用于 Twitter。 圖具有有趣的數學屬性,并且有一個稱為圖論的數學分支,用于研究它們。 圖也很有用,因為有許多現實世界的問題可以使用圖的算法來解決。例如,Dijkstra 的最短路徑算法,是從圖中找到某個節點到所有其他節點的最短路徑的有效方式。路徑是兩個節點之間的,帶有邊的節點序列。 圖的節點通常以圓形或方形繪制,邊通常以直線繪制。例如,上面的有向圖中,節點可能代表在 Twitter 上彼此“關注”的三個人。線的較厚部分表示邊的方向。在這個例子中,愛麗絲和鮑勃相互關注,都關注查克,但查克沒有關注任何人。 下面的無向圖展示了美國東北部的四個城市;邊上的標簽表示駕駛時間,以小時為單位。在這個例子中,節點的位置大致對應于城市的地理位置,但是通常圖的布局是任意的。 ## 2.2 NetworkX ![](https://img.kancloud.cn/e2/a0/e2a02732a4107df1236126b3a3302dca_354x238.png) > 圖 2.2:表示城市和高速公路的無向圖 為了表示圖,我們將使用一個名為 NetworkX 的包,它是 Python 中最常用的網絡庫。你可以在 <https://networkx.github.io/> 上閱讀更多信息,但是我們之后會解釋。 我們可以通過導入 NetworkX 和實例化`nx.DiGraph`來創建有向圖: ```py import networkx as nx G = nx.DiGraph() ``` 通常將 NetworkX 導入為`nx`。此時,`G`是一個`DiGraph`對象,不包含節點和邊。我們可以使用`add_node`方法添加節點: ```py G.add_node('Alice') G.add_node('Bob') G.add_node('Chuck') ``` 現在我們可以使用`nodes`方法獲取節點列表: ```py >>> G.nodes() ['Alice', 'Bob', 'Chuck'] ``` 添加邊的方式幾乎相同: ```py G.add_edge('Alice', 'Bob') G.add_edge('Alice', 'Chuck') G.add_edge('Bob', 'Alice') G.add_edge('Bob', 'Chuck') ``` 我們可以使用`edges`來獲取邊的列表: ```py >>> G.edges() [('Alice', 'Bob'), ('Alice', 'Chuck'), ('Bob', 'Alice'), ('Bob', 'Chuck')] ``` NetworkX 提供了幾個繪圖的功能;`draw_circular`將節點排列成一個圓,并使用邊將它們連接: ```py nx.draw_circular(G, node_color=COLORS[0], node_size=2000, with_labels=True) ``` 這就是我用來生成圖(?)的代碼。`with_labels`選項標注了節點;在下一個例子中,我們將看到如何標注邊。 為了產生圖(?),我們以一個字典開始,它將每個城市的名稱,映射為對應的經緯度: ```py pos = dict(Albany=(-74, 43), Boston=(-71, 42), NYC=(-74, 41), Philly=(-75, 40)) ``` 因為這是個無向圖,我實例化了`nx.Graph`: ```py G = nx.Graph() ``` 之后我可以使用`add_nodes_from`來迭代`pos`的鍵,并將它們添加為節點。 ```py G.add_nodes_from(pos) ``` 下面我會創建一個字典,將每條邊映射為對應的駕駛時間。 ```py drive_times = {('Albany', 'Boston'): 3, ('Albany', 'NYC'): 4, ('Boston', 'NYC'): 4, ('NYC', 'Philly'): 2} ``` 現在我可以使用`add_edges_from`,它迭代了`drive_times`的鍵,并將它們添加為邊: ```py G.add_edges_from(drive_times) ``` 現在我不使用`draw_circular`,它將節點排列成一個圓圈,而是使用`draw`,它接受`pos`作為第二個參數: ```py nx.draw(G, pos, node_color=COLORS[1], node_shape='s', node_size=2500, with_labels=True) ``` `pos`是一個字典,將每個城市映射為其坐標;`draw`使用它來確定節點的位置。 要添加邊的標簽,我們使用`draw_networkx_edge_labels`: ```py nx.draw_networkx_edge_labels(G, pos, edge_labels=drive_times) ``` `drive_times`是一個字典,將每條邊映射為它們之間的駕駛距離,每條邊表示為城市名稱的偶對。這就是我生成圖(?)的方式。 在這兩個例子中,這些節點是字符串,但是通常它們可以是任何可哈希的類型。 ## 2.3 隨機圖 隨機圖就像它的名字一樣:一個隨機生成的節點和邊的圖。當然,有許多隨機過程可以生成圖,所以有許多種類的隨機圖。 其中一個更有趣的是 Erd?s-Rényi 模型,PaulErd?s 和 AlfrédRényi 在 20 世紀 60 年代研究過它。 Erd?s-Rényi 圖(ER 圖)的特征在于兩個參數:`n`是節點的數量,`p`是任何兩個節點之間存在邊的概率。見 <http://en.wikipedia.org/wiki/Erdos-Renyi_model>。 Erd?s 和 Rényi 研究了這些隨機圖的屬性;其令人驚奇的結果之一就是,隨著隨機的邊被添加,隨機圖的屬性會突然變化。 展示這類轉變的一個屬性是連通性。如果每個節點到每個其他節點都存在路徑,那么無向圖是連通的。 在 ER 圖中,當`p`較小時,圖是連通圖的概率非常低,而`p`較大時接近`1`。在這兩種狀態之間,在`p`的特定值處存在快速轉變,表示為`p*`。 Erd?s 和 Rényi 表明,這個臨界值是`p* = lnn / n`,其中`n`是節點數。如果`p < p*`,隨機圖`G(n, p)`不太可能連通,并且如果`p > p*`,則很可能連通。 為了測試這個說法,我們將開發算法來生成隨機圖,并檢查它們是否連通。 ## 2.4 生成圖 ![](https://img.kancloud.cn/6c/88/6c88c2e8d09f196ae675f453397a553e_354x238.png) 我將首先生成一個完全圖,這是一個圖,其中每個節點都彼此連接。 這是一個生成器函數,它接收節點列表并枚舉所有不同的偶對。如果你不熟悉生成器函數,你可能需要閱讀附錄?,然后回來。 ```py def all_pairs(nodes): for i, u in enumerate(nodes): for j, v in enumerate(nodes): if i>j: yield u, v ``` 你可以使用`all_pairs`來構造一個完全圖。 ```py def make_complete_graph(n): G = nx.Graph() nodes = range(n) G.add_nodes_from(nodes) G.add_edges_from(all_pairs(nodes)) return G ``` `make_complete_graph`接受節點數`n`,并返回一個新的`Graph`,擁有`n`個節點,所有節點之間都有邊。 以下代碼生成了一個包含 10 個節點的完全圖,并繪制出來。 ```py complete = make_complete_graph(10) nx.draw_circular(complete, node_color=COLORS[2], node_size=1000, with_labels=True) ``` 圖(?)顯示了結果。不久之后,我們將修改此代碼來生成 ER 圖,但首先我們將開發函數來檢查圖是否是連通的。 ## 2.5 連通圖 如果每個節點到每個其他節點都存在路徑,這個圖就是連通圖。請見<http://en.wikipedia.org/wiki/Connectivity_(graph_theory)>。 對于許多涉及圖的應用,檢查圖是否連通是很有用的。幸運的是,有一個簡單的算法。 你可以從任何節點起步,并檢查是否可以到達所有其他節點。如果你可以到達一個節點`v`,你可以到達`v`的任何一個鄰居,他們是`v`通過邊連接的任何節點。 `Graph`類提供了一個稱為`neighbors`的方法,返回給定節點的鄰居列表。例如,在上一節中我們生成的完全圖中: ```py >>> complete.neighbors(0) [1, 2, 3, 4, 5, 6, 7, 8, 9] ``` 假設我們從節點`s`起步。我們可以將`s`標記為“已訪問”,然后我們可以標記它的鄰居。然后我們標記鄰居的鄰居,依此類推,直到你無法再到達任何節點。如果訪問了所有節點,則圖是連通圖。 以下是 Python 中的樣子: ```py def reachable_nodes(G, start): seen = set() stack = [start] while stack: node = stack.pop() if node not in seen: seen.add(node) stack.extend(G.neighbors(node)) return seen ``` `reachable_nodes`接受`Graph`和起始節點`start`,并返回可以從`start`到達的節點集合,他們。 最初,已訪問的集合是空的,我們創建一個名為`stack`的列表,跟蹤我們發現但尚未處理的節點。最開始,棧包含單個節點`start`。 現在,每次在循環中,我們: + 從棧中刪除一個節點。 + 如果節點已在`seen`中,我們返回到步驟 1。 + 否則,我們將節點添加到`seen`,并將其鄰居添加到棧。 當棧為空時,我們無法再到達任何節點,所以我們終止了循環并返回。 例如,我們可以找到從節點`0`可到達的,完全圖中的所有節點: ```py >>> reachable_nodes(complete, 0) {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} ``` 最初,棧包含節點`0`,`seen`是空的。第一次循環中,節點`0`添加到了`seen`,所有其他節點添加到了棧中(因為它們都是節點`0`的鄰居)。 下一次循環中,`pop`返回棧中的最后一個元素,即節點`9.`因此,節點`9`被添加到`seen`,并且其鄰居被添加到棧。 請注意,同一個節點在棧中可能會出現多次;實際上,具有`k`個鄰居的節點將添加到棧`k`次。稍后我們將尋找方法,來使此算法更有效率。 我們可以使用`reachable_nodes`來編寫`is_connected`: ```py def is_connected(G): start = next(G.nodes_iter()) reachable = reachable_nodes(G, start) return len(reachable) == len(G) ``` `is_connected`通過調用`nodes_iter`來選擇一個起始節點,`node_iter`返回一個迭代器對象,并將結果傳遞給`next`,返回第一個節點。 `reachable`獲取了一組節點,它們可以從`start`到達。如果這個集合的大小與圖的大小相同,那意味著我們可以訪問所有節點,也就是這個圖是連通的。 一個完全圖是連通的,毫不奇怪: ```py >>> is_connected(complete) True ``` 下一節中,我們會生成 ER 圖,并檢查它們是否是連通的。 ## 2.6 生成 ER圖 ![](https://img.kancloud.cn/2b/29/2b290304a3a4ac2c27925500b2af2dd0_354x238.png) > 圖 2.4:ER 圖,`n=10`,`p=0.3` ER 圖`G(n, p) `包含`n`個節點,每對節點以概率為`p`的邊連接。生成 ER 圖類似于生成完全圖。 以下生成器函數枚舉所有可能的邊,并使用輔助函數`flip`,來選擇哪些應添加到圖中: ```py def random_pairs(nodes, p): for i, u in enumerate(nodes): for j, v in enumerate(nodes): if i>j and flip(p): yield u, v ``` `flip`以給定概率`p`返回`True`,以互補的概率`1-p`返回`False`。 ```py from numpy.random import random def flip(p): return random() < p ``` 最后,`make_random_graph`生成并返回 ER 圖`G(n, p)`。 ```py def make_random_graph(n, p): G = nx.Graph() nodes = range(n) G.add_nodes_from(nodes) G.add_edges_from(random_pairs(nodes, p)) return G ``` `make_random_graph`幾乎和`make_complete_graph`,唯一的不同是它使用`random_pairs`而不是`all_pairs`。 這里是`p=0.3`的例子: ```py random_graph = make_random_graph(10, 0.3) ``` 圖(?)展示了結果。這個圖是連通圖;事實上,大多數`p=10`并且`p=3`的 ER 圖都是連通圖。在下一節中,我們將看看有多少。 ## 2.7 連通性的概率 ![](https://img.kancloud.cn/36/fb/36fbe6d3939fbeeeeab549c33b90a7b7_354x238.png) > 圖 2.5:連通性的概率,`n=10`,`p`是一個范圍。豎直的線展示了預測的臨界值。 ![](https://img.kancloud.cn/38/7a/387a609096ea0fe237b585f8e1043648_354x238.png) >圖 2.6:連通性的概率,`n`是多個值,`p`是一個范圍。 對于`n`和`p`的給定值,我們想知道`G(n, p)`連通的概率。我們可以通過生成大量隨機圖,來計算有多少個來估計它。就是這樣: ```py def prob_connected(n, p, iters=100): count = 0 for i in range(iters): random_graph = make_random_graph(n, p) if is_connected(random_graph): count += 1 return count/iters ``` `iters`是我們生成的隨機圖的數量。隨著我們增加`iter`,估計的概率就會更加準確。 ```py >>> prob_connected(10, 0.3, iters=10000) 0.6454 ``` 在具有這些參數的 10000 個 ER 圖中,6498 個是連通的,因此我們估計其中65%是連通的。所以 0.3 接近臨界值,在這里連通概率從接近 0 變為接近 1。根據 Erd?s 和 Rényi,`p* = lnn / n = 0.23`。 我們可以通過估計一系列`p`值的連通概率,來更清楚地了解轉變: ```py import numpy as np n = 10 ps = np.logspace(-2.5, 0, 11) ys = [prob_connected(n, p) for p in ps] ``` 這是我們看到的使用 NumPy 的第一個例子。按照慣例,我將 NumPy 導入為`np`。函數`logspace`返回從`10 ** -2.5`到`10 ** 0 = 1`的 11 個元素的數組,在對數刻度上等間隔。 為了計算`y`,我使用列表推導來迭代`ps`的元素,并計算出每個值為`p`的隨機圖的連通概率。 圖(?)展示了結果,豎直的線為`p*`。從 0 到 1 的轉變發生在預測的臨界值附近。在對數刻度上,這個轉變大致對稱。 對于較大的`n`值,圖(?)展示了類似的結果。隨著`n`的增加,臨界值越來越小,轉變越來越突然。 這些實驗與 Erd?s 和 Rényi 在其論文中證明的結果一致。 ## 2.8 圖的算法分析 這一章中,我提出了一個檢查圖是否連通的算法;在接下來的幾章中,我們將再次看到更多的圖的算法。并且我們要分析這些算法的性能,了解它們的運行時間如何隨著圖大小的增加而增長。 如果你還不熟悉算法分析,在你繼續之前,你應該閱讀附錄一。 圖算法的增長級別通常表示為頂點數量`n`,以及邊數量`m`的函數。 作為一個例子,我們分析從前面的`reachable_nodes`: ```py def reachable_nodes(G, start): seen = set() stack = [start] while stack: node = stack.pop() if node not in seen: seen.add(node) stack.extend(G.neighbors(node)) return seen ``` 每次循環,我們從棧中彈出一個節點;默認情況下,`pop`刪除并返回列表的最后一個元素,這是一個常數時間的操作。 接下來我們檢查節點是否被已訪問,這是一個集合,所以檢查成員是常數時間。 如果節點還沒有訪問,我們添加它是常量時間,然后將鄰居添加到棧中,這相對于鄰居數量是線性的。 為了使用`n`和`m`表達運行時間,我們可以將每個節點添加到`seen`和`stack`的總次數加起來。 每個節點只添加一次,所以添加的總數為`n`。 但是節點可能多次被添加到棧,具體取決于它們有多少鄰居。如果節點具有`k`個鄰居,則它會被添加到棧`k`次。當然,如果它有`k`個鄰居,那意味著它擁有`k`個邊。 所以添加到棧的總數是邊的數量`m`的兩倍,由于我們考慮每個邊兩次。 因此,這個函數的增長級別為`O(n + m)`,我們可以說,即運行時間與`n`或`m`成正比,以較大者為準。 如果我們知道`n`和`m`之間的關系,我們可以簡化這個表達式。例如,在完全圖中,邊數是`n(n-1)/ 2`,它屬于`O(n^2)`。所以對于一個完全圖,`reachable_nodes`是二次于`n`的。 ## 2.9 練習 本章的代碼在`chap02.ipynb`中,它是本書的倉庫中的 Jupyter 筆記本。使用此代碼的更多信息,請參閱第(?)節。 練習 1:啟動`chap02.ipynb`并運行代碼。筆記本中嵌入了一些簡單的練習,你可能想嘗試一下。 練習 2:我們分析了`reachable_nodes`的性能,并將其分類為`O(n + m)`,其中`n`是節點數,`m`是邊數。繼續分析,`is_connected`的增長級別是什么? ```py def is_connected(G): start = next(G.nodes_iter()) reachable = reachable_nodes(G, start) return len(reachable) == len(G) ``` 練習 3 :在我實現`reachable_nodes`時,你可能很困惑,因為向棧中添加所有鄰居而不檢查它們是否已訪問,明顯是低效的。編寫一個該函數的版本,在將鄰居添加到棧之前檢查它們。這個“優化”是否改變了增長級別?它是否使函數更快? > 譯者注:在彈出節點時將其添加到`seen`,在遍歷鄰居時檢查它們是否已訪問。 練習 4: 實際上有兩種 ER 圖。我們在本章中生成的一種,`G(n,p)`的特征是兩個參數,節點數量和節點之間的邊的概率。 一種替代定義表示為`G(n,m)`,也以兩個參數為特征:節點數`n`和邊數`m`。在這個定義中,邊數是固定的,但它們的位置是隨機的。 使用這個替代定義,重復這一章的實驗。這里是幾個如何處理它的建議: 1. 編寫一個名為`m_pairs`的函數,該函數接受節點列表和邊數`m`,并返回隨機選擇的`m`個邊。一個簡單的方法是,生成所有可能的邊的列表,并使用`random.sample`。 2. 編寫一個名為`make_m_graph`的函數,接受`n`和`m`,并返回`n`個節點和`m`個邊的隨機圖。 3. 創建一個`prob_connected`的版本,使用`make_m_graph`而不是`make_random_graph`。 4. 計算一系列`m`值的連通概率。 與第一類 ER 圖的結果相比,該實驗的結果如何?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看