樹的定義是,除根結點以外,樹的每一個結點都剛好有一個父結點。
而若是違背了這一個前提,即容許樹的每一個結點連通多個其它結點,再也不有父親、孩子之說,即獲得孩子的概念html
1、無向圖java
2、有向圖git
3、網絡(加權圖)算法
4、經常使用的圖算法
1。 好比:各類遍歷算法、尋找最短路徑算法、尋找網絡中最低代價路徑的算法,回答一些簡單圖相關問題(如圖是否連通,兩個頂點間最短路徑)
2。 遍歷:數據庫
深度優先遍歷:相似於樹的前序遍歷
可是要注意的是:圖中不存在根結點,所以圖的遍歷能夠從其中的任一頂點開始編程
廣度優先遍歷的算法:用一個隊列和一個無序列表來構造(使用隊列管理遍歷,使用無序列表構造出結果)
第一步,起始點進入隊列traveralQueue,同時標記該頂點爲已訪問的( visited)
而後開始循環,該循直持續到 traveralQueue爲空時中止,在這個循環中,從 traveralQueue中取出這個首頂點,並將它添加到 resultList的末端
接着,讓全部與當前頂點鄰接且還沒有被標記爲 visited的各個頂點依次進入隊列 traversalQuet,同時把它們逐個標記爲 visited
而後再重複上述循環
對每一個已訪問的頂點都重複這一過程,直到 traversalQucue爲空時結束,這時意味着沒法再找到任何新的頂點
如今resultList即以廣度優先次序(從給定的起始頂點開始)存放着各個頂點數組
深度優先遍歷的算法:其構造使用了與廣度優先遍歷一樣的邏輯,不過在深度優先遍歷中用 traversalstack代替了 traversalQueue
算法中還有另外一處不一樣:在頂點還沒有添加到 resultList以前,並不想標記該頂點爲visited網絡
圖的深度優先遍歷與廣度優先遍歷惟一的不一樣是:前者使用的是棧而不是隊列來管理遍歷數據結構
3。 測試連通性ide
4。 最小生成樹
5。 斷定最短路徑:斷定圖的「最短」路徑有兩種狀況
第一種:是斷定起始頂與目標頂點之間的字面意義上的最短路徑,也就是兩個頂點之間的最小邊數。
將廣度優先遍歷算法轉變成尋找最短路徑的算法,只需在遍歷期間再對每一個頂點存另兩個信息便可:從起始頂點到本頂點的路徑長度,以及路徑中做爲本頂點前驅的那個頂點
接着修改循環,使得當抵達目標頂點時循環將終止,最短路徑的路徑長度就是從起始頂點到目標頂點前驅的路徑長度再加1;
若是要輸出這條最短路徑上的頂點,只需沿前驅鏈回溯便可
第二種:尋找加權圖的最便宜路徑。這裏不使用頂點隊列(這要求咱們必須根據頂點的遭遇次數來探究圖),而是用一個 minheap或優先隊列來存儲頂點,
基於總權重(從起地頂點到本頂點的權重和)來衡量頂點對,這樣咱們老是能優先沿着最便宜的路徑來遊歷
對每一個頂點都必須存儲該頂點的標籤,(迄今爲止)從起始頂點到本頂點的最便宜路徑的權重,路徑上本頂點的前驅
在 minheap中將存儲頂點、對每條已經遇到但還沒有遊歷的候選路徑來權衡頂點對,
從 minheap取出頂點的時候,會權衡取自 minheap的頂點對;
如遇到一個頂點的權重小於目前本頂點中已存儲的權重,則會更新路徑的代價
5、圖的實現策略
1。鄰接列表:
對於圖節點來講,每一個節點能夠有多達n-1條邊與其它結點相連,所以用一種相似於鏈表的動態節點來存儲每一個節點帶有的邊,這種鏈表稱爲鄰接列表
2.。鄰接矩陣:
無向圖的矩陣是對稱的,因此對於無向圖來講,不必表示整個矩陣,只需給出矩陣對角線的一側便可
對於有向圖來講,全部邊都是定向的,故而矩陣不對稱
百度的解釋:
拓撲序:在圖中從頂點A到頂點B有一條有向路徑,則頂點A必定排在頂點B以前。知足這樣的條件的頂點序列稱爲一個拓撲序。
what ???
這就是拓撲序的定義?(手動笑哭)
這麼高大上的名字,就是這個意思?A到B有多條路徑,每個路徑,A都是起點,B是終點(本身瞎造的理解。。。)
那麼這麼 「 簡單 」 的定義有什麼用嗎?還專門定義了一個名詞?
那就是——拓撲排序:得到一個拓撲序列的過程
說實話,我也沒以爲這個排序有多麼厲害。。。
可是,看了相關參考,才知道專門定義一個這樣的名詞是頗有必要的
舉個栗子:
好比大學的課程安排:
例如:
若是你想學習離散數學,前提你必需要預修高等數學這門課。
若是你想學習數據結構這門課,那麼你要先學了程序設計這門課,等等等等
因此大學都會根據課程之間的聯繫來安排學生學習課程的前後次序
因此這個前後順序,就用到了有向圖來表示:
好比下面的這張有向圖:
圖中的每個頂點對應每一門課,若是兩門課之間是有預修關係的,即若是前一門課是後一門課的預修課程,那麼表示這兩門課的兩個頂點間就有一條有向邊
這樣的圖符合上面所說的拓撲序,即兩個頂點之間的邊表示的是兩個之間的先後關係
選擇不一樣的課程都會按照必定的路徑從A開始到B結束,這樣多個課程混雜也就造成了如上的有向圖
要明白這個問題,得先知道深度優先遍歷是如何遍歷的
課本說它與廣度優先遍歷的邏輯一致
百度的解釋爲:從圖的某個頂點v出發,訪問此頂點,而後從v的未被訪問過的鄰接點出發深度優先遍歷圖,直至圖中的全部和v有路徑相通的頂點都被訪問到(對於連通圖來說)
廣度優先遍歷中,頂點進入隊列而後標記爲已訪問,而後開始循環,該循直持續到隊列爲空時中止,在這個循環中,從隊列中取出這個首頂點,並將它添加到 resultList的末端
而深度優先遍歷不一樣的就在這裏:在頂點還沒有添加到 resultList以前,並不想標記該頂點爲visited
這有什麼區別嗎?
我以爲區別可能在於,深度遍歷是用棧來實現的,不一樣於隊列實現的廣度優先遍歷,
隊列能夠按照先進先出的規則,將鄰接的節點按照必定順序直接設定爲已訪問存進去,取出來的時候即按照存進去的順序取出來
而棧是後進先出的,因此在添加進棧的時候,不把他們標記爲已訪問,而當將他們取出來,放進resultList裏的時候纔將他們標記爲已訪問,
這樣能夠保證一條路走到黑?
結合百度的資料,總結一下兩者的區別:
【參考資料】
圖的深度優先遍歷和廣度優先遍歷理解
總結深度優先與廣度優先的區別
12、圖的遍歷--(2)深度優先搜索算法
圖的深度優先遍歷
對比着課本上給出的鄰接矩陣的實現代碼、其它網上博客,並參考了侯澤洋同窗的代碼實現了鄰接列表的代碼實現,並記錄下了以下分析:
1。 鄰接矩陣是經過一個二維數組實現了對一個點與其它點是否連通的記錄
而若是用鄰接列表的話,則不須要用二維數組記錄鏈接狀況:直接將與某點鄰接的點鏈在此點的後面便可
如圖:
將節點存在列表當中,在每一個節點後面鏈上其它與其鄰接的節點(造成鏈表)
對比代碼(上方爲鄰接矩陣,下方爲鄰接列表):
protected int numVertices; // 當前頂點個數 protected boolean[][] adjMatrix; // 鄰接矩陣 protected T[] vertices; // 頂點的值 protected int modCount;// 修改標記數 /* ****************************************************************************** */ protected int numVertices; // 當前頂點個數 protected List<List<Integer>> adjMatrix; // 鄰接的節點鏈成的鏈表 protected List<T> vertices; //存放節點的列表 protected int modCount;
2。 添加節點addvertices操做
對比代碼(上方爲鄰接矩陣,下方爲鄰接列表):
public void addVertex(T vertex) { // 若是頂點滿了 if ((numVertices + 1) == adjMatrix.length) expandCapacity(); vertices[numVertices] = vertex;// 添加結點 // 添加的這個頂點和每個頂點的連邊默認的設置 for (int i = 0; i < numVertices; i++) { adjMatrix[numVertices][i] = false; adjMatrix[i][numVertices] = false; } numVertices++; modCount++; } /* ******************************************************************************** */ public void addVertex(T vertex) { vertices.add(vertex);//直接添加到列表中 List list = new ArrayList(); list.add(numVertices); adjMatrix.add(list);//存儲添加節點的索引值 numVertices++; modCount++; }
3。 刪除節點removeVertex操做
鄰接矩陣的節點刪除操做,是直接經過覆蓋完成的:
首先先判斷刪除的節點索引值是否存在
而後先將節點所在的二維數組中的值行列用下一行、右一列依次向上,向左進行覆蓋
最後將節點數組中的要刪除的節點處的下一位依次向上移,即完成刪除操做
第二步是至關於完成了刪除邊的操做
鄰接列表不用上述操做,直接執行列表具備的刪除節點操做,接下來就是刪除與節點鄰接的邊的操做(具體操做在下面刪除邊的代碼中分析)
對比代碼(上方爲鄰接矩陣,下方爲鄰接列表):
public void removeVertex(int index){ if (indexIsValid(index)) { for (int a = 0; a < vertices.length; a++) {//至關於完成了刪除邊的操做 adjMatrix[a][index] = adjMatrix[a][index + 1]; adjMatrix[index][a] = adjMatrix[index + 1][a]; } for (int i = index; i < numVertices; i++) {//刪除節點 vertices[index] = vertices[index + 1]; } } } @Override public void removeVertex(T vertex) { if (isEmpty()) { throw new EmptyCollectionException("Graph"); } removeVertex(getIndex(vertex)); } /* ********************************************************************* */ public void removeVertex(T vertex) { int index = getIndex(vertex); if (indexIsValid(index)) vertices.remove(index);//刪除節點 for (int i = 0;i < adjMatrix.get(index).size()-1;i++)//找到與節點相鄰接的全部節點並刪除邊 { int x = adjMatrix.get(index).get(i+1); removeEdge(x,index); } adjMatrix.remove(index);//刪除記錄的節點的索引值 numVertices--; modCount++; }
4。 添加邊addEdge操做
對比代碼(上方爲鄰接矩陣,下方爲鄰接列表):
@Override public void addEdge(T v1, T v2) { addEdge(getIndex(v1), getIndex(v2)); } private void addEdge(int index1, int index2) { if (indexIsValid(index1) && indexIsValid(index2)) {//兩個索引都存在 adjMatrix[index1][index2] = true; adjMatrix[index2][index1] = true; modCount++; } } /* ********************************************************************* */ public void addEdge(int index1,int index2) { if (indexIsValid(index1)&&indexIsValid(index2)) { (adjMatrix.get(index1)).add(index2); (adjMatrix.get(index2)).add(index1); modCount++; } } public void addEdge(T vertex1,T vertex2) { int index1 = getIndex(vertex1); int index2 = getIndex(vertex2); if (indexIsValid(index1)&&indexIsValid(index2)) { (adjMatrix.get(index1)).add(index2); (adjMatrix.get(index2)).add(index1); modCount++; } }
5。 刪除邊removeEdge操做
對比代碼(上方爲鄰接矩陣,下方爲鄰接列表):
@Override public void removeEdge(T v1, T v2) { removeEdge(getIndex(v1), getIndex(v2)); } private void removeEdge(int index1, int index2) { if (indexIsValid(index1) && indexIsValid(index2)) { adjMatrix[index1][index2] = false; adjMatrix[index2][index1] = false; modCount++; } } /* ********************************************************************* */ @Override public void removeEdge(T vertex1, T vertex2) { int index1 = getIndex(vertex1); int index2 = getIndex(vertex2); if (indexIsValid(index1)&&indexIsValid(index2)) { if (adjMatrix.get(index1).contains(index2)) { (adjMatrix.get(index1)).remove(adjMatrix.get(index1).indexOf(index2)); (adjMatrix.get(index2)).remove(adjMatrix.get(index2).indexOf(index1)); modCount++; } } } public void removeEdge(int index1, int index2) { if (indexIsValid(index1)&&indexIsValid(index2)) { if ((adjMatrix.get(index1)).contains(index2)) { (adjMatrix.get(index1)).remove(adjMatrix.get(index1).indexOf(index2)); (adjMatrix.get(index2)).remove(adjMatrix.get(index2).indexOf(index1)); modCount++; } } }
【參考資料】
Java實現無向圖鄰接表
java:鄰接表無向圖的鏈表實現法
鄰接表無向圖(三)之 Java詳解
用鄰接表實現無向圖
用鄰接表表示圖【java實現】
無向圖的實現(鄰接表) 圖的遍歷
無向網絡只要在無向圖的基礎上,在邊的相關方法里加上權重參數便可
在書上的鄰接矩陣實現的代碼基礎上,把二維數組的true或false改爲相關權重值便可,刪除邊,只要將其賦成無窮大POSITIVE_INFINITY或其餘的什麼
運行結果截圖:
代碼行數(新增/累積) | 博客量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 5000行 | 30篇 | 400小時 | |
第一週 | 0/0 | 1/1 | 4/4 | |
第二週 | 560/560 | 1/2 | 6/10 | |
第三週 | 415/975 | 1/3 | 6/16 | |
第四周 | 1055/2030 | 1/4 | 14/30 | |
第五週 | 1051/3083 | 1/5 | 8/38 | |
第六週 | 785/3868 | 1/6 | 16/54 | |
第七週 | 733/4601 | 1/7 | 20/74 | |
第八週 | 2108/6709 | 1/8 | 20/74 | |
第九周 | 1425/8134 | 1/9 | 20/94 |