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

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

教材學習內容總結

常見的非線性結構有兩種——樹和圖,在通過了三週對樹的學習以後,本週咱們接觸了另外一種非線性結構圖的相關內容,包括圖的概念、圖的分類、圖的實現方法等。html

1、圖的概述

  • 概念:樹中的每一個結點都只有一個父結點,若是咱們容許一個結點連通多個其餘結點,樹就變成了圖。
  • 相關術語:
    • 頂點(Vertex):圖中的數據元素。
    • 邊(Edge):圖中各個頂點之間的鏈接。
    • 鄰接/鄰居:兩個頂點之間有一條邊,則稱這兩個頂點是鄰接的。
    • 路徑:鏈接兩個頂點之間的一系列邊稱爲兩個頂點間的路徑,邊的條數稱爲路徑長度(路徑長度=頂點數-1)。
    • 環路:首頂點與末頂點相同且路徑中沒有邊重複的路徑。
  • 例:
    • 頂點:A,B,C,D
    • 邊:(A,B)(A,C)(B,C)(B,D)(C,D)
    • 鄰接:A與B是鄰接結點,A與D不是鄰接結點
    • 路徑:A→D——(A,B)(B,C)(C,D) 路徑長度爲3
    • 環路:A→A——(A,B)(B,C)(C,A)
  • 分類:
    • 【是否有方向】無向圖和有向圖
    • 【每條邊帶有權重或代價】加權圖/網絡(加權圖能夠是有向的也能夠是無向的)
    • 【特殊的圖】生成樹

1.無向圖

  • 無向圖是一種邊爲無序結點對的圖。在無向圖中,(A,B)(B,A)指的是一條邊,表示A與B之間有一條兩個方向都連通的邊。
  • 徹底:一個無向圖是徹底的,說明對於有n個頂點的無向圖,圖中有n(n-1)/2條邊。
  • 連通:若是無向圖中的任何兩個頂點之間都存在一條路徑,則認爲該無向圖是連通的。
    • 同時連通還分爲強連通和弱連通(非強連通),強連通圖中,任何兩個頂點之間都是連通的,就是說任何兩個頂點之間都至少有一條路徑。
    • 徹底圖必定是連通圖,連通圖不必定是徹底圖。
  • 無向樹:一種連通的沒有環路的,其中一個元素被指定爲樹根的圖。

2.有向圖

  • 有向圖/雙向圖是一種邊爲有序頂點對的圖。在無向圖中,(A,B)(B,A)指的不是一條邊,(A,B)表示從A到B有一條連通的邊,但B到A沒有。
  • 拓撲序:書上的說法是:「若是有向圖中沒有環路,且有一條從A到B的邊,則能夠把頂點A安排在頂點B以前,這種排列獲得的頂點次序稱爲拓撲序」。說實話我看了七八遍也沒看懂這句話說的是什麼意思,後來上百度搜了一下,有了下面這張圖就很好理解了。
  • 有向樹:其中一個元素被指定爲樹根的有向圖稱爲有向樹。

3.加權圖

  • 加權圖/網絡:是一種每條邊都帶有權重或代價的圖。加權圖中,某一條路徑的權重等於該路徑中全部邊權重的總和。
  • 加權圖中邊的表示:在普通的圖中,咱們表示邊時只須要起始頂點和終止頂點便可,可是在加權圖中,除了上面的兩項外還須要增長一個表示權重的元素。例如在有向圖中,從A到B之間有一條邊,權重爲3,那麼它的表示就爲(A,B,3)

4.生成樹

  • 概念:一顆含有圖中全部頂點和部分邊的樹。一個圖的生成樹不必定是惟一的
  • 最小/大生成樹:樹中的路徑權重總和小於/大於它所來源的圖中的任何一顆生成樹的權重總和。

2、圖的算法

(一)遍歷

  • 圖的遍歷分爲兩種:廣度優先遍歷(簡稱BFS,與樹中的層序遍歷相似)深度優先遍歷(簡稱DFS,與樹中的前序遍歷相似)。
  • 廣度優先遍歷——使用一個隊列和一個無序列表來實現,隊列用於管理遍歷,無序列表用於存儲遍歷結果。
    • 第一步:起始頂點進入隊列,標記爲已訪問。
    • 第二步:從隊列中取出起始頂點加入無序列表的末端,讓與該頂點相連的還未被標記爲已訪問的頂點加入隊列中,把它們都標記爲已訪問。
    • 第三步:重複第二步的操做,每次取出隊列中的首個頂點加入無序列表,直至隊列爲空。
  • 深度優先遍歷——使用一個棧和一個無序列表來實現,棧的做用與廣度優先遍歷中隊列的做用相同。
    • 第一步:起始頂點進入棧。
    • 第二步:從棧中取出起始頂點加入無序列表的末端,標記爲已訪問,讓與該頂點相連的頂點加入棧中。
    • 第三步:重複第二步的操做,每次取出棧頂元素加入無序列表,把頂點標記爲已訪問,直至棧爲空。
  • 實例
    • 廣度優先遍歷:九、六、七、八、三、四、五、一、2
      • 過程:
    • 深度優先遍歷:九、六、三、一、二、四、五、七、8
      • 過程:

(二)測試連通性

  • 要判斷一個圖的連通性,須要肯定圖中的任意兩個頂點之間都有一條路徑,若是直接判斷的話時間複雜度會很大。書上給出了一種簡單的判斷方法:在一個含n個頂點的圖中,當且僅當圖中的每一個頂點的廣度優先遍歷的無序列表長度都爲n時,證實該圖就是連通的。

(三)最小生成樹

  • 推衍算法(以最小生成樹爲例):在尋找最小樹的過程當中須要一個最小堆用於每次尋找最小邊
    • (1)從圖中任選一個起始頂點,將它添加到最下生成樹中
    • (2)將全部含起始頂點的邊按照權重由小到大的順序加入到最小堆中
    • (3)從最小堆中選出權重最小的邊,將該邊和與該邊鏈接的最小生成樹中沒有的頂點加入最小生成樹中,加入的頂點成爲新的起始頂點。
    • (4)重複第二和第三步直至最小生成樹中含有圖的全部頂點或最小堆爲空時。

(四)判斷最短路徑

  • 狀況一:最短路徑爲兩個頂點之間的最小邊數
    • 這種狀況下將廣度優先遍歷進行修改便可實現,修改方式爲在遍歷的過程當中增長兩個信息:從起始頂點到遍歷到該頂點的路徑長度(爲了便於計算最短路徑長度),以及路徑中該頂點的前驅結點((爲了便於輸出整條最短路徑)。
  • 狀況二:最短路徑爲加權圖中路徑權重總和最小的路徑
    • 這種狀況下仍然是對廣度優先遍歷進行修改便可。首先,把頂點隊列改成最小堆或優先隊列,這樣在存儲頂點時就能夠依據起始頂點到被存儲頂點的權重和的大小順序來進行存儲。和狀況一同樣,在遍歷過程當中須要增長兩個信息:一是從起始頂點開始到所遍歷頂點的最小路徑權重,而是路徑上該頂點的前驅。

3、圖的實現策略

(一)鄰接列表

  • 鄰接列表是一種特殊的鏈表,它的圖樣相似於哈希排序中的鏈地址法,頂點存儲在一個列表中,每一個頂點又擁有一個邊列表。對於無向圖而言,一條邊會同時出如今邊兩邊的兩個頂點的鄰接列表中。對於加權圖而言,每條邊還會存儲一個值表明該邊的權重。

(二)鄰接矩陣

  • 鄰接矩陣是表示圖形中頂點之間相鄰關係的矩陣,對於n個頂點的圖而言,該圖的鄰接矩陣有n行n列,每個(行,列)或(列,行)表明了兩個頂點之間的一條邊。對於無向圖,若是A1和A2之間有一條邊,那麼在二維矩陣中,matrix[A1,A2]和matrix[A2,A1]處的值爲1。對於有向圖,若是A1和A2之間有一條A1指向A2的邊,那麼matrix[A1,A2]處的值爲1,matrix[A2,A1]處的值爲0。對於加權圖,把相應位置的1換成權值便可。
  • 用鄰接矩陣實現無向圖
    • 構造函數:構造函數中指數把圖中的頂點數目設置爲0,同時構建一個鄰接矩陣和一個存儲頂點的泛型數組。
    protected final int DEFAULT_CAPACITY = 5;
    protected int numVertices;    // 頂點數目
    protected boolean[][] adjMatrix;    // 布爾值的矩陣,存儲頂點之間有無邊
    protected T[] vertices;    // 存儲頂點及頂點的value
    protected int modCount;
    
    public Graph()
    {
        numVertices = 0;
        this.adjMatrix = new boolean[DEFAULT_CAPACITY][DEFAULT_CAPACITY];
        this.vertices = (T[])(new Object[DEFAULT_CAPACITY]);
    }
    • addEdge方法:此方法用於在頂點之間添加邊,添加的方法有兩種,一種方法要輸入的參數是兩個頂點,另外一種方法要輸入的參數是兩個頂點在鄰接矩陣中的索引值。
    public void addEdge(int index1, int index2)
    {
        if (indexIsValid(index1) && indexIsValid(index2))
        {
            adjMatrix[index1][index2] = true;
            adjMatrix[index2][index1] = true;
            modCount++;
        }
    }
    
    // 使用頂點進行添加的方法實質上和上面用索引值添加的方法是同樣的,只不過它使用了一個getIndex方法來獲取頂點的索引值
    public void addEdge(T vertex1, T vertex2)
    {
        addEdge(getIndex(vertex1), getIndex(vertex2));
    }
    
    // getIndex方法經過遍歷存放頂點的泛型數組找到頂點的索引值,頂點在泛型數組中的索引值即爲它在鄰接矩陣中的索引值
    public int getIndex(T vertex)
    {
        for (int i = 0;i < numVertices;i++){
            if (vertices[i] == vertex){
                return i;
            }
            else{
                return -1;
            }
        }
        return -1;
    }
    • addVertex方法:此方法用於向圖中添加新的頂點,添加過程當中有兩個步驟,一是在泛型數組中添加該頂點,二是在鄰接矩陣中增長一行和一列,並把其中全部恰當的位置都設爲false。
    public void addVertex(T vertex)
    {        
        //當鄰接矩陣滿了的時候,對其進行擴容
        if ((numVertices + 1) == adjMatrix.length) {
            expandCapacity();
        }
    
        //步驟一:在泛型數組中添加該頂點
        vertices[numVertices] = vertex;
        //步驟二:把鄰接矩陣中的適當位置改成false
        for (int i = 0; i < numVertices; i++)
        {
            adjMatrix[numVertices][i] = false;
            adjMatrix[i][numVertices] = false;
        }        
        numVertices++;
        modCount++;
    }
    • expandCapacity方法:用鄰接矩陣實現無向圖中的擴容與其餘用數組實現的擴容方法不一樣,它不只要複製數組,還要對鄰接矩陣進行復制和擴容。
    protected void expandCapacity()
    {
        // 建立新的泛型數組和鄰接矩陣
        T[] largerVertices = (T[])(new Object[vertices.length*2]);
        boolean[][] largerAdjMatrix = new boolean[vertices.length*2][vertices.length*2];
    
        // 外層循環進行泛型數組的複製,同時協助內層循環進行鄰接矩陣的複製
        for (int i = 0; i < numVertices; i++)
        {
            // 內層循環進行鄰接矩陣的複製
            for (int j = 0; j < numVertices; j++)
            {
                largerAdjMatrix[i][j] = adjMatrix[i][j];
            }
            largerVertices[i] = vertices[i];
        }
        // 將擴容後的兩者賦值給原來的泛型數組和鄰接矩陣
        vertices = largerVertices;
        adjMatrix = largerAdjMatrix;
    }

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

  • 問題1:在看書上關於如何生成最小生成樹的內容時,不能理解樹是怎麼找出來的
  • 問題1解決方案:當時看的時候,關於「下一步咱們往minheap中添加全部含該新頂點且另外一頂點尚不在最小生成樹中的邊」,我一開始的理解是每次加入新頂點後,邊只能從與該頂點鏈接的邊中選擇,這樣的話就與任選一個起始頂點衝突了,由於只有在選擇特定頂點時才能找出最小生成樹,選擇其餘頂點時都會有多餘的邊。後來我與個人結對夥伴張昊然同窗進行了討論,他一語點醒夢中人,原來是我對另外一頂點尚不在最小生成樹中的邊理解有誤,它說的是全部在最小堆中的邊而不須要必須是最小堆中與最新頂點相連的邊,由於最小堆中的邊都知足一邊的頂點在最小生成樹中,另外一頂點不在最小生成樹中這一條件。
  • 【補充】這週五上完課以後發現以前的理解並非對的,書上給的Prim法要求的就是每一次都要以上一會新加入的結點爲一段來尋找另外一端不在最小生成樹中的權重最小的邊,Prim方法的核心是每次加入一個頂點,而我上面說的每次從最小堆中取出最小邊的方法應該算是Kruscal方法,核心是每次添加一條權重最小的邊
  • 問題2:用鄰接列表實現無向圖的代碼實現
  • 問題2:解決方法:鄰接列表實現無向圖相比用鄰接矩陣實現我以爲其實要簡單許多,主要體如今添加/刪除邊上,由於用鄰接列表實現不須要經過循環來找到鏈接邊的兩個頂點的位置。
    • 構造函數:在設置構造函數以前,須要設置一個列表來做爲頂點集存儲全部的頂點,每一個頂點又要做爲一個鏈表的頭,因此還須要一個結點類,這個結點類能夠用以前的也能夠本身從新寫一個,在這裏我從新寫了一個VerticeNode類
    private ArrayList<VerticeNode> vertices; // 用於存儲頂點
    private int numVertices; // 記錄頂點的個數
    private int modCount;
    
    public LinearGraph(){
        numVertices = 0;
        modCount = 0;
        a = 0;
        vertices = new ArrayList<VerticeNode>();
    }
    • 添加頂點:將頂點的元素實例化爲VerticeNode類後加入隊列便可
    @Override
    public void addVertex(Object vertex) {
        VerticeNode node  = new VerticeNode(vertex);
        vertices.add(node);
        modCount++;
        numVertices++;
    }
    • 刪除頂點:刪除頂點分爲兩步,第一步是直接將頂點從頂點列表裏刪除,第二步是將頂點列表中所刪除頂點以後的頂點所有前移,保持列表的連貫性。
    @Override
    public void removeVertex(Object vertex) {
        int i = 0;
        // 將目標頂點刪除
        while (vertices.get(i).getElement() != vertex){
            i++;
        }
        vertices.remove(i);
        // 將頂點以後的列表裏的元素前移
        for (int j = 0;j < numVertices;j++){
            VerticeNode temp = vertices.get(j);
            while (temp.getNext() != null){
                if (temp.getNext().getElement() == vertex){
                    temp.setNext(temp.getNext().getNext());
                }
                temp = temp.getNext();
            }
            break;
        }
        modCount--;
        numVertices--;
    }
    • 添加邊:添加邊時首先要找到頂點所在的位置,在找到以後將另一個頂點添加到該頂點的鏈表末尾,接着再對另外一個頂點進行相同的操做。
    @Override
    public void addEdge(Object vertex1, Object vertex2) {
        // 尋找頂點要被添加的位置
        int i = 0;
        while (vertices.get(i).getElement() != vertex1){
            i++;
        }
        // 在頂點一的鏈表末端添加頂點二,在兩頂點之間創建邊
        VerticeNode temp = vertices.get(i);
        while (temp.getNext() != null){
            temp = temp.getNext();
        }
        temp.setNext(new VerticeNode(vertex2));
    
        // 對頂點二的鏈表進行上述相同操做
        int j = 0;
        while (vertices.get(j).getElement() != vertex2){
            j++;
        }
        VerticeNode temp1 = vertices.get(j);
        while (temp1.getNext() != null){
            temp1 = temp1.getNext();
        }
        temp1.setNext(new VerticeNode(vertex1));
    }
    • 刪除邊:在刪除邊時,首先要找到所刪除的邊的兩端頂點的位置,在進行刪除時分爲兩種狀況,一是所刪除的結點位於鏈表末端,二是所刪除的結點位於鏈表中間。
    @Override
    public void removeEdge(Object vertex1, Object vertex2) {
        // 找到所要刪除的頂點的位置
        int i = 0;
        while (vertices.get(i).getElement() != vertex1){
            i++;
        }
        // 在頂點一的鏈表中找到頂點二的前驅結點
        VerticeNode temp = vertices.get(i);
        while (temp.getNext().getElement() != vertex2){
            temp = temp.getNext();
        }
        // 若是頂點二位於末端,直接將temp的next設爲空便可
        if (temp.getNext().getNext() == null){
            temp.setNext(null);
        }
        // 若是頂點二位於中間,將temp的next設置爲頂點二的next便可
        else {
            temp.getNext().setNext(temp.getNext().getNext());
        }
    }

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

  • 問題一:在測試PP15.1時,輸出的全爲null
  • 問題一解決方法:我經過Debug發現賦值是賦值好了的,可是每個node結點雖然有值,可是仍然顯示爲空。
  • 後來詢問告終對夥伴以後他說不要把頂點設置成VerticeNode類,直接進行添加就能夠了。

代碼託管

  • 上週代碼量:18061

上週考試錯題總結(正確爲綠色,錯誤爲紅色)

上週沒有測試。java

結對及互評

點評模板:

  • 博客中值得學習的或問題:
    • 個人結對夥伴如今在教材內容總結方面一直保持着很認真的態度,可是但願從此的教材總結能夠在內容豐富的基礎上更加條理一點~除此以外他的問題總結方面仍是一如既往地保持了坦誠直接的風格,有問題就是有問題,沒問題就是沒問題,繼續加油吧~

點評過的同窗博客和代碼

  • 本週結對學習狀況
    • 20172322
    • 結對學習內容
      • 教會了我博客園修改字體樣式的方法。
      • 交流了課本內容。

其餘(感悟、思考等,可選)

  • 感受本身最近對java的學習有點不像之前那麼上心了,並且很愛拖沓,接下來要努力把剩下的課程堅持下來。

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 10/10 1/1 10/10
第二週 246/366 2/3 20/30
第三週 567/903 1/4 10/40
第四周 2346/3294 2/6 20/60
第五週 2346/3294 2/8 30/90
第六週 1343/4637 2/8 20/110
第七週 654/5291 1/9 25/135
第八週 2967/8258 1/10 15/150
第九周 2871/11129 2/12 20/170
  • 計劃學習時間:20小時
  • 實際學習時間:20小時
  • 改進狀況:由於經歷了樹以後其實我對學習圖仍是挺懼怕的,幸運的是它沒有我想的那麼難,太好了。

參考資料

相關文章
相關標籤/搜索