1. 程式人生 > >『嗨威說』資料結構 - 第六章學習內容小結

『嗨威說』資料結構 - 第六章學習內容小結

 

 本文主要內容:(與樹類似)

  一、圖的概念

  二、圖的重中之重——兩種重要儲存結構

  三、樹的升級拓展應用:最小生成樹

  四、本節應用習題

  五、個人反思與未來計劃

 

一、圖的基本概念:

  (1)圖的定義:

    圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:G(V,E),其中,G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。

    注意:線性表中可以沒有元素,稱為空表。樹中可以沒有結點,叫做空樹。但是在圖中不允許沒有頂點,可以沒有邊。

  (2)圖的基本術語:

  · 無向邊:若頂點Vi和Vj之間的邊沒有方向,稱這條邊為無向邊,用 (Vi,Vj)來表示。

  · 無向圖:圖中任意兩個頂點的邊都是無向邊。

  · 有向邊:若從頂點Vi到Vj的邊有方向,稱這條邊為有向邊,也稱為弧,用 <Vi, Vj> 來表示,其中 Vi 稱為弧尾,Vj 稱為弧頭。

  · 有向圖:圖中任意兩個頂點的邊都是有向邊。

  · 簡單圖:不存在自環(頂點到其自身的邊)和重邊(完全相同的邊)的圖。

  · 稀疏圖;有很少條邊或弧的圖稱為稀疏圖,反之稱為稠密圖。

  · 權:表示從圖中一個頂點到另一個頂點的距離或耗費。

   · 網:帶有權重的圖。

  ·  度:與特定頂點相連線的邊數。

  · 出度、入度:有向圖中的概念,出度表示以此頂點為起點的邊的數目,入度表示以此頂點為終點的邊的數目。

  · 連通圖:任意兩個頂點都相互連通的圖。

  · 極大連通子圖:包含竟可能多的頂點(必須是連通的),即找不到另外一個頂點,使得此頂點能夠連線到此極大連通子圖的任意一個頂點。

  · 連通分量:極大連通子圖的數量。

  · 強連通圖:此為有向圖的概念,表示任意兩個頂點a,b,使得a能夠連線到b,b也能連線到a 的圖。

  · 連通圖的生成樹:一個極小連通子圖,它含有原圖中全部定點,但只有足以構成一棵樹的 n-1 條變,這樣的連通子圖稱為連通圖的生成樹。

  · 最小生成樹:此生成樹的邊的權重之和是所有生成樹中最小的。

  (3)圖的兩種遍歷方式:

      · 深度優先遍歷:(DFS常利用遞迴思想)

        首先從圖中某個頂點v0出發,訪問此頂點,然後依次從v相鄰的頂點出發深度優先遍歷,直至圖中所有與v路徑相通的頂點都被訪問了;若此時尚有頂點未被訪問,則從中選一個頂點作為起始點,重複上述過程,直到所有的頂點都被訪問。

 

      · 廣度優先遍歷:(BFS常利用佇列+佇列不為空迴圈思想)

        首先,從圖的某個頂點v0出發,訪問了v0之後,依次訪問與v0相鄰的未被訪問的頂點,然後分別從這些頂點出發,廣度優先遍歷,直至所有的頂點都被訪問完。

 

二、圖的重中之重 —— 已學習的兩種儲存結構:

  (1)鄰接矩陣:

    圖的鄰接矩陣的儲存方式是用兩個陣列來表示圖。一個一維陣列儲存圖中頂點資訊,一個二維陣列(稱鄰接矩陣)儲存圖中的邊或弧的資訊。

    優缺點:

      · 優點:結構簡單,操作方便

      · 缺點:對於稀疏圖,這種實現方式將浪費大量的空間。

  (2)鄰接表:

    鄰接表是一種將陣列與連結串列相結合的儲存方法。其具體實現為:將圖中頂點用一個一維陣列儲存,每個頂點Vi的所有鄰接點用一個單鏈表來儲存。這種方式和樹結構中孩子表示法一樣。

    對於有向圖其鄰接表結構如下:

    優缺點:

      · 優點:本演算法的時間複雜度為 O(N + E),其中N、E分別為頂點數和邊數,鄰接表實現比較適合表示稀疏圖。

      · 缺點:操作繁瑣

    注:還有一種十字連結串列儲存結構,暫未教學,等學習熟練之後再單獨拿來寫博

 

三、圖的升級拓展之一:最小生成樹 (貪心演算法):

  (1)最小生成樹的概念:

      圖的生成樹是它的一棵含有所有頂點的無環連通子圖。一棵加權圖的最小生成樹(MST)是它的一棵權值(所有邊的權值之和)最小的生成樹。

  (2)最小生成樹的兩種實現演算法:

      · 普里姆演算法(Prim)

        實現過程:

        從頂點0開始,首先將頂點0加入到樹中(標記),頂點0和其它點的橫切邊(這裡即為頂點0的鄰接邊)加入優先佇列,將權值最小的橫切邊出隊,加入生成樹中。此時相當於也向樹中添加了一個頂點2,接著將集合(頂點1,2組成)和另一個集合(除1,2的頂點組成)間的橫切邊加入到優先佇列中,如此這般,直到佇列為空。

      

      · 克魯斯卡爾演算法(Kruskal)

        實現過程:

        按照邊的權重順序來生成最小生成樹,首先將圖中所有邊加入優先佇列,將權重最小的邊出隊加入最小生成樹,保證加入的邊不與已經加入的邊形成環,直到樹中有V-1到邊為止。

        注:具體實現程式碼暫未學習,僅瞭解實現過程,後續增加。

 

四、本章習題練習:

    拯救007:(DFS)

在老電影“007之生死關頭”(Live and Let Die)中有一個情節,007被毒販抓到一個鱷魚池中心的小島上,他用了一種極為大膽的方法逃脫 —— 直接踩著池子裡一系列鱷魚的大腦袋跳上岸去!(據說當年替身演員被最後一條鱷魚咬住了腳,幸好穿的是特別加厚的靴子才逃過一劫。)

設鱷魚池是長寬為100米的方形,中心座標為 (0, 0),且東北角座標為 (50, 50)。池心島是以 (0, 0) 為圓心、直徑15米的圓。給定池中分佈的鱷魚的座標、以及007一次能跳躍的最大距離,你需要告訴他是否有可能逃出生天。

輸入格式:
首先第一行給出兩個正整數:鱷魚數量 N(≤100)和007一次能跳躍的最大距離 D。隨後 N 行,每行給出一條鱷魚的 (x,y) 座標。注意:不會有兩條鱷魚待在同一個點上。

輸出格式:
如果007有可能逃脫,就在一行中輸出"Yes",否則輸出"No"。

輸入樣例 1:
14 20
25 -15
-25 28
8 49
29 15
-35 -2
5 28
27 -29
-8 -28
-20 -35
-25 -20
-13 29
-30 15
-35 40
12 12
輸出樣例 1:
Yes
輸入樣例 2:
4 13
-12 12
12 12
-12 -12
12 -12
輸出樣例 2:
No
【題目】拯救007

    題目有一點點小坑,剛開始還過了五個測試點,只差一個測試點,但還好一兩小時肝一下debug出來了,下面簡單說一下題意:

有一個人在一個圓內,半徑為7.5(這是個坑,不是15噢)單位,然後可以從圓內往外跳,但只有固定的幾個點可以跳,而且能不能跳過去看這個人的最大跳躍距離。只要能跳出100*100的大矩形則說明可以逃生輸出Yes,否則No

    題目大意就是這樣啦,簡單擬定一下思路:

1、對於每一個點,用一個struct去實現其儲存結構,儲存其x、y座標、能夠跳到的點的編號、能夠跳到點的數目、能否成為起跳點、能否成為終止點。

2、輸入完x、y座標點之後,掃一遍全點,連結能跳的點,並且判斷能否成為起跳點,能否成為終止點。

3、特判一下當最大可跳距離大於42.5時,可以不用跳到鱷魚,可以直接跳出矩形。

4、實現DFS深搜遞迴,打上vis陣列。

    現在我們來動手試一下吧~ 

 

    首先,標頭檔案,因為是ACMer選手,習慣了C語言的寫法,各位小夥伴不必在意噢,只需要把scanf輸入的東西換成cin,printf輸出的東西換成cout就搞定了

#include<stdio.h>
#include<math.h>
#include<string.h>
#define MAX 999

    

    第二步,開始建立圖結點的結構體:按上面我思路說的,儲存其x、y座標、能夠跳到的點的編號、能夠跳到點的數目、能否成為起跳點、能否成為終止點。

typedef struct ArcNode{
    int x;
    int y;
    int num;
    int Next[MAX];
    bool out = false;
    bool in = false;
}ArcNode;

    

    第三步,基本變數申明以及函式原型申明:

int N,maxJump,ans,vis[MAX];
void DFS(ArcNode *p,int i);
void buildGraph(ArcNode *&p);
void buildArc(ArcNode *&p);
void searchInOut(ArcNode *&p);

    

    第四步,對Main主函式進行模組化函式構建:

int main()
{
    ArcNode *G;        //建立結點
    buildGraph(G);      //匯入結點內容
    if(maxJump >= 42.5)   //特判
    {
        printf("Yes\n");
        return 0;
    }
    buildArc(G);        //掃一遍所有點,構造邊,使能互相跳的點結合起來
    searchInOut(G);      //掃一遍所有點,判斷是否能夠成為起跳點和終止點
    ans = -1;          //答案初始化
    for(int i = 1;i<=N;i++)
        if(G[i].in)      //如果這個點是起跳點
        {
            memset(vis,0,sizeof(vis));  //重置vis陣列
            DFS(G,i);            //開始深搜
        }    
    if(ans == -1) printf("No\n");    //列印答案
    else printf("Yes\n");
    return 0;
}

 

    第五步,匯入結點,比較簡單:一個輸入匯入就可以了。

void buildGraph(ArcNode *&p)
{
    scanf("%d %d",&N,&maxJump);
    p = new ArcNode[N+1];
    for(int i = 1;i<=N;i++)
        scanf("%d %d",&p[i].x,&p[i].y);
}

 

    第六步,連結各點,實現儲存可以互跳的點,這裡需要兩層for迴圈的遍歷,可能效率優點不高,但簡單粗暴

void buildArc(ArcNode *&p)
{
    for(int i = 1;i<=N;i++)
    {
        for(int u = 1;u<=N;u++)
        {
            if(u == i) continue;
            if((maxJump * maxJump) >= (p[i].x-p[u].x)*(p[i].x-p[u].x)+(p[i].y-p[u].y)*(p[i].y-p[u].y)) //這裡除了用sqrt還可以直接用平方比較,誤差會小一點
            {
                p[i].num++;
                p[i].Next[p[i].num] = u; //將可互跳的邊匯入
            }
        }
    }
}

 

    第七步,搜尋起始點和終止點,也是比較簡單的,一個距離公式就好了。

void searchInOut(ArcNode *&p)
{
    for(int i = 1;i<=N;i++)
    {
        if((maxJump + 7.5)*(maxJump + 7.5) >= p[i].x*p[i].x+p[i].y*p[i].y)
            p[i].in = true;
        if(maxJump >= 50 - abs(p[i].x) || maxJump >= 50 - abs(p[i].y))
            p[i].out = true;
    }
}

 

    第八步,巢狀一下DFS遞迴就好了,注意遞迴的終止條件。

void DFS(ArcNode *p,int i)
{
    vis[i] = 1;
    if(p[i].out == true) //如果搜到了這個點是終止點,說明這個人可以跳出去,那麼就給答案變數做個標記。
        ans = 1;
    if(p[i].num == 0)  //遞迴終止條件
        return;
    for(int u = 1; u<=p[i].num; u++)
        if(!vis[p[i].Next[u]])//防止遞迴迴圈需要一個標記陣列
            DFS(p,p[i].Next[u]);
}

    

    這樣整個程式就完成啦~ 完整程式碼貼上:

 1 #include<stdio.h>
 2 #include<math.h>
 3 #include<string.h>
 4 #define MAX 999
 5 
 6 typedef struct ArcNode{
 7     int x;
 8     int y;
 9     int num;
10     int Next[MAX];
11     bool out = false;
12     bool in = false;
13 }ArcNode;
14 
15 int N,maxJump,ans,vis[MAX];
16 void DFS(ArcNode *p,int i);
17 void buildGraph(ArcNode *&p);
18 void buildArc(ArcNode *&p);
19 void searchInOut(ArcNode *&p);
20 int main()
21 {
22     ArcNode *G;
23     buildGraph(G);
24     if(maxJump >= 42.5)
25     {
26         printf("Yes\n");
27         return 0;
28     }
29     buildArc(G);
30     searchInOut(G);
31     ans = -1;
32     for(int i = 1;i<=N;i++)
33         if(G[i].in)
34         {
35             memset(vis,0,sizeof(vis));
36             DFS(G,i);
37         }    
38     if(ans == -1) printf("No\n");
39     else printf("Yes\n");
40     return 0;
41 }
42 void buildGraph(ArcNode *&p)
43 {
44     scanf("%d %d",&N,&maxJump);
45     p = new ArcNode[N+1];
46     for(int i = 1;i<=N;i++)
47         scanf("%d %d",&p[i].x,&p[i].y);
48 }
49 void buildArc(ArcNode *&p)
50 {
51     for(int i = 1;i<=N;i++)
52     {
53         for(int u = 1;u<=N;u++)
54         {
55             if(u == i) continue;
56             if((maxJump * maxJump) >= (p[i].x-p[u].x)*(p[i].x-p[u].x)+(p[i].y-p[u].y)*(p[i].y-p[u].y))
57             {
58                 p[i].num++;
59                 p[i].Next[p[i].num] = u;
60             }
61         }
62     }
63 }
64 void searchInOut(ArcNode *&p)
65 {
66     for(int i = 1;i<=N;i++)
67     {
68         if((maxJump + 7.5)*(maxJump + 7.5) >= p[i].x*p[i].x+p[i].y*p[i].y)
69             p[i].in = true;
70         if(maxJump >= 50 - abs(p[i].x) || maxJump >= 50 - abs(p[i].y))
71             p[i].out = true;
72     }
73 }
74 void DFS(ArcNode *p,int i)
75 {
76     vis[i] = 1;
77     if(p[i].out == true)
78         ans = 1;
79     if(p[i].num == 0)
80         return;
81     for(int u = 1; u<=p[i].num; u++)
82         if(!vis[p[i].Next[u]])
83             DFS(p,p[i].Next[u]);
84 }
【完整程式碼展示】拯救007 

    

    下面將具體的匯入結點和DFS搜尋過程打印出來,大家可以看看他的步驟流程:

 

五、個人反思及未來計劃:

  有一天老師跟我說了一句話,讓我一直留著比較深的印象:

  

  是啊,從開學到現在一直有不少人告訴我,大家能夠專心幹一件事,你真的就是很棒的人了。

  而我,在上學期加了五個社團,Quanta、Eddy、數挖、ACM、招協,拖著班長,拖著五個兼職,我也不知道我怎麼活下來的,大概是想把自己忙成狗 吧,把一些傷心的事情忘得一乾二淨。

  下學期收了收心,退了幾個社團,僅留ACM和數挖,班長的事情隨著英劇的結束事情也少了不少,兼職也拖剩了一個,慢慢收心,大概上學期各個方面的接觸,也讓我逐步摸清了未來的發展方向。

  就這樣吧,努力計劃做好每天該乾的事情,不負身邊人對我的期望,好好對待每一個人,去努力的帶給他們快樂,帶給自己快樂,帶來更多的動力。

  大二,大概有了方向了,嗯,努力幹下去。

 

  (1)圖比較抽象的資料結構上基礎有些不牢,進一步學習普里姆演算法、克魯斯卡爾演算法、迪傑斯特拉演算法等,特別是碰到鏈式儲存的指標使用的時候,需要找時間給自己多加強這方面的學習。

    (2)ACM集訓隊每天幾道題,每週寫一篇部落格。

  (3)完成論文標解並準備好論文演講『Improving patch-based scene text script identification with ensembles of conjoined networks』

  (4)完成論文標解並準備好論文演講『漢老雙語命名實體識別及對齊方法研究_韓