資料結構——圖

來源:互聯網
上載者:User

標籤:graph   圖   資料結構   

圖(Graph)

(參考資料:《大話資料結構》《演算法導論》)

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

圖的相關術語

1.無向圖與有向圖

無向圖:圖中任意兩個頂點u和v之間的邊沒有方向(無向邊),每一條無向邊用無序偶對(u,v)或(v,u)表示。
無向完全圖:任意兩個頂點之間都存在邊,邊個數:n*(n-1)/2(n個頂點)。
有向圖:圖中任意兩個頂點u和v之間的邊具有方向(有向邊,弧),每一條從頂點u到頂點v的有向邊用有序偶對 < u,v > 表示,u稱為弧尾,v稱為弧頭。
有向完全圖:任意兩個頂點之間都存在方向相反的兩條弧,邊個數:n*(n-1)(n個頂點)。

  • 無向邊用”(u,v)”表示,有向邊用“ < u,v > “表示。

2.稀疏圖和稠密圖

稀疏圖:邊的條數|E|遠小於|v|^2的圖。
反之,稱為稠密圖。

3.權重圖(網)

圖中的每一條邊都帶有一個相關的權重的圖。
權重圖可以由鄰接表和鄰接矩陣表示(用權重值代替1),若含0權重,則可以用無窮數表示無邊情況。

4.頂點的度

無向圖:頂點u的度是和u相關聯的邊的數目。
有向圖:頂點u的入度是以頂點u為弧頭的邊的數目;頂點u的出度是以頂點u為弧尾的邊的數目。

5.路徑

從頂點u到頂點v的路徑是一個頂點序列。
路徑的長度:路徑上的邊或弧的數目。
簡單路徑:序列中頂點不重複出現的路徑。
迴路或環:第一個頂點到最後一個頂點相同的路徑。
簡單迴路或簡單環:除了第一個頂點和最後一個頂點之外,其餘頂點不重複出現的迴路。

圖的表示

1.鄰接表

鄰接鏈表表示有一個包含|V|條鏈表的數組Adj所構成,每個頂點有一條鏈表。對於每個頂點u,鄰接鏈表Adj[u]包含所有與頂點u之間有邊相連的頂點v。
儲存空間:O(V+E)。
無向圖:所有鄰接鏈表的長度之和為2|E|;有向圖:所有鄰接鏈表的長度之和為|E|。

優點

1.表示稀疏圖,表示緊湊,節約空間,表示簡單。
2.魯棒性高,可以對其進行簡單修改來支援許多其他的圖變種。
缺點
1.無法快速判斷兩個頂點u和v之間是否是圖中的一條邊,需要在鄰接鏈表Adj[u]裡搜尋結點v。

#define MAXVEX 10              // maximum vertex numbers in the graphtypedef int VertexType;        // vertex data typetypedef int EdgeType;          // edge weight type// edge node to record the adjacent vertex node for a vertex nodetypedef struct EdgeNode {    int adjvex;                // the adjacent vertex node position in array    EdgeType weight;           // edge weight    struct EdgeNode* next;     // next edge node}EdgeNode;// vertex node to record the data for a vertex nodetypedef struct VertexNode {    VertexType data;           // vertex data    EdgeNode *firstedge;       // first edge node pointer}VertexNode; // graphtypedef struct {    VertexNode adj[MAXVEX];    int numVertexes;    int numEdges;}Graph;Graph* CreateGraph(){    Graph* G = new Graph;    cout << "input vertex num and edge num:" << endl;    cin >> G->numVertexes >> G->numEdges;    cout << "input vertex data one by one:" << endl;    for (int index = 0; index < G->numVertexes; index++) {        cin >> G->adj[index].data;        G->adj[index].firstedge = NULL;    }    cout << "input edge each by two vertex index(begin from 0):" << endl;    for (int index = 0; index < G->numEdges; index++) {        int i, j;        cin >> i >> j;        EdgeNode* e = new EdgeNode;        e->adjvex = j;        e->next = G->adj[i].firstedge;        G->adj[i].firstedge = e;#define NODRECTIONGRAPH#ifdef NODRECTIONGRAPH        e= new EdgeNode;        e->adjvex = i;        e->next = G->adj[j].firstedge;        G->adj[j].firstedge = e;#endif    }    return G;}void BFS(Graph* G) {    memset(visit, 0, sizeof(visit[0]) * MAXVEX);    queue<EdgeNode*> q;    EdgeNode* temp;    for (int index = 0; index < G->numVertexes; index++) {        if (!visit[index]) {            visit[index] = true;            q.push(G->adj[index].firstedge);            cout << " from vertex data: " <<                G->adj[index].data << endl;            while (!q.empty()) {                temp = q.front();                if (temp && !visit[temp->adjvex]) {                    cout << "get weight: " << temp->weight <<                        " to " << temp->adjvex << endl;                    visit[temp->adjvex] = true;                    q.push(temp->next);                }                q.pop();            }        }    }}void DFS_VISIT(Graph* G, int index){    visit[index] = true;    cout << "vertex data: " << G->adj[index].data << endl;    EdgeNode* temp = G->adj[index].firstedge;    while (temp) {        if (!visit[temp->adjvex])            DFS_VISIT(G, temp->adjvex);        temp = temp->next;    }}void DFS(Graph* G){    memset(visit, 0, sizeof(visit[0]) * MAXVEX);    for (int index_i = 0; index_i < G->numVertexes; index_i++) {        if (!visit[index_i])            DFS_VISIT(G, index_i);    }}

2.鄰接矩陣

由一個|V|x|V|的矩陣A = (aij)表示。aij = 1,若含有(i,j)邊;aij = 0,其他。
儲存空間:O(V^2)。
無向圖:鄰接矩陣為對稱矩陣,可以減少圖儲存空間一半需求。

優點

1.表示稠密圖。
2.可以快速判斷兩個頂點u和v之間是否是圖中的一條邊。

缺點

1.消耗非常大的儲存空間。

#define MAXVEX 10              // maximum vertex numbers in the graphtypedef int VertexType;        // vertex data typetypedef int EdgeType;          // edge weight type#define INFINITY 65535         // set as infinity value// graphtypedef struct {    VertexType vertex[MAXVEX];    // vertex list    EdgeType arc[MAXVEX][MAXVEX]; // adjacent matrix    int numVertexes;    int numEdges;}Graph;Graph* CreateGraph(){    Graph* G = new Graph;    cout << "input vertex num and edge num:" << endl;    cin >> G->numVertexes >> G->numEdges;    cout << "input vertex data one by one:" << endl;    for (int index = 0; index < G->numVertexes; index++) {        cin >> G->vertex[index];    }    // initial adjacent matrix    for (int index_i = 0; index_i < G->numVertexes; index_i++)        for (int index_j = 0; index_j < G->numVertexes; index_j++)            G->arc[index_i][index_j] = INFINITY;    cout << "input edge each by two vertex index and weight:" << endl;    for (int index = 0; index < G->numEdges; index++) {        int i, j, w;        cin >> i >> j >> w;        G->arc[i][j] = w;#define NODRECTIONGRAPH#ifdef NODRECTIONGRAPH        G->arc[j][i] = w;#endif    }    return G;}void BFS(Graph* G) {    memset(visit, 0, sizeof(visit[0]) * MAXVEX);    queue<int> q;    for (int index = 0; index < G->numVertexes; index++) {        if (!visit[index]) {            visit[index] = true;            q.push(index);            while (!q.empty()) {                int index_i = q.front();                cout << " from vertex " << index_i << " data: " <<                    G->vertex[index_i] << endl;                for (int index_j = 0; index_j < G->numVertexes; index_j++) {                    if (G->arc[index_i][index_j] != INFINITY &&                            !visit[index_j]) {                        visit[index_j] = true;                        cout << "get weight: " << G->arc[index_i][index_j] <<                            " to " << index_j << endl;                        q.push(index_j);                    }                }                q.pop();            }        }    }}void DFS_VISIT(Graph* G, int index){    visit[index] = true;    cout << "vertex data: " << G->vertex[index] << endl;    for (int index_j = 0; index_j < G->numVertexes; index_j++) {        if (G->arc[index][index_j] != INFINITY && !visit[index_j])            DFS_VISIT(G, index_j);    }}void DFS(Graph* G){    memset(visit, 0, sizeof(visit[0]) * MAXVEX);    for (int index_i = 0; index_i < G->numVertexes; index_i++) {        if (!visit[index_i])            DFS_VISIT(G, index_i);    }}

圖的搜尋

1.廣度優先搜尋 BFS Breadth-First-Search

  • 演算法始終將已發現頂點和未發現頂點之間的邊界,沿其廣度方向向外擴充。也就是說,演算法需要在發現所有距離源頂點s為k的所有頂點後,才會發現距離源頂點s為k+1距離的其他頂點。
  • 對每個頂點,設定標誌位記錄是否被”發現“過。
  • 採用先進先出隊列實現(可以聯想二叉樹的層次遍曆,是圖廣度優先搜尋的子集)。
  • 頂點u的屬性u.d為最短距離,記錄從源頂點s到頂點u之間的距離。
  • 廣度優先樹:BFS過程中產生的前驅子圖(通過記錄每個頂點的前驅頂點)。
    時間複雜度:O(V + E)。

廣度優先搜尋虛擬碼

BFS(G,s)  for each vertex u in G.V - {s}    u.color = WHITE    u.d = INFINITY    u.parent = NIL  s.color = GRAY  s.d = 0  s.parent = NIL  Q = EMPTY  ENQUEUE(Q,s)  while Q != EMPTY    u = DEQUEUE(Q,s)    for each vertex v in G.adj[u]      if v.color == WHITE        v.color = GRAY        v.d = v.d + 1        v.parent = u        ENQUEUE(Q,s)    u.color = BLACK

2.深度優先搜尋 DFS Depth-First-Search


  • 演算法總是對最近才發現的頂點的出發邊進行探索,直到該頂點的所有出發邊都被發現為止。演算法重複整個過程,直到所有的頂點都被發現為止。
  • 對每個頂點,設定標誌位記錄是否被”發現“過。
  • 採用函數遞迴調用實現(可以聯想二叉樹的先序、中序、後序遍曆,是圖深度優先搜尋的子集)。
  • 頂點u的屬性u.d為時間戳記,發現時間,記錄從頂點u第一次被發現的時間。
  • 頂點u的屬性u.f為時間戳記,完成時間,記錄完成對頂點u的鄰接鏈表掃描的時間。
  • 深度優先森林:深度優先搜尋的前驅子圖形成一個由多棵深度優先樹構成的深度優先森林(搜尋可能從多個源結點重複進行)。
  • DFS對邊進行分類

  • 樹邊(u,v):深度優先森林的邊,頂點v因演算法對邊(u,v)的探索而首先被發現。(u.d < v.d < v.f < u.f)
  • 後向邊(u,v):是將結點u串連到深度優先樹中一個祖先結點v的邊(有向圖中的自迴圈)。(v.d < =u.d < u.f <= v.f)
  • 前向邊(u,v):是將結點u串連到深度優先樹中一個後代結點v的邊。 (u.d < v.d < v.f < u.f)
  • 橫向邊(u,v):上述三種邊以外的邊。 (v.d < v.f < u.d < v.f)
  • 無向圖:每條邊要麼是樹邊,要麼是後向邊。

時間複雜度:O(V + E)。

廣度優先搜尋虛擬碼

DFS(G)  for each vertex u in G.V    u.color = WHITE    u.parent = NIL  time = 0  for each vertex u in G.V     if (u.color == WHITE)       DFS-VISIT(G,u)DFS-VISIT(G,u)  time = time + 1  u.d = time  u.color = GRAY  for each vertex v in G.adj[u]    if v.color == WHITE      v.parent = u      DFS-VISIT(G,v)  u.color = BLACK  time = time + 1  u.f = time

應用:有向非循環圖的拓撲排序

  • 利用深度優先搜尋演算法。
  • 有向非循環圖的深度優先搜尋不產生後向邊。

應用:將有向圖分解為強連通分量

  • 利用深度優先搜尋演算法。

資料結構——圖

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.