數據結構--圖的基礎知識


定義

圖(Graph), 又V和E兩個非空集合構成,表示為G = (V,E);

其中,V表示的是圖G中的頂點的又窮非空集合;E表示的是圖G中的兩個頂點之間連接的邊的有窮集合;

V(G),E(G)通常分別表示G的頂點集,邊集;

ps: 一個圖,可以沒有邊,也就是E(G)可以為空,但是V(G)不能

圖的基本術語

無向邊: 若頂點vi到vj之間的邊沒有方向,則稱這條邊為無向邊(Edge),用無序偶對(vi,vj)來表示。

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

有向邊: 若從頂點vi到vj的邊有方向,則稱這條邊為有向邊,也稱為弧(Arc)。用有序偶來表示,vi稱為弧尾(Tail),vj稱為弧頭(Head)。

有向圖: 如果圖中任意兩個頂點之間的邊都是有向邊,則稱該圖為有向圖(Directed graphs)。

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

簡單圖: 在圖中,若不存在頂點到其自身的邊,且同一條邊不重復出現,則稱這樣的圖為簡單圖。

無向完全圖: 在無向圖中,如果任意兩個頂點之間都存在邊,則稱該圖為無向完全圖。

含有n個頂點的無向完全圖有n(n-1)/2條邊。

有向完全圖: 在有向圖中,如果任意兩個頂點之間都存在方向互為相反的兩條弧,則稱該圖為有向完全圖。

含有n個頂點的有向完全圖有n×(n-1)條邊。

對於具有n個頂點和e條邊數的圖,無向圖,有向圖。

稀疏圖與稠密圖: 有很少條邊或弧的圖稱為稀疏圖,反之稱為稠密圖。這里稀疏和稠密是模糊的概念,都是相對而言的。
: 有些圖的邊或弧具有與它相關的數字,這種與圖的邊或弧相關的數叫做權(Weight)。這些權可以表示從一個頂點到另一個頂點的距離或耗費。
: 帶權的圖通常稱為網(Network)。
子圖: 設有兩個圖G=(V,{E})和G'=(V',{E'}),如果V'∈V且E'∈E,則稱G'為G的子圖(Sub-graph)。

鄰接點: 對於無向圖G=(V,{E}),如果邊(v,v')∈E,則稱頂點v和v'互為鄰接點(Adjacent),即v和v'相鄰接。

邊(v,v')依附(incident)於頂點v和v',或者說(v,v')與頂點v和v'相關聯

: 頂點v的度(Degree)是和v相關聯的邊的數目,記為TD(v)。

對於有向圖G=(V,{E}),如果弧∈E,則稱頂點v鄰接到頂點v',頂點v'鄰接自頂點v。弧和頂點v,v'相關聯。以頂點v為
頭弧的數目稱為v的入度(InDegree),記為ID(v);以v為尾的弧的數目稱為v的出度(OutDegree),記為OD(v);頂點v的度為TD(v)=ID(v)+OD(v)。

路徑: 無向圖G=(V,{E})中從頂點v到頂點v'的路徑(Path)是一個頂點序列。如果G是有向圖,則路徑也是有向的。

路徑的長度是路徑上的邊或弧的數目。

回路: 第一個頂點和最后一個頂點相同的路徑稱為回路或環(Cycle)。
簡單路徑: 序列中頂點不重復出現的路徑稱為簡單路徑。
簡單回路: 除了第一個頂點和最后一個頂點之外,其余頂點不重復出現的回路,稱為簡單回路或簡單環。

連通: 在無向圖G中,如果從頂點v到頂點v'有路徑,則稱v和v'是連通的。
連通圖: 如果對於圖中任意兩個頂點vi、vj∈V,vi和vj都是連通的,則稱G是連通圖(Connected Graph)。
連通分量: 無向圖中的極大連通子圖稱為連通分量。連通分量的概念強調:

  • 要是子圖;
  • 子圖要是連通的;
  • 連通子圖含有極大頂點數;
  • 具有極大頂點數的連通子圖包含依附於這些頂點的所有邊。

強連通圖: 在有向圖G中,如果對於每一對vi、vj∈V、vi≠vj,從vi到vj和從vj到vi都存在路徑,則稱G是強連通圖。
強連通分量: 有向圖中的極大強連通子圖稱做有向圖的強連通分量。

連通圖的生成樹: 所謂的一個連通圖的生成樹是一個極小的連通子圖,它含有圖中全部的n個頂點,但只有足以構成一棵樹的n-1條邊。

如果一個圖有n個頂點和小於n-1條邊,則是非連通圖,如果它多於n-1邊條,必定構成一個環,因為這條邊使得它依附的那兩個頂點之間有了第二條路徑。

有向樹: 如果一個有向圖恰有一個頂點的入度為0,其余頂點的入度均為1,則是一個有向樹。

對有向樹的理解比較容易,所謂入度為0其實就相當於樹中的根結點,其余頂點入度為1就是說樹的非根結點的雙親只有一個。

有向圖的生成森林: 一個有向圖的生成森林由若干棵有向樹組成,含有圖中全部頂點,但只有足以構成若干棵不相交的有向樹的弧。

圖的存儲結構

由千圖的結構比較復雜,任意兩個頂點之間都可能存在聯系,因此無法以數據元素在存儲區

中的物理位置來表示元素之間的關系,即圖沒有順序存儲結構,但可以借助二維數組來表示元素

之間的關系,即鄰接矩陣表示法。另一方面,由於圖的任意兩個頂點間都可能存在關系,因此,

用鏈式存儲表示圖是很自然的事,圖的鏈式存儲有多種,有鄰接表、十字鏈表和鄰接多重表,應

根據實際需要的不同選擇不同的存儲結構。

鄰接矩陣表示法

image-20220207130751628

由圖解:頂點之間關聯就對應二維數組上就賦值1,反之就為0;

對於,有邊的位置,賦值其權,其余為 ∞;

使用鄰接矩陣表示法,除了需要用二維數組表示元素之前的關系時,還需要用一個一維數組來表示每個頂點所包含的數據;

// 對於網和普通的圖,主要是多了一個權,所以這里以網結構來展開
const int MVNum = 100; // 最大頂點數
const int MaxInt = 32767; // 最大值, ∞
// 定義一個結構體
typedef struct {
    ElemType vexs[MVNum]; // 頂點表
    int arcs[MVNum][MVNum]; // 鄰接矩陣
    int vexNum, arcNum; // 圖的當前點數和邊數
}AMGraph;

創建一個無向網

// 
Status CreateUDN(AMGraph &G) {
    // 
    cin>>G.vexNum>>G.arcNum; // 輸入中的頂點數,和邊數
    
    for(int i = 0; i < G.vexNum; i++) {
        cin>>G.vexs[i]; // 這里對頂點元素數據賦值
    }
    
    for(int i = 0; i < G.vexNum; i++) {
        for(int j = 0; j < G.vexNum; j++) {
            G.arcs[i][j] = MaxInt; // 對鄰接矩陣初始化
        }
    }
    
    for(int i = 0; i < G.arcNum; i++) {
        int v1,v2; // 頂點
        int w; // 權重
        cin>>v1>>v2>>w; 
        // 獲取v1,v2頂點在鄰接矩陣中的位置
        int _v1 = locateVex(G,v1);
        int _v2 = lovateVex(G,v2);
        G.arcs[_v1][_v2] = w; //設置權重
        G.arcs[_v2][_v1] = G.arcs[_v1][_v2]; // 這是無向網的特別處理
    }
    return OK;
}

鄰接矩陣表示的優劣

(1)優點

  • 便於判斷兩個頂點之間是否有邊, 即根據A[i][j] = 0或1來判斷。

  • 便於計算各個頂點的度。對千無向圖,鄰接矩陣第凶於元素之和就是頂點l的度;對於有向圖,第i行 元素之和就是頂點 i 的出度,第i 列元素之和就是頂點l 的入度。

(2) 缺點

  • 不便千增加和刪除頂點。

  • 不便千統計邊的數目,需要掃描鄰接矩陣所有元素才能統計完畢。

  • 空間復雜度高。如果是有向圖,n個頂點需要n2個單元存儲邊。如果是無向圖,因其鄰接矩陣是對稱的,所以對規模較大的鄰接矩陣可以采用壓縮存儲的方法,僅存儲下三角(或上三角)的元素,這樣需要n(n-1)/2個單元即可。但無論以何種方式存儲,鄰接矩陣表示法的空間復雜度均為0(心,這對千稀疏圖而言尤其浪費空間。

鄰接表表示法

采用鏈式存儲的結構,每個頂點構建一個單鏈表,其中表頭存放頂點信息,其余結點存放相關邊的信息;

image-20220207154617908

  • data, info 為數據域
  • firstarc,nextarc 為鏈域
  • adjvex 鄰接域

表頭結點采用順序存儲的結構存儲,以便可以隨機訪問任一頂點的邊鏈表

// 
const int MaxInt = 100;
// 邊結點
typedef struct ArcNode{
    int adjvex; // 改變所指向的頂點的位置
    struct ArcNode * nextarc; // 指向下一條邊
    int info; // 和邊相關的信息
}ArcNode;

// 頂點信息
typedef struct VNode {
    int data; // 數據域
    ArcNode * firstarc; //	指向第一條依附該頂點的邊的指針
}VNode, AdjList[MVNum];

// 
typedef struct {
    AdjList vertices; //鄰接表
    int vexNum, arcNum; // 圖的當前頂點,邊數
}ALGraph;

采用鄰接表創建一個無向圖

//
status CreateUDG(ALGraph & G) {
    cin>>G.vexNum>>G.arcNum;
    // 對每一個鏈表進行賦值初始化
    for(int i = 0; i<G.vexNum; i++) {
        cin>>G.vertices[i].data; // 輸入頭結點信息
        G.vertices[i].firstarc = NULL;
    }
    for(int i = 0; i < G.arcNum; i++) {
        cin>>v1>>v2;
        int _v1 = locateVex(G,v1);
        int _v2 = lovateVex(G,v2);
        // 動態開辟空間,創建一個邊結點
        ArcNode * p1 = new ArcNode; // 
        p1->adjvex = _v2; // 鄰接點序號為_v2
        p1->nextarc = G.vertices[_v1].firstarc;
        G.vertices[_v1].firstarc = p1;
        // 對稱結點
        ArcNode * p2 = new ArcNode;
        p2->adjvex = _v1;
        p2->nextarc = G.vertices[_v2].firstarc;
        G.vertices[_v2].firstarc = p2;
    }
    return OK;
}

ps:,一個圖的鄰接矩陣表示是唯一的,但其鄰接表表示不唯一,這是因為鄰接表表示中,各邊表結點的鏈接次序取決於建立鄰接表的算法,以及邊的輪入次序

鄰接表表示法的優缺點

(1) 優點

  • 便於增加和刪除頂點。

  • 便於統計邊的數目, 按頂點表 順 序掃描 所 有邊表可得到邊的數目。

  • 空間效率高。對於一個具有n個頂點e條邊的圖 G,G 是無向圖,則在其鄰接表表示中有 n 個頂點表結點和 2e 個邊表結點;若 G 是有向圖,則在它的鄰接表表示或逆鄰接表表示中均有 n 個頂點表結點和e個邊表結點。因此,鄰接表或逆鄰接表表示的空間復雜度為 O(n + e), 適合表示稀疏圖。對於稠密圖,考慮到鄰接表中要附加鏈域,因此常采取鄰接矩陣表示法。

(2) 缺點

  • 不便於判斷頂點之間是否有邊,要判定 Vi和vj之間是否有邊,就需掃描第i個邊表,最壞情況下要耗費 O(n)時間。

  • 不便於計算有向圖各個頂點的度。對於無向圖,在鄰接表表示中頂點Vi的度是第i個邊表中的結點個數。 在有向圖的鄰接表中,第 i 個邊表上的結點個數是頂點 Vi的出度,但求 Vj的入度較困難,需遍歷各頂點的邊表。若有向圖采用逆鄰接表表示(就是邊表尾入度),則與鄰接表表示相反,求頂點的入度容易,而求頂點的出度較難。

十字鏈表

十字鏈表 (Orthogonal List) 是有向圖的另一種鏈式存儲結構。可以看成是將有向圖的鄰接表和逆鄰接表結合起來得到的一種鏈表。 在十字鏈表中,對應於有向圖中每一條弧有一個結點,對應於每個頂點也有一個結點。

image-20220207163228113

  • 尾域 (tailvex) 和頭域 (headvex) 分別指示弧尾和弧頭這兩個頂點在圖中的位置;
  • 鏈域 hlink 指向弧頭相同的下一條弧,而鏈域 tlink 指向弧尾相同的下一條弧;
  • info域指向該弧的相關信息。弧頭相同的弧在同一鏈表上,弧尾相同的弧也在同一鏈表上;
  • data 域存儲和頂點相關的信息,如頂點的名稱等;
  • firstinfirstout為兩個鏈域,分別指向以該頂點為弧頭或弧尾的第一個弧結點。

image-20220207163456799

ps: 此圖弧結點省略了info

有向圖的十字鏈表存儲表示

// 
const int MAX_VERTEX_NUM = 20;
typedef struct ArcBox {
    int tailvext, headvex; // 該弧頭尾頂點的位置, 這里特指頂點在順序存儲中的索引值
    struct ArcBox * hlink, * tlink; // 弧頭,弧尾相同的鏈域
    int info; // 信息
}ArcBox;

typedef struct VexNode {
    int data;
    ArcBox * firstin, * firstout; // 入弧,出弧
}VexNode;

typedef struct {
    VexNode xlist[MAX_VERTEX_NUM]; // 
    int vexnum,arcnum; // 有向圖當前頂點數,和弧數
}OLGraph;

鄰接多重表

鄰接多重表 (Adjacency Multilist) 是無向圖的另一種鏈式存儲結構。雖然鄰接表是無向圖的

一種很有效的存儲結構,在鄰接表中容易求得頂點和邊的各種信息。但是,在鄰接表中每一條邊

(Vi, Vj )有兩個結點,分別在第i個和第j個鏈表中,這給某些圖的操作帶來不便。

image-20220207164601805

  • mark 為標志域,可用以標記該條邊是否被搜索過;
  • ivexjvex為該邊依附的兩個頂點在圖中的位置;
  • ilink 指向下一條依附於頂點 ivex 的邊;
  • jlink 指向下一條依附於頂點jvex的邊,info為指向和邊相關的各種信息的指針域;

image-20220207164615461

無向圖的鄰接多重表存儲表示

const int MAX_VERTEX_NUM = 20;
typedef enum{unvisited, visited} VisitIf;
typedef struct EBox {
    VisitIf mark; // 訪問標記
    int ivex, jvex; // 該邊依附的兩個頂點的位置
    struct EBox * ilink, *jlink; // 分別指向依附這兩個頂點的下一條邊
    int info;
}EBox;
typedef struct VexBox {
    int data;
    EBox * firstedge; // 指向第一條依附該頂點的邊
}VexBox;
typedef struct {
    VexBox adjmulist[MAX_VERTEX_NUM];
    int vexNum, arcNum;
}AMLGraph;

圖的基本操作

CreateGraph{&G,V,VR}

初始條件:V是圖的頂點集,VR是圖中弧的集合。

操作結果:按V和VR的定義構造圖G。

DestroyGraph { &G}

初始條件:圖G存在。

操作結果:銷毀圖G。

Locat eVex{G,u}

初始條件:圖G存在,u和G中頂點有相同特征。

操作結果:若G中存在頂點 u, 則返回該頂點在圖中的位置;否則返回其他信息。

GetVex{G,v}

初始條件:圖G存在,v是G中某個頂點。

操作結果:返回v的值

PutVex(&G,v,value);

初始條件:圖G存在,v是G中某個頂點。

操作結果:對 v賦值value。

FirstAdjVex(G,v)

初始條件:圖G存在,v是G中某個頂點。

操作結果:返回 v的第一 個鄰接頂點。若v在G中沒有鄰接頂點,則返回 “空” 。

NextAdjVex(G,v,w)

初始條件:圖G存在,v是G中某個頂點,w是v的鄰接頂點。

操作結果:返回v的(相對千w的)下一 個鄰接頂點。若w是v的最后一 個鄰接點,則返回 “空” 。

InsertVex(&G,v)

初始條件:圖G存在,v和圖中頂點有相同特征。

操作結果:在圖G中增添新頂點v。

DeleteVex(&G,v)

初始條件:圖G存在,v是G中某個頂點。

操作結果:刪除 G中頂點v及其相關的弧。

InsertArc(&G,v,w)

初始條件:圖G存在,v和w是G中兩個頂點。

操作結果:在G中增添弧<v, w>, 若G是無向圖,則還增添對稱弧<w, v>。

Dele七eArc(&G,v,w)

初始條件:圖G存在,v和w是G中兩個頂點。

操作結果:在G中刪除弧<v, w>, 若G是無向圖,則還刪除對稱弧<w, v>。

DFSTraverse(G)

初始條件:圖G存在。

操作結果:對圖進行深度優先遍歷,在遍歷過程中對每個頂點訪問一次。

BFSTraverse(G)

初始條件:圖G存在。

操作結果:對圖進行廣度優先遍歷,在遍歷過程中對每個頂點訪問一次


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM