20172308 《程序設計與數據結構》第九周學習總結

教材學習內容總結

第 十五 章 圖

樹的定義是,除根結點以外,樹的每一個結點都剛好有一個父結點。
而若是違背了這一個前提,即容許樹的每一個結點連通多個其它結點,再也不有父親、孩子之說,即獲得孩子的概念html

1、無向圖java

  • 圖與樹相似,也由結點和這些結點之間的鏈接構成(這些結點就是圖的頂點,而結點之間的鏈接就是圖的
  • 無向圖是一種邊爲無序結點對的圖
  • 若是圖中的兩個頂點之間有一條連通邊,則稱這兩個頂點是鄰接的(也互稱鄰居
  • 連通一個頂點及其自身的邊稱爲自循環
  • 若是無向圖擁有最大數目的連通頂點的邊,則認爲這個無向圖是徹底的
  • 對有n個頂點的無向圖,要使該圖爲徹底的,要求有n(n-1)/2條邊(這裏假設其中沒有邊是自循環的)
  • 路徑是圖中的一系列邊,每條邊連通兩個頂點(無向圖中的路徑是雙向的)
  • 若是無向圖中的任意兩個頂點之間都存在一條路徑,則認爲這個無向圖是連通的
  • 環路是一種首頂點和末頂點相同且沒有重邊的路徑
  • 沒有環路的圖稱爲無環的
  • 無向樹是一種連通的無環無向圖,其中一個元素被指定爲樹根

2、有向圖git

  • 有向圖(雙向圖),它是一種邊爲有序頂點對的圖
  • 有向圖的路徑是圖中連通兩個頂點的有向邊序列(有向圖中的路徑不是雙向的)
  • 若是有向圖中沒有環路,且有一條從A到B的邊,則能夠把頂點A安排在頂點B以前。這種排列獲得的頂點次序稱爲拓撲序
  • 爲何說樹也是圖?須要知足的條件?

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.。鄰接矩陣:

  • 每一個單元都表示了圖中兩個頂點交接狀況的二維數組(這些交接狀況由一個代表兩個頂點是否連通的布爾值表示)
  • 無向圖的矩陣是對稱的,因此對於無向圖來講,不必表示整個矩陣,只需給出矩陣對角線的一側便可

  • 對於有向圖來講,全部邊都是定向的,故而矩陣不對稱

教材學習中的問題和解決過程

問題1:什麼是「拓撲序」?在哪裏應用?

問題1解析:

百度的解釋:
拓撲序:在圖中從頂點A到頂點B有一條有向路徑,則頂點A必定排在頂點B以前。知足這樣的條件的頂點序列稱爲一個拓撲序。

what ???
這就是拓撲序的定義?(手動笑哭)
這麼高大上的名字,就是這個意思?A到B有多條路徑,每個路徑,A都是起點,B是終點(本身瞎造的理解。。。)

那麼這麼 「 簡單 」 的定義有什麼用嗎?還專門定義了一個名詞?

那就是——拓撲排序:得到一個拓撲序列的過程
說實話,我也沒以爲這個排序有多麼厲害。。。
可是,看了相關參考,才知道專門定義一個這樣的名詞是頗有必要的

舉個栗子:
好比大學的課程安排:
例如:
若是你想學習離散數學,前提你必需要預修高等數學這門課。
若是你想學習數據結構這門課,那麼你要先學了程序設計這門課,等等等等
因此大學都會根據課程之間的聯繫來安排學生學習課程的前後次序

因此這個前後順序,就用到了有向圖來表示:
好比下面的這張有向圖:

圖中的每個頂點對應每一門課,若是兩門課之間是有預修關係的,即若是前一門課是後一門課的預修課程,那麼表示這兩門課的兩個頂點間就有一條有向邊

這樣的圖符合上面所說的拓撲序,即兩個頂點之間的邊表示的是兩個之間的先後關係
選擇不一樣的課程都會按照必定的路徑從A開始到B結束,這樣多個課程混雜也就造成了如上的有向圖

【參考資料】
拓撲排序的實現
拓撲序

問題2:爲何深度優先遍歷在頂點還沒有添加到 resultList以前,並不想標記該頂點爲visited?

問題2解析:

要明白這個問題,得先知道深度優先遍歷是如何遍歷的
課本說它與廣度優先遍歷的邏輯一致
百度的解釋爲:從圖的某個頂點v出發,訪問此頂點,而後從v的未被訪問過的鄰接點出發深度優先遍歷圖,直至圖中的全部和v有路徑相通的頂點都被訪問到(對於連通圖來說)

廣度優先遍歷中,頂點進入隊列而後標記爲已訪問,而後開始循環,該循直持續到隊列爲空時中止,在這個循環中,從隊列中取出這個首頂點,並將它添加到 resultList的末端
而深度優先遍歷不一樣的就在這裏:在頂點還沒有添加到 resultList以前,並不想標記該頂點爲visited

這有什麼區別嗎?

我以爲區別可能在於,深度遍歷是用棧來實現的,不一樣於隊列實現的廣度優先遍歷,
隊列能夠按照先進先出的規則,將鄰接的節點按照必定順序直接設定爲已訪問存進去,取出來的時候即按照存進去的順序取出來
而棧是後進先出的,因此在添加進棧的時候,不把他們標記爲已訪問,而當將他們取出來,放進resultList裏的時候纔將他們標記爲已訪問,
這樣能夠保證一條路走到黑?

結合百度的資料,總結一下兩者的區別:

  • 1) 二叉樹的深度優先遍歷的非遞歸的通用作法是採用棧,廣度優先遍歷的非遞歸的通用作法是採用隊列。
  • 2) 深度優先遍歷:對每個可能的分支路徑深刻到不能再深刻爲止,並且每一個結點只能訪問一次。
    廣度優先遍歷:又叫層次遍歷,從上往下對每一層依次訪問,在每一層中,從左往右(也能夠從右往左)訪問結點,訪問完一層就進入下一層,直到沒有結點能夠訪問爲止。   
  • 3)深度優先搜素算法:不所有保留結點,佔用空間少;有回溯操做(即有入棧、出棧操做),運行速度慢。
    廣度優先搜索算法:保留所有結點,佔用空間大; 無回溯操做(即無入棧、出棧操做),運行速度快。
  • 一般 深度優先搜索法不所有保留結點,擴展完的結點從數據庫中彈出刪去,這樣,通常在數據庫中存儲的結點數就是深度值,所以它佔用空間較少。
    因此,當搜索樹的結點較多,用其它方法易產生內存溢出時,深度優先搜索不失爲一種有效的求解方法。  
    廣度優先搜索算法,通常需存儲產生的全部結點,佔用的存儲空間要比深度優先搜索大得多,所以,程序設計中,必須考慮溢出和節省內存空間的問題。
    但廣度優先搜索法通常無回溯操做,即入棧和出棧的操做,因此運行速度比深度優先搜索要快些

【參考資料】
圖的深度優先遍歷和廣度優先遍歷理解
總結深度優先與廣度優先的區別
12、圖的遍歷--(2)深度優先搜索算法
圖的深度優先遍歷

代碼運行中的問題及解決過程

問題1:PP15.1,利用鄰接列表實現無向圖,如何實現?

問題1解決過程:

對比着課本上給出的鄰接矩陣的實現代碼、其它網上博客,並參考了侯澤洋同窗的代碼實現了鄰接列表的代碼實現,並記錄下了以下分析:
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操做

  • 鄰接矩陣須要將添加的節點與其它節點之間設置爲false存儲在二維數組裏,在添加節點前還得判斷數組是否滿以及執行擴展數組操做
  • 鄰接列表不須要上述操做,無序判斷數組是否滿,直接添加便可,可是要記錄存儲添加節點的索引值

對比代碼(上方爲鄰接矩陣,下方爲鄰接列表):

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操做

  • 鄰接矩陣直接找到索引處的兩個節點,將兩個索引處對應的二位數組設置成true便可(無向的)
  • 鄰接列表先找到第一個索引處的節點,而後將下一個索引處的節點添加到其後便可(無向的,索引反過來在執行一遍便可)
    這裏有兩種實現方式,在兩個索引處加邊,或在兩個節點間加邊

對比代碼(上方爲鄰接矩陣,下方爲鄰接列表):

@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操做

  • 鄰接矩陣:與添加邊正好相反,把兩個索引處對應的二位數組設置成false便可
  • 鄰接列表:與添加邊正好相反,先找到第一個索引處的節點,但這裏須要判斷,下一索引處的節點是否與其鄰接
    若是鄰接,則將下一個索引處的節點刪除便可(無向的,索引反過來在執行一遍便可)
    這裏也有兩種實現方式,在兩個索引處刪邊,或在兩個節點間刪邊

對比代碼(上方爲鄰接矩陣,下方爲鄰接列表):

@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實現】
無向圖的實現(鄰接表) 圖的遍歷

問題2:PP15.7,即實現一個無向網絡

問題2解決過程:

無向網絡只要在無向圖的基礎上,在邊的相關方法里加上權重參數便可
在書上的鄰接矩陣實現的代碼基礎上,把二維數組的true或false改爲相關權重值便可,刪除邊,只要將其賦成無窮大POSITIVE_INFINITY或其餘的什麼

運行結果截圖:

本週錯題

本週無錯題

代碼託管

結對及互評

  • 博客中值得學習的或問題:
    • 侯澤洋同窗的博客排版工整,界面很美觀,而且本週還對博客排版、字體作了調整,很用心
    • 問題總結作得很全面:對課本上不懂的代碼會作透徹的分析,即使能夠直接拿過來用而不用管他的含義
    • 對教材中的細小問題都可以關注,而且主動去百度學習
    • 代碼中值得學習的或問題:
    • 對於編程的編寫總能找到角度去解決
  • 本週結對學習狀況
    • 20172302
    • 結對學習內容
      • 第十五 章內容:圖

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 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
相關文章
相關標籤/搜索