1. 程式人生 > >20172305 2018-2019-1 《Java軟體結構與資料結構》第九周學習總結

20172305 2018-2019-1 《Java軟體結構與資料結構》第九周學習總結

20172305 2018-2019-1 《Java軟體結構與資料結構》第九周學習總結

教材學習內容總結

本週內容主要為書第十五章內容:

  • (結點和結點之間的連線構成)
    • 頂點:結點
    • 邊:結點之間的連線
    • 鄰接:兩個結點之間有一條連通邊,則兩個結點是鄰接的,有時鄰接頂點稱為鄰居。
    • 自迴圈(環):連通一個頂點及其自身的邊
    • 環路:一種首頂點和末頂點相同且沒有重邊的路徑,沒有環路的圖稱為無環的。
  • 無向圖:一種邊為無序結點對的圖

    • 如果無向圖擁有最大數目的連通頂點的邊,則認為這個無向圖是完全的

      • 對有n個頂點的無向圖,要使圖完全就要求有n(n-1)/2條邊。
    • 如果無向圖中的任意兩個頂點之間都存在一條路徑,則認為這個無向圖是連通的

    • 無向圖的路徑是雙向的。
    • 路徑:圖中一系列邊,每條邊連通兩個頂點。
    • 無向樹是一種連通的無環無向圖,其中一個元素被指定為樹根。

  • 有向圖(雙向圖):邊為有序頂點對的圖,eg:邊(A,B)允許從A向B遊歷,但不允許反方向的遊歷。

    • 如果有向圖的每兩個頂點之間都有兩條方向相反的邊連線,則認為這個有向圖是完全的

    • 如果有向圖的任意兩個頂點之間都存在一條路徑,且連線兩個頂點的路徑中所有的邊都必須同向,則認為這個有向圖是連通的

    • 拓撲序(排列得到的頂點次序):如果有向圖中沒有環路,且有一條從A到B的邊,則可以把頂點A安排在頂點B之前。
    • 有向圖的路徑是單向的。
    • 路徑:圖中連通兩個頂點的有向邊序列。
    • 有向樹是一種指定了一個元素作為樹根的有向圖。

      • 不存在其他頂點到樹根的連線
      • 每個非樹根元素恰好有一個連線
      • 樹根到每個其他頂點都有一條路徑
  • 網路(加權圖):一種每條邊都帶有權重或代價的圖
    • 三元組來表示每條邊:起始頂點、終止頂點、權重

      • 無向網路的起始頂點與終止頂點可以互換
      • 有向圖必須包含每個有向連線的三元組
  • 圖演算法--遍歷
    • 廣度優先遍歷(類似樹的層序遍歷)

      • 首先從一個未走到過的頂點作為起始頂點,比如元素0頂點作為起點。
      • 沿0頂點的邊去嘗試訪問其它未走到過的頂點,首先發現2頂點還沒有走到過,於是來到了2頂點。
      • 返回到0頂點,再以0頂點作為出發點繼續嘗試訪問其它未走到過的頂點,這樣來到了1頂點。
      • 返回到0頂點,再以0頂點作為出發點繼續嘗試訪問其它未走到過的頂點,這樣來到了5頂點。
      • 但是,此時沿0頂點的邊,已經不能訪問到其它未走到過的頂點了,所以需要返回到2頂點。
      • 沿2頂點的邊去嘗試訪問其它未走到過的頂點,(因為1頂點已經走過了)首先發現3頂點還沒有走到過,於是來到了3頂點。
      • 返回到2頂點,再以2頂點作為出發點繼續嘗試訪問其它未走到過的頂點,這樣來到了4頂點。
      • 但是,此時沿4頂點的邊,已經不能訪問到其它未走到過的頂點了,至此,所有頂點我們都走到過了,遍歷結束。
    • 深度優先遍歷(類似樹的前序遍歷)

      • 首先從一個未走到過的頂點作為起始頂點,比如元素0頂點作為起點。
      • 沿0頂點的邊去嘗試訪問其它未走到過的頂點,首先發現2頂點還沒有走到過,於是來到了2頂點。
      • 再以2頂點作為出發點繼續嘗試訪問其它未走到過的頂點,這樣又來到了1頂點。
      • 再以1號頂點作為出發點繼續嘗試訪問其它未走到過的頂點。
      • 但是,此時沿1頂點的邊,已經不能訪問到其它未走到過的頂點了,所以需要返回到2頂點。
      • 返回到2號頂點後,以2頂點作為出發點繼續嘗試訪問其它未走到過的頂點,此時又會來到3頂點,再以3號頂點作為出發點繼續訪問其它未走到過的頂點,於是又來到了5號頂點。
      • 但是,此時沿5頂點的邊,已經不能訪問到其它未走到過的頂點了,所以需要返回到3頂點。
      • 返回到3頂點後,以3頂點作為出發點繼續嘗試訪問其它未走到過的頂點,此時會來到4頂點,再以4頂點作為出發點繼續訪問其它未走到過的頂點。
      • 但是,此時沿4頂點的邊,已經不能訪問到其它未走到過的頂點了,至此,所有頂點我們都走到過了,遍歷結束。
    • 圖的遍歷可以從任一頂點開始
    • 深度優先遍歷和廣度優先遍歷的位移不同是使用到了棧而不是佇列來管理遍歷。

  • 圖演算法--測試連通性
    • 在一個圖中,無論哪個為起始頂點,當且僅當廣度優先遍歷中的頂點數等於圖中的頂點數目時,則該圖是連通的。
  • 圖演算法--最小生成樹
    • 生成樹是一棵含有圖中所有頂點和部分邊(可能不是所有邊)的樹。
    • 最小生成樹是邊的權重總和和小於或等於同一個圖中其他任何一棵生成樹的權重總和。

      • 非連通的無向圖,不存在最小生成樹
      • 權重不一定和距離成正比
      • 權重可能是0或負數
      • 若存在相等的權重,那麼最小生成樹可能不唯一
    • Prim演算法:
  • 圖演算法--判斷最短路徑
    • 方法一:判定起始頂點與目標頂點之間的字面意義上的最短路徑,即兩個頂點之間的最小邊數。
    • 方法二:Dijkstra演算法

    • 以0頂點為開始位置,0頂點到1頂點的權重為3,到2頂點的權重為max,到3頂點的權重為4,到4頂點的權重為5。

    • 選取權重最小的頂點為1頂點,因為所有的權重都為正,那麼0頂點到1頂點的最短距離就是3,再以1頂點開始,1頂點可以到4頂點和2頂點,到4頂點的位置是3+1=4<5,即0頂點到4頂點的最短距離是通過1頂點的,再看到2頂點的位置是3+10=13<max,即0頂點直接到2頂點的距離為max,但是通過1頂點可以縮短為13。

    • 扣除0頂點和1頂點已經確定好了最短距離之後,選取最短的權重為0頂點到3頂點為4。

    • 以3頂點開始,3頂點可以到2頂點,到2頂點的位置是4+7=11<13,即0頂點到2頂點的最短距離,從max到通過1頂點再到通過3頂點到2頂點。

    • 扣除0頂點、1頂點和3頂點,已經確定好了最短距離之後,選取最小的權重為0頂點到1頂點再到4頂點為4,4頂點可以到1頂點、3頂點和2頂點,但是3頂點和1頂點已經確定好了,只剩2頂點,那麼到2頂點的位置為3+1+6=10<11,即到2頂點的最短距離改為通過1頂點、4頂點最後到2頂點。

    • 最後扣除0頂點、1頂點、3頂點和4頂點,只剩下2頂點,距離只剩10,所以到2頂點的最短距離為10,結束演算法。

  • 圖的實現策略
    • 鄰接列表

      • 在鄰接表的表示中,無向圖的同一條邊在鄰接表中儲存的兩次。如果想要知道頂點的讀,只需要求出所對應連結串列的結點個數即可。
      • 有向圖中每條邊在鄰接表中只出現一此,求頂點的出度只需要遍歷所對應連結串列即可。求出度則需要遍歷其他頂點的連結串列。
        無向圖的鄰接列表:

        有向圖的鄰接列表:
    • 鄰接矩陣

      • 在鄰接矩陣表示中,無向圖的鄰接矩陣是對稱的。矩陣中第 i 行或 第 i 列有效元素個數之和就是頂點的度。
      • 在有向圖中 第 i 行有效元素個數之和是頂點的出度,第 i 列有效元素個數之和是頂點的入度。
        無向圖的鄰接矩陣:

        有向圖的鄰接矩陣:
    • 鄰接矩陣與鄰接表優缺點:鄰接矩陣的優點是可以快速判斷兩個頂點之間是否存在邊,可以快速新增邊或者刪除邊。而其缺點是如果頂點之間的邊比較少,會比較浪費空間。因為是一個 n∗nn∗n 的矩陣。而鄰接表的優點是節省空間,只儲存實際存在的邊。其缺點是關注頂點的度時,就可能需要遍歷一個連結串列。還有一個缺點是,對於無向圖,如果需要刪除一條邊,就需要在兩個連結串列上查詢並刪除。
    • 邊集陣列

      • 邊集陣列由兩個一維陣列構成:一個儲存頂點資訊; 一個儲存邊的資訊,這個邊陣列每個資料元素由一條邊的起點下標(begin)、終點下標(end)、和權(weight)組成。
      • 邊集陣列關注的是邊的集合,在邊集陣列中要查詢一個頂點的度需要掃描整個邊陣列,效率並不高。因此它更適合對邊依次進行處理的操作,而不適合對頂點相關的操作。
        無向圖的邊集陣列:

        有向圖的邊集陣列:
    • 十字連結串列

      • 十字連結串列是為了便於求得圖中頂點的度(出度和入度)而提出來的。用十字連結串列來儲存有向圖,可以達到高效的存取效果。它是綜合鄰接表和逆鄰接表形式的一種鏈式儲存結構。

        綠色連結串列表示以結點A為弧頭的弧組成的連結串列。黃色連結串列表示以結點A為弧尾的弧組成的連結串列。
    • 鄰接多重表

      • 鄰接多重表主要用於儲存無向圖。如果用鄰接表儲存無向圖,每條邊的兩個邊結點分別在以該邊所依附的兩個頂點為頭結點的連結串列中,這給圖的某些操作帶來不便。因此,在進行這一類操作的無向圖的問題中採用鄰接多重表作儲存結構更為適宜。

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

  • 問題1:如何測試有向圖和無向圖的連通性?
  • 問題1解決方案:無向圖的連通性在書上已經給出,字面理解就是任意兩點之間都存在一條路徑,而從編寫的角度就是一個頂點開始深度優先遍歷或是廣度優先遍歷得出的頂點數。而有向圖的是具有方向的,如果這樣的話我們如何判定有向圖的連通性?在查詢的過程中,發現有向圖的連通性可以分成強連通圖、弱連通圖以及單向連通圖。書上的圖15.4的連通圖就是一個弱連通圖,如果是強連通圖則需要在此基礎上每兩點之間都新增一條與之方向相反的線。針對有向圖的測試連通性的方法可以用到Tarjan演算法

    • 在有向圖中, 若對於任意兩個兩點之間, 都存在互逆的路徑,則稱此圖是強連通圖。即有向圖中,若對於任意兩個不同的頂點x和y,都存在從x到y以及從y到x的路徑,則稱G是強連通圖。
    • 有向圖的所有的有向邊替換為無向邊,所得到的圖稱為原圖的基圖。如果一個有向圖的基圖是連通圖,則有向圖是弱連通圖。
    • 如果有向圖中,對於任意節點v1和v2,至少存在從v1到v2和從v2到v1的路徑中的一條,則原圖為單向連通圖。
    • 強連通圖、連通圖、單向連通圖三者之間的關係是,強連通圖必然是單向連通的,單向連通圖必然是弱連通圖。
    • 在Tarjan演算法中為每個節點i維護了以下幾個變數:
      DFN[i]:深度優先搜尋遍歷時節點i被搜尋的次序。
      low[i]:節點i能夠回溯到的最早位於棧中的節點。
      flag[i]:標記幾點i是否在棧中。
    • Tarjan演算法的執行過程:
      (1)首先就是按照深度優先搜尋演算法搜尋的次序對圖中所有的節點進行搜尋。
      (2)在搜尋過程中,對於任意節點u和與其相連的節點v,根據節點v是否在棧中來進行不同的操作:
      a.節點v不在棧中,即節點v還沒有被訪問過,則繼續對v進行深度搜索。
      b.節點v已經在棧中,即已經被訪問過,則判斷節點v的DFN值和節點u的low值的大小來更新節點u的low值。如果節點v的 DFN值要小於節點u的low值,根據low值的定義(能夠回溯到的最早的已經在棧中的節點),我們需要用DFN值來更新u 的low值。
      (3)在回溯過程中,對於任意節點u用其子節點v(其實不能算是子節點,只是在深度遍歷的過程中,v是在u之後緊挨著u的節點)的   low值來更新節點u的low值。因為節點v能夠回溯到的已經在棧中的節點,節點u也一定能夠回溯到。因為存在從u到v的直接路徑,所以v能夠到的節點u也一定能夠到。
      (4)對於一個連通圖,我們很容易想到,在該連通圖中有且僅有一個節點u的DFN值和low值相等。該節點一定是在深度遍歷的過程中,該連通圖中第一個被訪問過的節點,因為它的DFN值和low值最小,不會被該連通圖中的其他節點所影響。

程式碼學習中的問題和解決過程

  • 問題1:如何理解書中深度和廣度的遍歷程式碼?遞迴形式如何表示?
  • 問題1的解決方案:廣度優先遍歷和深度優先遍歷的區別是在於廣度優先遍歷用的是佇列,深度優先遍歷用的是棧,一個是先進先出另一個後進先出。
    • 遍歷是建立了一個無序列表和棧或是佇列,佇列或棧用來存放的是索引值,無序列表儲存的是結果,先進行判斷如果你想查詢的的索引值是超出用來存放內容的陣列的長度的話,就會直接迭代無序列表,注意此時的無序列表是沒有內容的,所以迭代出的內容也什麼也沒有。如果沒有超過則建立一個布林的一維陣列用來記錄每個索引值對應的內容是否被查過,先記錄每個索引值都未被查到,再將開始查詢元素的索引值存入棧或佇列中,並將該索引值對應的布林型陣列記憶體放的false改為true來確定是該元素已經查過。進入while迴圈變數x是剛入棧或是佇列的索引值,廣度優先遍歷是將該索引值對應的內容新增到無序列表的尾部,再進入for迴圈不斷將為未存元素存入佇列中,通過判斷存入佇列的元素是否與x元素有邊,如果有邊還未被查詢的元素就會被存入佇列中,然後跳出for迴圈在while迴圈中此時更換出隊的索引值,再進入for迴圈通過判斷條件來確定繼續存放在佇列中的索引值,這樣通過不同的索引值來實現圖上的每一個元素都會連著下一個元素,類似層序遍歷的思路。而深度優先遍歷迴圈體條件和判斷條件都相同,但是判斷條件內部不同,需要輔助一個布林型的變數來判斷是否與查詢元素有聯絡,深度優先遍歷與廣度優先遍歷不同的是深度有可能會遇到該元素沒有與之相連的邊的元素未被查到,而與它相連的邊的被查元素還有邊和未查元素,這樣就需要返回到上一個,在從上一個開始進行迴圈,而多出的那個判斷條件就是確定該元素是否符合上述可能情況如果符合的話就會彈出,到上一個元素位置進行查詢,這是廣度優先遍歷不會遇到的情況,也是改用棧的原因,深度優先遍歷類似與前序遍歷的思路。根據書上所寫可以用遞迴的方法編寫程式碼,附遞迴版程式碼(廣度優先遍歷的程式碼在網上查詢的)
    public void DFSRecursion() {
          boolean[] visited = new boolean[numVertices];
          for (int i = 0; i < numVertices; i++)
              visited[i] = false;
          for(int i=0;i<numVertices;i++) {
              if (!visited[i]) {
                  depthFirstSearch(visited,i);
              }
          }
      }
      private void depthFirstSearch(boolean[] isVisited,int  i) {
          System.out.print(vertices[i]+" ");
          isVisited[i]=true;
          int w=getFirstNeighbor(i);
          while (w!=-1) {
              if (!isVisited[w]) {
                  depthFirstSearch(isVisited,w);
              }
              w=getNextNeighbor(i, w);
          }
      }
    
      public void broadFirstSearch() {
          boolean[] visited = new boolean[numVertices];
          for (int i = 0; i < numVertices; i++)
              visited[i] = false;
          for(int i=0;i< numVertices;i++) {
              if(!visited[i]) {
                  broadFirstSearch(visited, i);
              }
          }
      }
      private void broadFirstSearch(boolean[] isVisited,int i) {
          int u,w;
          LinkedList queue=new LinkedList();
    
          System.out.print(vertices[i]+"  ");
          isVisited[i]=true;
          queue.addLast(vertices[i]);
          while (!queue.isEmpty()) {
              u=((Integer)queue.removeFirst()).intValue();
              w=getFirstNeighbor(u);
              while(w!=-1) {
                  if(!isVisited[w]) {
                      System.out.print(vertices[w]+"  ");
                      isVisited[w]=true;
                      queue.addLast(w);
                  }
                  w=getNextNeighbor(u, w);
              }
          }
      }

  • 問題2:PP15.7如何確定最短路徑和最便宜路徑?
  • 問題2的解決方案:根據題目要求允許客戶輸入兩個城市以及兩個城市之間的價格,這就類似加權圖,根據GraphADT介面以及實現的新增頂點和新增邊的方法都沒有涉及到權重(這裡的權重為兩個城市之間的價格),而無向圖實現的是確定兩個頂點之間是否有邊,用一個二維布林型陣列儲存對應頂點是否有邊,我們的想法是可以將是否有邊來改為數字,用票價來實現是否有聯絡。那麼問題又來了,沒有聯絡的如何來界定?定義為最大還是最小?如果定義為最小為零的話,我們的選擇就要在排除零的前提下進行Dijkstra演算法,但是如果我們將無聯絡的看作是無窮大的情況下,就會產生不一樣的效果,在選取權重較小即最便宜路徑的時候就會很方便,而且事實上提供的演算法也是要將無聯絡的設為最大,就如同教材內容總結部分敘述的演算法一樣。但是,無論是無窮大還是無窮小多是用什麼表示?和數序符號一樣麼?API提供瞭解決辦法(侯澤洋同學提供):Double.POSITIVE_INFINITY表示的是正無窮大值,Double.NEGATIVE_INFINITY表示的是負無窮大值。這樣就可以實現了,但是Dijkstra演算法如何書寫呢?又是一個問題好喪...在網上查了幾個,沒有適用成功所以通過看侯澤洋的部落格找到了一個不錯的部落格,用他的演算法來實現的具體程式碼,而且侯澤洋的程式碼層次設計的非常好,實現的也乾淨利落,很強大,是我值得學習的。此外,最短路徑就可以用到Graph內的shortestPathLength方法和iteratorShortestPath實現,我在此基礎上通過返回最短路徑的數值,如果是1的話就會存在直達列車,大於1的話就會是不可直達,在同通過遍歷最短路徑的迭代方法進行輸出最短路徑就好。

程式碼託管

上週考試錯題總結

錯題已經在上週部落格寫過...

結對與互評

點評(王禹涵)

  • 部落格中值得學習的或問題:
    • 教材總結寫的很好,排版整齊。盜圖還被稱為醜圖有點不太合適吧。問題2的排版可以再調整一下會更好的。
  • 程式碼中值得學習的或問題:
    • 迭代器的問題可以推薦看看第七章的內容,迭代器的輸出方式有兩種方法可以都去嘗試一下!
  • 基於評分標準,我給本部落格打分:7分。
    • 得分情況如下:
    • 正確使用Markdown語法(加1分)
    • 模板中的要素齊全(加1分)
    • 教材學習中的問題和解決過程, 二個問題加2分
    • 程式碼除錯中的問題和解決過程, 一個問題加1分
    • 感想,體會不假大空的加1分
    • 點評認真,能指出部落格和程式碼中的問題的加1分

點評(方藝雯)

  • 部落格中值得學習的或問題:
    • 圖片排版不錯,問題二的解決方案的文字部分可以稍稍修飾一下,堆排序的部分寫的很好很細緻,配圖也很精細。
  • 程式碼中值得學習的或問題:
    • 程式碼中的問題連結了我的部落格,有點受寵若驚。
  • 基於評分標準,我給本部落格打分:6分。
  • 得分情況如下:
    • 正確使用Markdown語法(加1分)
    • 教材學習中的問題和解決過程, 三個問題加3分
    • 程式碼除錯中的問題和解決過程, 無個問題加0分
    • 感想,體會不假大空的加1分
    • 點評認真,能指出部落格和程式碼中的問題的加1分

互評物件

感悟

第十五章的圖感覺和紅黑樹一樣,要構造很亂的資料結構來實現,此外還有一堆演算法可以優化圖的操作(演算法作用很好,但是難於理解,有的思路就難理解)。好在有些程式碼給出了,但是書上的遍歷程式碼就沒給全,導致始終讀不懂一個方法具體幹嘛的,具體實現的策略也不同於之前的資料結構。要保持好的心態來學習程式碼,切莫急功近利...

學習進度條

程式碼行數(新增/累積) 部落格量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 0/0 1/1 15/15
第二週 703/703 1/2 20/35
第三週 762/1465 1/3 20/55
第四周 2073/3538 1/4 40/95
第五週 981/4519 2/6 40/135
第六週 1088/5607 2/8 50/185
第七週 1203/6810 1/9 50/235
第八週 2264/9074 2/11 50/285
第九周 2045/11119 1/12 50/335

參考資料