數據結構 - 圖

概念

圖的定義

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

需要注意:

  1. 圖中數據元素叫做頂點(Vertext)。

  2. 在圖中,不允許沒有頂點。若 V 是圖的頂點的集合,那麼,V 是非空
    有窮集合。

  3. 圖的任意兩個頂點之間都可能有關係,它們的關係用邊來表示。邊集可
    以是空的。

其他概念

無向邊

若頂點 $V_i$ 到 $V_j$ 之間的邊沒有方向,這條邊就叫做無向邊(Edge),
用無序偶對 ($V_iV_j$) 來表示。

無向圖

如果圖中任意兩個頂點之間的邊都是無向邊,則稱該圖爲無項圖(Undirected graphs)。

有向邊

若從頂點 $V_i$ 到 $V_j$ 的邊有方向,則稱這條邊爲有向邊,也稱爲弧 (Arc)。

這條有向邊用有序偶 $<V_i,V_j>來表示,$V_j是弧尾(Tail),$V_j$是弧頭(Head)

有向圖

如果圖中任意兩個頂點之間的邊都是有向邊,這個圖就是有向圖。

無向邊用小括號 “()”表示,有向邊用尖括號“<>”表示。

簡單圖

在圖中,若不存在頂點到其自身的邊,且同一條邊不重複出現,這樣的圖就是簡單圖。

數據結構_圖_簡單圖

無向完全圖

在無向圖中,如何任意兩點之間都存在邊,這個圖就是無向完全圖。

n 個頂點的無向完全圖有 ${n(n-1)} \over 2$ 條邊。

有向完全圖

在有向圖中,如果任意兩點之間都存在方向互爲相反的兩條弧,這個圖就是有向完全圖。

n個頂點的有向完全圖有 $n(n-1) \over 2$ 條邊。

稠密圖和稀疏圖

邊或弧很少的圖是稀疏圖;邊或弧很多的圖是稠密圖。

它們都是相對概念。

權、網

與圖的邊或弧相關的數字叫做權(Weight)。

權可以表示一個頂點到另一個頂點的距離或耗費。

帶權的圖叫做網(Network)。

子圖

假設有兩個圖 $G = (V,\lbrace E \rbrace)$,$G' = (V',\lbrace E' \rbrace)$,$V' \subseteq V$,$E' \subseteq E$,那麼,
G'G 的子圖。

圖的頂點與邊間的關係(暫時不做筆記,有空再補充)

概念太多,記錄太麻煩,暫時不做筆記,有空再補充。

連通圖

連通圖

圖7-2-13圖2根據定義,我認爲它不是強連通圖。

數據結構_圖_連通圖

數據結構_圖_連通圖_2

數據結構_圖_連通圖_3

數據結構_圖_連通圖_4

生成樹

不理解。

數據結構_圖_生成樹_1

數據結構_圖_生成樹_2

有向樹

不理解。

數據結構_圖_有向樹_1

數據結構_圖_有向樹_2

圖的抽象數據類型

ADT 圖(Graph)

Data
    頂點的有窮非空集合和邊的集合
    
Operation
    CreateGraph(*G, V, VR):按照頂點集 V 和 邊弧集 VR 的定義構造圖 G。
    DestroyGraph(*G):圖G存在則銷燬它。
    LocateVex(G, u):若圖 G 中存在頂點 u,則返回它在圖中的位置。
    GetVex(G, v):返回圖 G 中頂點 v 的值。
    PutVex(G, v, value):將圖 G 中頂點 v 賦值 value。
    FirstAdjVex(G, *v):返回頂點 v 的一個鄰接頂點,若頂點在 G 中無鄰接頂點則返回空。
    NextAdjVex(G, v, *w):返回頂點 v 相對於頂點 w 的下一個鄰接頂點,若 w 是 v 的最後
                          一個鄰接點則返回“空”。
    InsertVex(*G, v):在圖 G 中增添新頂點 v。
    DeleteVex(*G, v): 刪除圖  G 中頂點 v 及其相關的弧。
    InsertArc(*G, v, w):在圖中增添弧 <v,w>,若 G 是無向圖,還要增加對稱弧 <w,v>。
    DeleteArc(*G, v, w):在圖中刪除弧 <v,w>,若 G 是無向圖,還需要刪除對稱弧 <w,v>。
    DFSTraverse(G):對圖 G 深度優先遍歷,在遍歷過程中對每個頂點調用。
    HFSTraverse(G):對圖 G 廣度優先遍歷,在遍歷過程中對每個頂點調用。
endADT

圖的存儲結構(難理解)

鄰接矩陣

定義

圖的鄰接矩陣(Adjacency Matrix)存儲方式是用兩個數組表示圖。一個一維數組存儲圖中頂點信息,一個
二維數組(稱爲鄰接矩陣)存儲圖中的邊或弧信息。

數據結構_圖_鄰接矩陣_定義

無向圖實例

數據結構_圖_鄰接矩陣_解釋

有向圖實例

數據結構_圖_鄰接矩陣_有向圖

網圖

數據結構_圖_鄰接矩陣_網圖定義

數據結構_圖_鄰接矩陣_網圖

鄰接矩陣存儲結構

鄰接矩陣存儲結構代碼:

備註:用65536代替 $\infty$

typedef char VertexType;
typedef int EdgeType;
#define MAXVEX 100;     // 最大頂點數
#define INFINITY 65535;  
typedef struct
{
    VertexType vexs[MAXVEX];    // 頂點表
    EdgeType arc[MAXVEX][MAXVEX];   // 鄰接矩陣,可看作邊表
    int numVertexes, numEdges;  // 圖中當前頂點數和邊數
}MGraph;

創建無向網圖代碼:

void CreateGraph(MGraph *G)
{
    int i, j, k, w;
    printf("%s", "輸入頂點數和邊數:\n");
    scanf("%d, %d", &G->numVertexes, &G->numEdges);     // 輸入頂點數和邊數
    for(i = 0; i < G->numVertexes; i++){
        scanf(&G->vexs[i]);
    }
    for(i = 0; i < G->numVertexes; i++){
        for(j = 0; j < G->numVertexes; j++){
            G->arc[i][j] = INFINITY;    // 鄰接矩陣初始化
        }
    }
    for(k = 0; k < G->numEdges; k++){   // 讀入numEdges條邊,建立鄰接矩陣
        printf("輸入邊(vi,vj)上的下標i、下標j和權w:\n");
        scanf("%d, %d, %d", &i, &j, &w);    // 輸入邊(vi,vj)上的權w
        G->arc[i][j] = w;
        G->arc[j][i] = G->arc[i][j];    // 因爲是無向圖,矩陣對稱
    }
}

鄰接表

概念

定義

數組與鏈表相結合的方法稱爲鄰接表(Adjacency List)。

思路

  1. 圖中頂點用一個一維數組存儲。該數組存儲兩部分內容:頂點信息和指針。
    該指針指向該頂點的第一個鄰接點。

  2. 圖中的每個頂點 $v_i$ 的所有鄰接點構成一個線性表,用單鏈表存儲。
    這個線性表可能叫 邊表,也可能叫 出邊表

當圖爲無向圖的時候,這個線性表叫做頂點 $v_i$ 的邊表。

當圖爲有向圖的時候,這個線性表叫做頂點 $v_i$ 作爲弧尾的出邊表。

摘抄書本

無向圖的鄰接表結構

數據結構_圖_鄰接表_無向圖的鄰接表結構

$v_0$ 的鄰接點是 $v_1,v_2,v_3$,$v_0$的 firstEdge 存儲的指針指向它的第一個鄰接點 $v_1$。
$v_1$ 的 adjvex 存儲的是 該頂點在頂點表中的下標,next 存儲的指針指向 $v_0$ 的第二個鄰接點 $v_2$。

有向圖的鄰接表結構

數據結構_圖_鄰接表_有向圖的鄰接表結構_1

數據結構_圖_鄰接表_有向圖的鄰接表結構_2

網圖的鄰接表結構

數據結構_圖_鄰接表_網圖

鄰接表存儲結構(看不懂)

結點定義代碼

typedef char VertexType;
typedef int EdgeType;

typedef struct EdgeNode     // 邊表結點
{
    int adjvex;     // 鄰接點域,存儲該頂點對應的下標
    EdgeType weight;    // 權值
    struct EdgeNode *next;  // 鏈域,指向下一個連接點
}EdgeNode;

typedef struct VertexNode   // 頂點表結點
{
    VertexType data;    // 頂點域,存儲頂點信息
    EdgeNode *firstEdge;    // 邊表頭指針
}VertexNode, AdjList[MAXVEX];

typedef struct
{
    AdjList adjList;
    int numVertexes, numEdges;      // 圖中當前頂點數和邊數
}GraphAdjList;

邊表結點 EdgeNode 爲什麼如此定義?

無向圖的鄰接表創建代碼

void CreateALGraph(GraphAdjList *G)
{
    int i, j, k;
    EdgeNode *e;
    printf("輸入頂點數和邊數:\n");
    scanf("%d, %d", &G->numVertexes, &G->numEdges);     // 輸入頂點數和邊數 
    
    for(i = 0; i < G->numVertexes; i++){
        scanf(&G->adjList[i].data);     // 輸入頂點信息。這種寫法,有問題嗎?
        G->adjList[i].firstEdge = NULL;     // 將邊表置爲空表。爲什麼?
    }
    
    for(k = 0; k < G->numVertexes; i++){    // 建立邊表
        printf("輸入邊(Vi,Vj)上的頂點序號:\n");
        scanf("%d, %d", &i, &j);    // 輸入邊(Vi,Vj)上的頂點序號
        e = (EdgeNode *)malloc(sizeof(EdgeNode));       // 申請內存空間,創建邊表結點
        e->adjvex = j;      // 鄰接序號爲j
        e->next = G->adjList[i].firstEdge;  // 將e指針指向當前頂點指向的結點 ?
        G->adjList[i].firstEdge = e;    // 將當前頂點的指針指向e
        e = (EdgeNode *)malloc(sizeof(EdgeNode));   // 申請內存空間,創建邊表結點
        e->adjvex = i;  // 鄰接序號爲i
        e->next = G->adjList[j].firstEdge;  // 將e指針指向當前頂點指向的結點
        G->adjList[j].firstEdge = e;    // 將當前頂點的指針指向e
    }
}

十字鏈表(看不懂)

摘抄

數據結構_圖_十字鏈表_1

 

數據結構_圖_十字鏈表_2

 

數據結構_圖_十字鏈表_3

 

數據結構_圖_十字鏈表_4

鄰接多重表(看不懂)

摘抄

數據結構_圖_鄰接多重表_1

 

數據結構_圖_鄰接多重表_2

 

數據結構_圖_鄰接多重表_3

 

數據結構_圖_鄰接多重表_4

 

數據結構_圖_鄰接多重表_5

邊集數組

摘抄

數據結構_圖_邊集數組_1

 

數據結構_圖_邊集數組_2

圖的遍歷

定義

從圖中某一個頂點出發遍訪圖的其餘所有頂點,且使所有頂點被訪問且只訪問一次。
這個過程,就叫做圖的遍歷(Traversing Graph)。

深度優先遍歷(Depth_First_Search)

摘抄

數據結構_圖_深度優先遍歷_摘抄_1

虛線表示已經遍歷過的點,實線表示未遍歷過的點。

數據結構_圖_深度優先遍歷_摘抄_2

 

數據結構_圖_深度優先遍歷_摘抄_3

代碼

鄰接矩陣代碼

typedef int Boolean;    // Boolean是布爾類型,其值是TRUE或FALSE
Boolean visited[MAX];   // 訪問標誌的數組

void DFS(MGraph G, int i)
{
    int j;
    visited[i] = TRUE;
    printf("%c", G.vexs[i]);
    
    /*2*/for(j = 0; j < G.numVertexes; j++){
        /*1*/if(G.arc[i][j] == 1 && !visited[j] ){
            DFS(G, j);  // 對未訪問的頂點遞歸調用
        }
    }
}

void DFSTraverse(MGraph G)
{
    int i;
    
    for(i = 0; i < G.numVertexes; i++){
        visited[i] = FALSE;     // 初始所有頂點狀態都是未訪問過狀態
    }
    
    for(i = 0; i < G.numVertexes; i++){
        if(!visited[i]){
            DFS(G, i);
        }
    }
}

第1行,G.arc[i][j] == 1 表明 $v_iv_j$ 邊存在,這是根據鄰接圖矩陣定義得出的
結果。

第1行,visited[j] 不能理解這句。

第2行的循環裏面的遞歸,什麼時候終止?

鄰接表代碼(不懂)

void DFS(GraphAdjList GL, int i)
{
    EdgeNode *p;
    visited[i] = TRUE;
    printf("%c", GL->adjList[i].data);
    p = GL->adjList[i].firstEdge;
    while(p){
        if(!visited[p->adjvex]){
            DFS(GL, p->adjvex);
        }
        p = p->next;
    }
}

void DFSTraverse(GraphAdjList GL)
{
    int i;
    for(i = 0; i < GL->numVertexes; i++){
        visited[i] = FALSE;
    }
    for(i = 0; i < GL->numVertexes; i++){
        if(!visited[i]){
            DFS(GL, i);
        }
    }
}

廣度優先遍歷(Breadth_First_Search)

摘抄

數據結構_圖_廣度優先遍歷_摘抄_1

數據結構_圖_廣度優先遍歷_摘抄_2

不明白如何得到這張圖。

代碼

鄰接矩陣代碼(不懂)

void BFSTraverse(MGraph G)
{
    int i, j;
    Queue Q;
    for(i = 0; i < G.numVertexes; i++){
        visited[i] = FALSE;
    }
    InitQueue(&Q);  // 初始化一個輔助用的隊列
    for(i = 0; i < G.numVertexes; i++){     // 對每一個頂點做循環
        if(!visited[i]){
            visited[i] = TRUE;
            printf("%c", G.vexs[i]);
            EnQueue(&Q, i);     // 將此頂點入隊列
            while(!QueueEmpty(Q)){
                DeQueue(&Q, &i);    // 將隊列中元素出隊列,賦值給i
                for(j = 0; j < G.numVertexes; j++){
                    if(G.arc[i][j] == 1 && !visited[j]){
                        visited[j] = TRUE;
                        printf("%c", G.vexs[j]);
                        EnQueue(&Q, j);
                    }
                }
            }
        }
    }
}

鄰接表代碼(不懂)

void BFSTraverse(GraphAdjList GL)
{
    int i;
    EdgeNode *p;
    Queue Q;
    for(i = 0; i < GL->numVertexes; i++){
        visited[i] = FALSE;
    }
    InitQueue(&Q);
    for(i = 0; i < GL->numVertexes; i++){
        if(!visited[i]){
            visited[i] = TRUE;
            printf("%c", GL->adjList[i].data);
            EnQueue(&Q, i);
            while(!QueueEmpty(Q)){
                DeQueue(&Q, &i);
                p = GL->adjList[i].firstEdge;   // 找到當前頂點邊錶鏈表頭指針
                while(p){
                    if(!visited[p->adjvex]){
                        visited[p->adjvex] = TRUE;
                        printf("%c", GL->adjList[p->adjvex].data);
                        EnQueue(&Q, p->adjvex);
                    }
                }
                p = p->next;    // 指針指向下一個鄰接點
            }
        }
    }
}

最小生成樹

定義(不懂)

把構造連通網的最小代價生成樹稱爲最小生成樹(Minimum Cost Spanning Tree)。

普里姆(Prim)算法(不懂)

代碼

void MiniSpanTree_Prim(MGraph G)
{
    int min, i, j, k;
    int adjvex[MAXVEX];     // 保存相關頂點下標
    int lowcost[MAXVEX];    // 保存相關頂點間邊的權值
    // 初始化第一個權值爲0,即v0加入生成樹
    // lowcost的值爲0,在這裏就是此下標的頂點已經加入生成樹
    lowcost[0] = 0;     
    adjvex[0] = 0;  // 初始化第一個頂點下標爲0
    for(i = 1; i < G.numVertexes; i++){
        lowcost[i] = G.arc[0][i];   // 將v0頂點與之有邊的權值存入數組
        adjvex[i] = 0;  // 初始化都爲v0的下標
    }
    for(i = 1; i < G.numVertexes; i++){
        min = INFINITY; //  初始化最小權值爲 INFINITY
        j = 1; k = 0;
        while(j < G.numVertexes){
            if(lowcost[j] != 0 && lowcost[j] < min){
                min = lowcost[j];   // 讓當前權值成爲最小值
                k = j;      // 將當前最小值的下標存入k
            }
            j++;
        }
        printf("(%d, %d)", adjvex[k], k);   //打印當前頂點邊中權值最小的邊
        lowcost[k] = 0;     // 將當前頂點的權值設置爲0,表示此頂點已經完成任務
        for(j = 1; j < G.numVertexes; j++){
            if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j]){
                lowcost[j] = G.arc[k][j];   // 將較小權值存入lowcost
                adjvex[j] = k;      // 將下標爲k的頂點存入adjvex
            }
        }
    }
}

代碼解釋

數據結構_圖_普里姆算法_代碼解釋_8

 

數據結構_圖_普里姆算法_代碼解釋_1

 

數據結構_圖_普里姆算法_代碼解釋_2

 

數據結構_圖_普里姆算法_代碼解釋_3

 

數據結構_圖_普里姆算法_代碼解釋_4

 

數據結構_圖_普里姆算法_代碼解釋_5

 

數據結構_圖_普里姆算法_代碼解釋_6

 

數據結構_圖_普里姆算法_代碼解釋_7

克魯斯卡爾(Kruskal)算法

代碼

typedef struct 
{
    int begin;
    int end;
    int weight;
}Edge;

void MiniSpanTree_Kruskal(MGraph G)
{
    int i, n, m;
    Edge edges[MAXEDGE];    // 定義邊集數組
    int parent[MAXVEX];     // 定義一數組用來判斷邊與邊是否形成環路
    for(i = 0; i < G.numVertexes; i++){
        parent[i] = 0;      // 初始化數組值爲0
    }
    for(i = 0; i < G.numVertexes; i++){
        n = Find(parent, edges[i].begin);
        m = Find(parent, edges[i].end);
        if(n != m){     // 假如n與m不等,說明此邊沒有與現有生成樹形成環路
            // 將此邊的結尾頂點放入下標爲起點的parent中
            // 表示此頂點已經在生成樹集合中
            parent[n] = m;      
            printf("(%d, %d) %d", edges[i].begin, edges[i].end, edges[i].weight);
        }
    }
}

int Find(int *parent, int f)    // 查找連線頂點的尾部下標
{
    while(parent[f] > 0){
        f = parent[f];
    }
    
    return f;
}

代碼解釋

數據結構_圖_克魯斯卡爾算法_代碼解釋_1

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_2

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_3

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_4

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_5

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_6

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_7

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_8

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_9

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_10

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_11

 

數據結構_圖_克魯斯卡爾算法_代碼解釋_12

最短路徑(看不懂)

暫時放棄

定義

非網圖的最短路徑,是指兩頂點之間經過的邊數最少的路徑。

網圖的最短路徑,是指兩頂點之間經過的邊上的權值之和最小的路徑。
路徑上的第一個源點,最後一個頂點是終點。

拓撲排序

關鍵路徑



作者:剛剛悟道
鏈接:https://www.jianshu.com/p/6cace353141d
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章