20172314 2018-2019-1《程序設計與數據結構》第九周學習總結

教材學習內容總結

無向圖

  • 與樹相似,由頂點和邊鏈接構成。圖只有頂點沒有邊時,也是圖,是一種特殊形式。
  • 由相互連通的結點對錶示,(A,B)表示從頂點A到頂點B有一條邊,無序結點對表示頂點A,B之間的邊能夠從兩個方向遊歷,邊記作(A,,B)或(B,A)都可
  • 無向圖就是邊全爲無序結點對的圖
    html

  • 兩個頂點之間如有連通邊,則稱這兩個頂點是鄰接的。他們互爲鄰居
  • 路徑表示一系列的邊,路徑的長度表示路徑中邊的條數(或頂點數減一);無向圖中路徑ABD與路徑DBA等同。
  • 注意區分:若是無向圖擁有最大數目的連通頂點的邊,則無向圖是徹底的;若是無向圖中任意兩個頂點之間都存在一條路徑,則認爲無向圖是連通的。如圖是非連通無向圖,D與另外三個頂點之間均沒有任何路徑。
    java

  • 注意區分:聯通一個節點及其自身的邊稱爲自循環,邊(A,A)表示鏈接A到自身的一個環;環路是一種首頂點和末頂點相同且沒有重邊的路徑
  • 沒有環路的圖稱爲無環的
  • 無向樹是一種連通的無環無向圖,其中一個元素被指定爲樹根node

有向圖

  • 有向圖也稱雙向圖,是一種邊爲有序頂點對的圖,邊(A,B)和(B,A)是不一樣的有向邊
    git

  • 有向圖的路徑是鏈接兩個頂點的有向邊序列。路徑ABD與路徑DBA是不一樣的
  • 如有向圖中任意兩個頂點之間都存在一條路徑,則認爲該有向圖是連通的。下圖中第一個圖是連通的,第二個圖不是,由於沒有任何路徑能從其它頂點遊歷到1,也不能從6出發遊歷任何其餘頂點
    算法

  • 若是有向圖中沒有環路,且有一條從A到B的邊,那麼就能夠把A安排在頂點B以前,這種排列獲得的頂點次序稱爲拓撲序
  • 有向樹是一種指定了一個元素做爲樹根的有向圖,該圖具備以下屬性:
    • 不存在其餘頂點到樹根的鏈接
    • 每一個非樹根元素剛好有一個鏈接
    • 樹根到每一個其它頂點都有一條路徑

網絡

  • 網絡又稱爲加權圖,是一種每條邊都帶有權重或代價的圖
  • 加權圖中的路徑權重是該路徑各邊權重之和
  • 用三元組表示每條邊,包括起始頂點、終止頂點和權重。對於有向圖來講,必須包含每一個有向鏈接的三元組

經常使用的圖算法

  • 遍歷
    • 廣度優先遍歷(BFS):相似於樹的層次遍歷,使用隊列和無序列表
    • 深度優先遍歷(DFS):相似於樹的前序遍歷,使用棧和無序列表
  • 測試連通性
    • 簡單解釋:在一個含有n個頂點的圖中,當且僅當對每一個頂點v,從v開始的廣度優先遍歷的resultList大小都是n,則該圖就是連通的。例如

    無向連通圖及以每一個頂點爲起點的廣度優先遍歷


    無向非連通圖及以每一個頂點爲起點的廣度優先遍歷

    數組

  • 最小生成樹
    • 生成樹是一棵含有圖中全部頂點和部分邊(但可能不是全部邊)的樹。
    • 因爲樹也是圖,有些圖自己就是一棵生成樹,這時該圖的惟一輩子成樹將包含全部邊。
    • 最小生成樹(保持圖連通的最少邊)(生成樹不是惟一的)邊的權重總和小於或等於同一圖中其餘任何一棵生成樹的權重總和
    • 最小生成樹集合包括n個點和n-1條邊。且沒有迴路。
    • 最小生成樹生成過程:從網絡中任意選取一個起始頂點,並添加到最小生成樹中,而後將全部含起始頂點的邊按照權重次序添加到minheap中(若是處理的是有向網絡,則只會添加那些以這個特定頂點爲起點的邊),接着從minheap中取出最小邊,並與新頂點添加到最小生成樹中。往minheap中添加全部含該新頂點且另外一頂點尚不在最小生成樹中的邊。繼續這一過程,直到最小生成樹含有原始圖中的全部頂點(或minheap爲空)時結束。
  • 判斷最短路徑
    • 兩頂點之間最小邊數
      • 在編歷時記錄從起始頂點到本頂點的路徑長度,以及路徑中做爲本頂點前驅的那個頂點。並當抵達目標頂點時循環終止,最短路徑長度就是獲得的路徑長度加1
    • 加權圖的最便宜路徑
      • 使用minheap或優先隊列來存儲頂點,基於總權重衡量頂點對,每一個頂點都必須存儲該頂點的標籤(本頂點以前最便宜的路徑權重及路徑上本頂點的前驅),從minheap取出頂點的時候,會權衡頂點對,若是未按由小到大取出頂點,則會更新路徑

圖的實現策略

  • 鄰接列表
    • 用相似於鏈表的動態結點來存儲每一個結點帶有的邊。這種鏈表稱爲鄰接列表
    • 對網絡或加權圖來講,每條邊會存儲成一個含權重的三元組;對無向圖而言,邊(A,B)會同時出如今頂點A和頂點B的鄰接列表中
  • 鄰接矩陣
    • 能夠用集合儲存邊
    • 用稱爲鄰接矩陣的二維數組存儲頂點,每一個單元表示兩個頂點的交接狀況,由表示是否連通的布爾值表示
    無向圖的鄰接矩陣

    由於是無向圖,因此該矩陣沿對角線對稱,只需給出一側便可
    有向圖的鄰接矩陣

    鄰接矩陣也能夠用於網絡或加權圖,只須要在矩陣的各個單元中存儲一個表明邊權重的對象,沒有邊的單元爲null
  • 用鄰接矩陣實現無向圖
    • addEdge方法:使用getIndex方法定位索引並調用addEdge方法進行賦值
    • addVertex方法:往圖中添加一個頂點包括在數組的下一個可用位置添加該頂點,把鄰接矩陣中全部恰當的位置都設置成false
    • expandCapacity方法:不只擴展頂點數組並把已有頂點複製到新數組中,並且還必須擴展鄰接列表的容量並把舊內容複製到新列表中

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

  • 問題一:廣度優先遍歷和深度優先遍歷的實現過程及其原理
  • 問題一解決:經過查找資料其過程可認爲:網絡

    廣度優先遍歷(使用一個隊列):
    • 首先選擇一個頂點做爲起始頂點,讓起始頂點進入隊列中,並將其染成灰色(visited),其他頂點爲白色。
    • 開始循環:從隊列首部選出一個頂點,添加到resultList末端(塗黑),並找出全部與之鄰接的頂點,放入隊列尾部(塗灰),沒訪問過的頂點是白色。而後再次取出新的起始頂點塗黑放入resultList中,如此循環。若是頂點的顏色是灰色,表示已經發現而且放入了隊列,若是頂點的顏色是白色,表示尚未發現。
    • 基本就是出隊的頂點變成黑色,在隊列裏的是灰色,還沒入隊的是白色。





    深度優先遍歷(使用棧):
    • 其整體思想爲:訪問頂點v,依次從v的未被訪問的鄰接點出發,到一條路徑的盡頭時,將其入棧返回上一個頂點,若其有其餘孩子,繼續向下。直到全部頂點都被訪問。

    過程爲:
    • 先往棧中壓入右節點,再壓左節點,這樣出棧就是先左節點後右節點了
    • 首先將頂點A壓入棧中,stack(A)
    • 將A彈出,進入無序列表中,同時將v的右孩子B和左孩子C壓入棧中,此時左孩子在棧的頂部,stack(B,C)
    • 將B頂點彈出,同時將B的子頂點E,D壓入棧中,此時D在棧的頂部,stack(D,E,C)
    • 將D頂點彈出,沒有子頂點壓入,此時E在棧的頂部,stack(E,C)
    • 將E頂點彈出,同時將E的子頂點I壓入,stack(I,C)
    • 依次往下,最終遍歷完成

    二者之間惟一不一樣之處是:深度優先遍歷使用棧而不是隊列來管理遍歷學習

  • 問題二:圖的鄰接列表較難理解
  • 問題二解決:鄰接列表是對每一個頂點創建一個鏈表,表示以改頂點爲起點的全部頂點。經過訪問一個頂點的鏈表便可得出該頂點的全部邊。由此也可也得出以改頂點爲起點的一條路徑。
    有向圖:


    一共有1234四個頂點,因此創建四個頂點,以1爲起點的有234,以2爲起點的只有4,以3爲起點的沒有,以4爲起點的只有3。
    無向圖:


    一共有ABCD四個頂點,創建4個鏈表。A頂點鏈接的邊是BCD,與B頂點鏈接的是ABD,與C頂點鏈接的只有A,與D頂點鏈接的是AB。測試

代碼調試中的問題和解決過程

  • 問題一:在iteratorBFS方法中,spa

    if (!indexIsValid(startIndex))
            return resultList.iterator();
    不清楚iterator方法的做用
  • 問題一解決:首先,這個方法的做用是爲了使類可迭代,使類迭代須要有如下幾步:
    • 在類聲明中加入implements Iterable ,對應的接口爲(即java.lang.Iterator)

      public interface Iterable<Item>{
          Iterator<Item> iterator();
      }
    • 在類中實現iterator()方法,返回一個本身定義的迭代器Iterator

      public Iterator<Item> iterator(){
          //若是須要逆序遍歷數組,自定義一個逆序迭代數組的迭代器
           return new ReverseArrayIterator();
      }
    • 在類中設置內部類(如private class ReverseArrayIterator() ),內部類聲明中加入implements Iterator ,對應的接口爲(即java.util.Iterator)

      public interface Iterator {  
            boolean hasNext();  
            Object next();  
            void remove();  
      }

      這些是在第四周中學到過的,好比ArrayList類,徹底符合以上步驟。
      在這裏,因爲索引無效,必需要返回一個相同類型的值,實際上就是空的。

  • 問題二:對廣度優先遍歷代碼的理解
  • 問題二解決:見註釋

    private Iterator<T> iteratorBFS(int startIndex) {
        Integer x;
        QueueADT<Integer> traversalQueue = new LinkedQueue<Integer>();
        UnorderedListADT<T> resultList = new ArrayUnorderedList<T>();
        //索引無效,返回空
        if (!indexIsValid(startIndex))
            return resultList.iterator();
        boolean[] visited = new boolean[maxCount];
        //把全部頂點設爲false,白色
        for (int i = 0; i < maxCount; i++)
            visited[i] = false;
        //進入隊列的爲true,即訪問過的,灰色
        traversalQueue.enqueue(startIndex);
        visited[startIndex] = true;
        while (!traversalQueue.isEmpty()) {
            //出隊列塗黑存入resultList中
            x = traversalQueue.dequeue();
            resultList.addToRear((T) nodelist.get(x).getElement());
            //若是進入resultList的頂點還有相鄰的未訪問過的頂點,將其塗灰入隊
            for (int i = 0; i < maxCount; i++) {
                if (hasEdge(x, i) && !visited[i]) {
                    traversalQueue.enqueue(i);
                    visited[i] = true;
                    Int++;
                }
            }
        }
        return new GraphIterator(resultList.iterator());
        }
  • 問題三:最短路徑的代碼理解,不知道iteratorShortestPathIndices方法和iteratorShortestPath方法的聯繫和區別。
  • 問題三解決:iteratorShortestPathIndices方法構建最短路徑中頂點集合的迭代器,而後iteratorShortestPath方法獲取迭代器並輸出構成最短路徑的結點。
    • iteratorShortestPath方法
    //最短路徑的頂點集
    private Iterator<T> iteratorShortestPath(int startIndex, int targetIndex)
    {
        UnorderedListADT<T> resultList = new ArrayUnorderedList<T>();
        //若是索引值都無效,返回空
        if (!indexIsValid(startIndex) || !indexIsValid(targetIndex))
            return resultList.iterator();
        //it表示構成startindex和targetindex之間最短路徑的頂點集,並存儲在resultlist鏈表中,獲取結點集合的迭代器對象
        Iterator<Integer> it = iteratorShortestPathIndices(startIndex, targetIndex);
        while (it.hasNext())
            resultList.addToRear((T)nodelist.get(((Integer)it.next())).getElement());
        return new GraphIterator(resultList.iterator());
    }
    • iteratorShortestPathIndices方法,構建從開始到結束的節點集合的迭代器對象
    //找到最短路徑的頂點值
    private Iterator<Integer> iteratorShortestPathIndices(int startIndex, int targetIndex)
    {
        int index = startIndex;
        int[] pathLength = new int[maxCount];//路徑長度數組
        int[] predecessor = new int[maxCount];//前驅結點
        QueueADT<Integer> traversalQueue = new LinkedQueue<Integer>();
        UnorderedListADT<Integer> resultList = new ArrayUnorderedList<Integer>();
        //若是索引無效或起始終點爲同一索引,返回空
        if (!indexIsValid(startIndex) || !indexIsValid(targetIndex) || (startIndex == targetIndex))
            return resultList.iterator();
        boolean[] visited = new boolean[maxCount];//訪問過爲true,染灰
        //先標記爲都沒訪問過,染白
        for (int i = 0; i < maxCount; i++)
            visited[i] = false;
        //將起始值入隊標爲訪問過,染灰
        traversalQueue.enqueue(Integer.valueOf(startIndex));
        visited[startIndex] = true;
        pathLength[startIndex] = 0;//路徑長度爲0
        predecessor[startIndex] = -1;//前驅結點爲-1位置
        //若是還有訪問過的灰色,而且未找到目標索引
        while (!traversalQueue.isEmpty() && (index != targetIndex))
        {
            index = (traversalQueue.dequeue()).intValue();//出隊列染黑,index儲存其元素值
            //若是有其餘結點與index有聯繫卻沒有被訪問過的,入隊染灰
            for (int i = 0; i < maxCount; i++)
            {
                if (hasEdge(index,i) && !visited[i])
                {
                    pathLength[i] = pathLength[index] + 1;//長度加一
                    predecessor[i] = index;//index爲其前驅結點,predecessor中存儲最短路徑頂點
                    traversalQueue.enqueue(Integer.valueOf(i));//進隊染灰
                    visited[i] = true;
                }
            }
        }
        //若是index不是目標索引,返回空
        if (index != targetIndex)
            return resultList.iterator();
        StackADT<Integer> stack = new LinkedStack<Integer>();
        index = targetIndex;
        stack.push(Integer.valueOf(index));//目標索引入棧
        do
        {//index不是起始索引值的元素值時,index表示其前驅結點的元素值,將結點的值依次入棧
            index = predecessor[index];
            stack.push(Integer.valueOf(index));
        } while (index != startIndex);
        while (!stack.isEmpty())
            resultList.addToRear(((Integer)stack.pop()));//棧不爲空時,彈出結點添加到resultlist鏈表
        return new GraphIndexIterator(resultList.iterator());//resultlist中儲存的就是最短路徑的頂點集
    }
  • 問題四:出現從未遇到過的異常:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  • 問題四解決:有三種可能致使此類錯誤的狀況:
    • 真實的存在內存泄漏
    • 沒有爲應用程序提供足夠多的內存,這種狀況下,能夠增長JVM堆可用空間大小,或者減小程序所需內存量
    • 程序中無意的對象引用保持,使得沒有明確的釋放對象,以至於堆增加再增加,直到沒有額外的空間。
      網上的解決辦法:

      選中被運行的類,點擊菜單‘run->run...’,選擇(x)=Argument標籤頁下的vm arguments框裏
      輸入 -Xmx800m, 保存運行。

    但我沒怎麼明白具體操做,我認爲個人錯誤多是第三種狀況,因而把第一週中inkedStack方法檢查了一下,發現方法size,isEmpty,和toString不完善,在其餘方法的條件中調用時就有可能出錯,修改以後問題解決。

代碼託管

上週考試錯題總結

結對及互評

  • 譚鑫20172305:譚鑫的博客中最突出的就是他的擴展學習不少,好比Tarjan算法是我沒有了解的,能夠說很用心了,博客總結的一直很詳細,小小的建議就是但願解釋的時候能夠分一下層次,一大段一大段的有點難閱讀(o´ω`o)ノ

  • 王禹涵20172323:王禹涵的博客總體很好,及哦啊才問題總結詳細,有一個問題就是不太明白代碼問題二。

其餘

關於圖的概念有邏輯性,容易理解,可是問題仍是在代碼理解上,我以爲Java學習不能停留在知識表面,要深刻理解代碼,並學會構造纔是最終目的。

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積)
目標 5000行 30篇 400小時
第一週 0/0 1/1 8/8
第二週 1163/1163 1/2 15/23
第三週 774/1937 1/3 12/50
第四周 3596/5569 2/5 12/62
第五週 3329/8898 2/7 12/74
第六週 4541/13439 3/10 12/86
第七週 1740/15179 1/11 12/97
第八週 5947/21126 1/12 12/109
第九周 7968/29094 2/14 12/121

參考

相關文章
相關標籤/搜索