2018-2019-20172321 《Java軟件結構與數據結構》第九周學習總結

2018-2019-20172321 《Java軟件結構與數據結構》第九周學習總結

教材學習內容總結

第15章 圖

無向圖

  • 圖由頂點組成。
    • 頂點由名字或標號來表示,如:A、B、C、D;
    • 邊由鏈接的頂點對來表示,如:(A,B),(C,D),表示兩頂點之間有一條邊。
  • 無向圖:與樹相似,圖也由結點和這些結點之間的鏈接構成。這些結點是頂點,而結點之間的連接是邊。無向圖是一種邊爲無序結點對的圖。因而,記作(A,B)的邊就意味着A與B之間有一條從兩個方向均可以遊歷的鏈接。邊記做(A,B)和記做(B,A)的含義是徹底同樣的。

  • 鄰接(鄰居):兩個頂點之間有邊鏈接。
  • 自循環(懸掛):本身鏈接到本身的邊。
  • 徹底圖:含有最多 條邊的無向圖。例如:

無向圖G是一個徹底圖。html

  • 路徑:鏈接圖中兩個頂點的邊的序列,能夠由多條邊組成。java

    無向圖中的路徑是雙向的。ios

  • 路徑長度:路徑中所含邊的數目(頂點個數減1 )。
  • 連通圖:無向圖中 任意兩個頂點間都有路徑。例如:git

徹底圖必定是連通圖,連通圖不必定是徹底圖。算法

  • 環(迴路):第一個頂點與最後一個頂點相同且沒有重複邊的路徑。例如:

  • 無環圖:沒有環的圖。
  • 子圖:相似集合中「子集」的概念,示例以下:

其中,(b),(c)是(a)的子圖。數組

  • 生成樹:包含無向圖G1全部頂點的極小連通子圖稱爲G1的生成樹。例如:

  • 稀疏圖與稠密圖:有不多條邊或弧(如 e < n lognn,n 是圖的頂點數,e 是弧數)的圖稱爲稀疏圖,反之稱爲稠密圖。

有向圖

  • 有向圖:頂點之間有序鏈接,邊是頂點的有序對。網絡

    邊(A,B)和(B,A)方向不一樣。數據結構

  • 有向路徑:鏈接兩個頂點有向邊的序列。
  • 有向圖中的有序對經常使用序偶表示,例如:函數

上圖中路徑 V00→V22→V33 是從V00到V33的路徑,可是反過來再也不成立。學習

  • 注意連通有向圖與無向圖不一樣,有向邊決定連通性。例如:

上圖中左圖爲聯通圖,右圖不聯通,由於從任何頂點到頂點1都沒有路徑。

  • 有向樹是一個有向圖,其中指定一個元素爲根,則具備下列特性:

    任何頂點到根都沒有鏈接。

到達每一個非根元素的鏈接都只有一個。

從根到每一個頂點都有路徑。

  • 頂點的度、出度、入度:

網絡

  • 網絡:(加權圖)是一種每條邊都帶有權重或代價的圖。

網絡能夠是無向的也能夠是有向的。

經常使用圖的算法

  • 遍歷

  • 廣度優先遍歷:相似於樹的層序遍歷, 從一個頂點開始,輻射狀地優先遍歷其周圍較廣的區域。
    • 廣度優先遍歷算法:若頂點A先於頂點B被訪問,則頂點A的鄰接點也先於頂點B的鄰接點被訪問。特色:先把起始頂點附近的頂點訪問完,再訪問遠處的頂點。在廣度優先遍歷算法的具體實現中,須要兩個隊列。一個輔助遍歷,保存遍歷過程當中遇到的頂點,當訪問完成了某個頂點A後,將A出隊列,緊接着將A的全部鄰接點都入隊列,並訪問。
  • 深度優先遍歷:圖的深度優先搜索,相似於樹的先序遍歷, 所遵循的搜索策略是儘量「深」地搜索圖。
    • 在深度優先遍歷中,須要兩個棧,這裏能夠看出深度優先遍歷帶有遞歸的性質。一個棧用來輔助遍歷,即用來保存遍歷過程當中裏面的頂點,另外一個棧用來保存遍歷的順序。之因此另外須要一個棧來保存遍歷的順序的緣由 與 廣度優先遍歷 中須要用另外一個隊列來保存 遍歷順序 的緣由相同。當深度優先遍歷到某個頂點時,若該頂點的全部鄰接點均已經被訪問,則發生回溯,即返回去遍歷 該頂點 的 前驅頂點 的 未被訪問的某個鄰接點。

      圖的深度優先遍歷與廣度優先遍歷的惟一不一樣是,他使用的是棧而不是隊列來管理遍歷。

  • 連通性:從任意結點開始的廣度優先遍歷中獲得的頂點數等於圖中所含頂點數。
  • 生成樹:包含圖中全部頂點及圖中部分邊的一棵樹。
  • 最小生成樹:所含邊權值之和小於其餘生成樹的邊的權值之和。
    • 由於樹老是圖,對於有些圖來講,圖自己就是一顆生成樹,因此這樣的圖的生成樹中將包含所有的邊。
    • 最小生成樹是其所含邊的權值之和小於等於圖的任意其餘生成樹的邊的權值之和的生成樹。

一般構造最小生成樹的算法有兩種:

① Prim算法: ① Prim算法: ① Prim算法:
它是從點的方面考慮構建一顆MST,大體思想是:設圖G頂點集合爲U,首先任意選擇圖G中的一點做爲起始點a,將該點加入集合V,再從集合U-V中找到另外一點b使得點b到V中任意一點的權值最小,此時將b點也加入集合V;以此類推,如今的集合V={a,b},再從集合U-V中找到另外一點c使得點c到V中任意一點的權值最小,此時將c點加入集合V,直至全部頂點所有被加入V,此時就構建出了一顆MST。由於有N個頂點,因此該MST就有N-1條邊,每一次向集合V中加入一個點,就意味着找到一條MST的邊。

② Kruskal算法: ② Kruskal算法: ② Kruskal算法:
假設 WN=(V,{E}) 是一個含有 n 個頂點的連通網,則按照克魯斯卡爾算法構造最小生成樹的過程爲:先構造一個只含 n 個頂點,而邊集爲空的子圖,若將該子圖中各個頂點當作是各棵樹上的根結點,則它是一個含有 n 棵樹的一個森林。以後,從網的邊集 E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不一樣的樹,則將其加入子圖,也就是說,將這兩個頂點分別所在的兩棵樹合成一棵樹;反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之。依次類推,直至森林中只有一棵樹,也即子圖中含有 n-1條邊爲止。

  • 斷定最短路徑:
    • 斷定起始頂點和目標頂點之間是否存在最短路徑(兩個頂點之間邊數最少的路徑)。
    • 在帶權圖中找到最短路徑。(Dijkstra算法)
    • 最短路徑算法:在邊不帶權值的圖中求頂點A到頂點B的最短路徑--其實就是頂點A到頂點B之間的最少邊的條數。調用最短路徑算法以前,首先要肯定一個初始頂點,圖中其餘頂點的路徑長度都是相對於初始頂點而言的。求兩個頂點間最短路徑,其實並非找出兩個頂點間全部的路徑長度,而後取最小值。而是藉助於廣度優先遍歷算法,將每一個頂點相對於初始頂點的最短路徑長度保存在 cost 屬性中,廣度優先算法的性質保證了頂點間的路徑是最短的。

圖的實現策略

  • 鄰接矩陣:

  • 對於圖的構造咱們有三種方法,第一種 鄰接矩陣,第二種 鄰接表,第三種 十字鏈表。在這裏咱們深度解析 鄰接矩陣與鄰接表 的構造方法!首先咱們闡述第一種方法: 鄰接矩陣 (鄰接矩陣用於相對來講比較稠密的無向圖)例如此無向圖:

相對應的鄰接矩陣表示以下:

#include<iostream>
#include<string>
#define maxSize 10
using namespace  std;
//在此聲明 不用template 模板
class Graph{
public:
Graph(string str[],int vertex,int arc1);
~Graph(){
//因爲沒有動態申請內存 沒法對改內存進行釋放(在此處作聲明)
cout<<"析構函數調用成功!";
};
void DFS(int v); //深度優先遍歷
void FdgDFS(int v); //非遞歸的深度優先遍歷
void BFS(int v); //廣度優先遍歷 
private:
int verNum;
int arcNum;
string verName[maxSize];
//對於鄰接矩陣構造無向圖確定少不了鄰接矩陣 即二維數組
int arc[maxSize][maxSize];
//遍歷時,對於沒有訪問過的頂點,須要創建flag 標識
int visited[maxSize]={0};
};
Graph::Graph(string str[],int vertex,int arc1){
 verNum=vertex;
//頂點的賦值
for(int i=0;i<verNum;i++){
verName[i]=str[i];
}
for(int i=0;i<verNum;i++)  //初始化鄰接矩陣
  for(int j=0;j<verNum;j++)
    arc[i][j]=arc[j][i]=0;
//對邊進行構造 輸入依附於邊的鄰接點的下標
arcNum=arc1;
for(int k=0;k<arcNum;k++){
  int i=0;
  int j=0;
  cout<<"請輸入依附於邊的鄰接點下標 "<<endl;
  cin>>i>>j;
  //無向圖的鄰接矩陣是對稱的 
  arc[i][j]=arc[j][i]=1; 
 }
}
//遞歸的深度遍歷
void Graph::DFS(int v){
 cout<<verName[v];
 visited[v]=1;
 for(int j=0;j<verNum;j++){
 if(arc[v][j]==1 && visited[j]==0)
  DFS(j);
  }
}
//非遞歸的深度優先遍歷
void Graph::FdgDFS(int v){
 int Stack[maxSize];
 int top=-1;
 cout<<verName[v];
 visited[v]=1;
 Stack[++top]=v;
 while(top!=-1){
 v=Stack[top];
 for(int j=0;j<verNum;j++){
  if(arc[v][j]==1 && visited[j]==0){
  cout<<verName[j];
  visited[j]=1;
 Stack[++top]=j;
 break;
 } 
 if(j==verNum-1)
 top--;
  } 
 }
}
//廣度優先遍歷
void Graph::BFS(int v){
 //定義隊列進行遍歷
 int Queue[maxSize];
 int front=-1;
 int rear=-1;
 cout<<verName[v];
 visited[v]=1;
 Queue[++rear]=v;
 while(front!=rear){
 //出隊
 v=Queue[++front];
 for(int j=0;j<verNum;j++)
 if(arc[v][j]==1 && visited[j]==0){
 cout<<verName[j];
 visited[j]=1;
 //入隊
 Queue[++rear]=j;
    }
  }
}
int main(){ 
 string mystr[4]={"v0","v1","v2","v3"};
 Graph myGraph(mystr,4,4);
 //深度優先遍歷
 myGraph.DFS(2);
 //非遞歸深度優先遍歷
 myGraph.FdgDFS(2);
 //廣度優先遍歷
 myGraph.BFS(2);
 return 0;
}

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

  • 問題1:問題是有向圖和無向圖的廣度遍歷的區別
  • 問題1解決方案:根據我查的資料顯示是不同的,可是再向圖中從出發的頂點沒有路徑的頂點也要寫出來,這不是找全部的路徑而是遍歷因此不能漏掉任何一個元素。

我看到一個大佬的博客解決了這個問題,並且我閱讀以後也有了比較深入的理解
大佬的博客————圖的深度優先遍歷和廣度優先遍歷理解

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

  • 問題1:先是在用鄰接矩陣實現無向圖的補全,前面的都一切OK,該輸出的基本上都輸出了,可是在最後出現了一個空指針問題

  • 問題1解決方案:以後發現,是當時寫程序的時候不夠嚴謹,結果這個變量爲空的,可是我當時沒有去注意就直接使用,就出現NullPointException。
  • 問題2:在實現pp15.1的時候,出現了一個數組越界異常,這就沒道理了啊

  • 問題2解決方案:不斷往上追溯,最後發現好像是以前的一個類裏面的遺留問題,當時沒注意到,這個類的在這裏被使用就出問題了

代碼託管

上週考試錯題總結

這兩道題改了選項,我當時沒注意到羣裏面的,好氣哦

結對及互評

  • 20172324曾程
  • 博客中值得學習的或問題:
    • 書上代碼的學習很到位
    • 代碼調試環節比較詳細,從截圖上來看註釋也很規範整齊,值得學習。
  • 基於評分標準,我給本博客打分:11分。得分狀況以下:
    • 正確使用Markdown語法(加1分)
    • 模板中的要素齊全(加1分)
    • 教材學習中的問題和解決過程, 加4分
    • 代碼調試中的問題和解決過程, 加4分
    • 本週有效代碼超過300分行,加2分
    • 進度條中記錄學習時間與改進狀況的加1分

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積)
目標 5000行 30篇 400小時
第一週 0/0 1/1 8/8
第二週 671/671 1/2 17/25
第三週 345/1016 1/3 15/40
第四周 405/1421 2/5 23/63
第五週 1202/2623 1/5 20/83
第六週 1741/4364 1/6 20/103
第七週 400/4764 1/7 20/123
第八週 521/5285 2/9 24/147
第九周 1622/6907 2/11 17/164

參考資料

相關文章
相關標籤/搜索