1. 程式人生 > >圖及其應用c語言實現(資料結構複習最全筆記)(期末複習最新版)

圖及其應用c語言實現(資料結構複習最全筆記)(期末複習最新版)

一.圖的基本概念

1.圖的定義


圖是由頂點(vertex)集合及頂點間的關係組成的一種資料結構。Graph=(V,E)Graph=(V,E)其中,頂點集合 V={x|x∈某個物件資料集}V={x|x∈某個物件資料集} 是有窮非空集合;E={(x,y)|x,y∈V}E={(x,y)|x,y∈V} 是頂點間關係的有窮集合,也叫邊(edge)集合。Path(x,y)Path(x,y)表示從頂點x到y的一條單向通路,他是有方向的。

2.圖的相關概念


∙∙ 有向圖(Directed Graph):一般用<u,v><u,v>表示 
∙∙ 無向圖(Undirected Graph):一般用(u,v)(u,v)表示 
∙∙ 完全圖(Complete Graph):在n個頂點組成的無向圖中,若有n(n−1)2n(n−1)2條邊,則稱為無向完全圖。在n個頂點組成的有向圖中,若有n(n−1)n(n−1)條邊,則稱為有向完全圖。完全圖中的邊數達到最大。 
..稀疏圖:邊或弧很少的圖

..稠密圖:邊或弧很多的圖
∙∙ 權(Weight):在某些圖中,邊具有與之相關的數量(比如一個頂點到另一個頂點的距離、花費的代價、所需的時間、次數等)。這種帶權圖也叫做網路(Network)。 
∙∙ 鄰接頂點(Adjacent vertex):如果(u,v)是E(G)中的一條邊,則u和v互為鄰接頂點,且邊(u,v)依附於頂點u和v,頂點u和v依附於邊(u,v)。如果<u,v><u,v>是E(G)中的一條有向邊,則稱頂點u鄰接到頂點v,頂點v鄰接自頂點u,邊<u,v><u,v>與頂點u和v相關聯。 
∙∙ 子圖(Subgraph):Subgraph Let G = (V, E) be a graph with vertex set V and edge set E. A subgraph of G is a graph G’ = (V’, E’) where 
1. V’ is a subset of V 
2. E’ consists of edges (v, w) in E such that both v and w are in V’ 

∙∙ 度(Degree):與頂點v關聯的邊數,稱為v的度,記作deg(v)。有向圖中,頂點的度是入度和出度之和。頂點v的入度是指以v為終點的有向邊的條數,記作indeg(v),頂點v的出度是指以v為始點的有向邊的條數,記作outdeg(v)。頂點v的度為deg(v)=indeg(v)+outdeg(v)。一般地,若圖中有n個頂點,e條邊,那麼:e=12{∑ni=1deg(vi)}e=12{∑i=1ndeg(vi)} 
∙∙ 路徑(Path)&路徑長度(Path length):路徑可以用頂點序列表示,在某些演算法中,也可用一系列邊來表示。對與不帶權圖,路徑長度指的是此路徑上邊的條數,對於帶權圖,路徑長度指的是此路徑上各條邊的權值之和。 
∙∙ 簡單路徑&迴路(Cycle):路徑上各頂點互不重複,這樣的路徑為簡單路徑。路徑上第一個頂點與最後一個頂點重合,這樣的路徑為迴路。 
 
∙∙ 連通圖(Connected graph)&連通分量(Connected component ):無向圖中,若從頂點v1到頂點v2頂點v1到頂點v2有路徑,則稱頂點v1與頂點v2頂點v1與頂點v2是連通的。如果圖中任一對頂點都是連通的,則稱此圖是連通圖。非連通圖的極大連通子圖叫做連通分量。 
∙∙ 強連通圖(Strongly connected graph)&強連通分量(Strongly connected component ):有向圖中,若在每一對頂點vi到頂點vj頂點vi到頂點vj之間存在一條從頂點vi到頂點vj頂點vi到頂點vj的路徑,也存在一條從頂點vj到頂點vi頂點vj到頂點vi的路徑,則稱此圖是強連通圖。非強連通圖的極大強連通子圖叫做強連通分量。 
∙∙ 生成樹(Spanning tree):一個無向連通圖的生成樹是它的極小連通子圖,若圖中有n個頂點,則其生成樹由n-1條邊構成。若是有向圖,則可能得到它的若干有向樹組成的森林。 
∙∙ 最小生成樹(Minimum spanning tree):一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。

*包含圖的所有頂點,n-1條邊
*沒有迴路
*邊的權重和最小

由此可知,如果一個圖有n個頂點和小於n-1條邊,則是非連通圖。

如果一個圖有n個頂點和大於n-1條邊,則必定形成環

此外,有n-1條邊的不一定事生成樹


3.圖的相關性質(重點)

a:在無向圖中,與一個頂點關聯的邊數稱為該頂點的度,一般使用d表示,設G=(V,E)是一個無向圖,令n=|V|,e=|E|,

每個頂點度數之和等於2e;0<=e<=n(n-1)/2。

b:在有向圖中,一個頂點的入度是指關聯至該頂點的邊數,頂點的出度是指,關聯於該頂點的邊數。設G=(V,E)是一個無向圖,令n=|V|,e=|E|,則圖G的每個節點的出度之和和入度之和相等等於e,0<=e<n(n-1)/2

c:具有n個結點的無向完全圖共有n*(n-1)/2條邊

d:具有n個結點的有向完全圖共有n*(n-1)條邊

二.圖的抽象資料型別

三.圖的儲存結構

關於圖的儲存結構,可以分為以下五種(前兩種必會):

(1) 鄰接矩陣

圖的鄰接矩陣儲存方式是用兩個陣列來表示圖:

一個一維陣列儲存圖中頂點資訊;

一個二維陣列(稱為鄰接矩陣)儲存圖中邊或弧的資訊

(2) 鄰接表

鄰接矩陣是一種不錯的圖儲存結構。 但是:對於邊樹相對頂點較少的圖,這種結構是存在儲存空間的極大浪費的。

因此我們考慮先進一步,使用鄰接表儲存,關於鄰接表的處理辦法是這樣:

 

需要N個頭指標 + 2E個結點(每個結點至少2個域),則E小於多少是省空間的?也就是多稀疏才是合算的?

答案:

 

下圖是一個無向圖的鄰接表結構:

對於有向圖而言,我們會發現鄰接表只適合計算其出度,並不能較好的計算入度。

為了更便於確定頂點的入度(或以頂點為弧頭的弧),我們可以建立一個有向圖的逆鄰接表。

如下圖所示:

而對於有權值的網圖,可以在邊表節點定義中再增加一個weight的資料域,儲存權值資訊即可。 如下圖所示:

那麼,有了這些結構的圖,下面定義程式碼如下:

 

(3) 十字連結串列

對於有向圖而言,鄰接表也是有缺陷的。

試想想哈,關心了出度問題,想了解入度問題就必須把整個圖遍歷才能知道。

反之,逆鄰接表解決了入度問題卻不瞭解出度的情況。

那是否可以將鄰接表和逆鄰接表結合起來呢?答案是肯定的。

這就是所謂的儲存結構:十字連結串列。其詳解如下圖:

(4) 鄰接多重表

有向圖的優化儲存結構為十字連結串列。

對於無向圖的鄰接表,有沒有問題呢?如果我們要刪除無向圖中的某一條邊時?

那也就意味著必須找到這條邊的兩個邊節點並進行操作。其實還是比較麻煩的。比如下圖:

欲刪除上圖中的(V0,V2)這條邊,需要對鄰接表結構中右邊表的陰影兩個節點進行刪除。

仿照十字連結串列的方式,對邊表節點的結構進行改造如下:

(5)邊集陣列

邊集陣列側重於對邊依次進行處理的操作,而不適合對頂點相關的操作。

關於邊集陣列詳解如下:

 四.圖的遍歷(最重要的)

圖的遍歷圖和樹的遍歷類似,那就是從圖中某一頂點出發訪遍圖中其餘頂點,且使每一個頂點僅被訪問一次,這個過程就叫做圖的遍歷。

對於圖的遍歷來說,如何避免因迴路陷入死迴圈,就需要科學地設計遍歷方案,通過有兩種遍歷次序方案:深度優先遍歷和廣度優先遍歷。

(1) 深度優先遍歷

深度優先遍歷(Depth_First_Search),也稱為深度優先搜尋,簡稱DFS。

為了更好的理解深度優先遍歷。請看下面的圖解:

其實根據遍歷過程轉換為右圖後,可以看到其實相當於一棵樹的前序遍歷

下面給出遍歷演算法實現程式碼

鄰接矩陣的dfs演算法實現

void Visit( Vertex V )
{
    printf(" %d", V);
}

void DFS( MGraph Graph, Vertex V, void (*Visit)(Vertex) )
{
    Vertex j;
    Visited[V] = true;//表示從V開始訪問
    Visit(V);//訪問V,其實拋開題來講這裡一般都是列印V
    for(j = 0;j < Graph->Nv;j++)
    {
        if(Graph->G[V][j] == 1 && !Visited[j])//鄰接矩陣等於1代表有邊,此時如果還沒被訪問就遞迴呼叫
            DFS(Graph,j,Visit);
    }
}

要注意的是,實際做題時遍歷函式的引數有很多種,你得自己會變通 

舉個簡單的例子,dfs也可以這麼寫(其實本質都一樣)


void DFS(int v)//鄰接矩陣的dfs
{
    visited[v] = 1;
    cout<<v<<" ";
    for(int i = 0;i < Nv;i++)
    {
        if((G[v][i] == 1) && !visited[i])//鄰接矩陣等於1代表有邊(連通),此時如果還沒被訪問就遞迴呼叫
            DFS(i);
    }
}

或者

void DFS(MGraph G, int i){
    int j;
    printf("%d ", G.vexs[i]);
    visited[i] = 1;
    for(j = 0; j < G.numV; j++){
        if(G.arc[i][j] == 1 && !visited[j]){
            DFS(G, j);
        }
    }
}

void DFSTraverse(MGraph G){
    int i;
    for(i = 0; i < G.numV; i++){
        visited[i] = 0;
    }
    for(i = 0; i < G.numV; i++){
        if(!visited[i]){
            DFS(G, i);
        }
    }
}

 

鄰接表的dfs演算法實現

/* 鄰接表儲存的圖 - DFS */
 
void Visit( Vertex V )
{
    printf(" %d\n", V);
}
 
/* Visited[]為全域性變數,已經初始化為false */
void DFS( LGraph Graph, Vertex V, void (*Visit)(Vertex) )
{   /* 以V為出發點對鄰接表儲存的圖Graph進行DFS搜尋 */
    PtrToAdjVNode W;
     
    Visit( V ); /* 訪問第V個頂點 */
    Visited[V] = true; /* 標記V已訪問 */
 
    for( W=Graph->G[V].FirstEdge; W; W=W->Next ) /* 對V的每個鄰接點W->AdjV */
        if ( !Visited[W->AdjV] )    /* 若W->AdjV未被訪問 */
            DFS( Graph, W->AdjV, Visit );    /* 則遞迴訪問之 */
}

 你也可以這麼寫

void DFS(Graph *G, int i)//鄰接表的深度優先遞迴演算法
{
    EdgeNode *p;
    visited[i] = 1;
    printf("%d", G->adjList[i].data);
    p = G->adjList[i].firstedge;
    while(p){
        if(!visited[p->adjvex]){
            DFS(G, p->adjvex);
        }
        p = p->next;
    }
}

void DFSTraverse(Graph *G)//鄰接表的深度遍歷操作
{
    int i;
    for(i = 0; i < G->maxVertexes; i++){
        visited[i] = 0;
    }
    for(i = 0; i < G->maxVertexes; i++){
        if(!visited[i]){
            DFS(G, i);
        }
    }
}

(2)廣度優先遍歷

廣度優先遍歷(Breadth_First_Search),又稱為廣度優先搜尋,簡稱BFS。

深度遍歷類似樹的前序遍歷,廣度優先遍歷類似於樹的層序遍歷。

鄰接矩陣實現

/* 鄰接矩陣儲存的圖 - BFS */
 
/* IsEdge(Graph, V, W)檢查<V, W>是否圖Graph中的一條邊,即W是否V的鄰接點。  */
/* 此函式根據圖的不同型別要做不同的實現,關鍵取決於對不存在的邊的表示方法。*/
/* 例如對有權圖, 如果不存在的邊被初始化為INFINITY, 則函式實現如下:         */
bool IsEdge( MGraph Graph, Vertex V, Vertex W )
{
    return Graph->G[V][W]<INFINITY ? true : false;
}
 
/* Visited[]為全域性變數,已經初始化為false */
void BFS ( MGraph Graph, Vertex S, void (*Visit)(Vertex) )
{   /* 以S為出發點對鄰接矩陣儲存的圖Graph進行BFS搜尋 */
    Queue Q;     
    Vertex V, W;
 
    Q = CreateQueue( MaxSize ); /* 建立空佇列, MaxSize為外部定義的常數 */
    /* 訪問頂點S:此處可根據具體訪問需要改寫 */
    Visit( S );
    Visited[S] = true; /* 標記S已訪問 */
    AddQ(Q, S); /* S入佇列 */
     
    while ( !IsEmpty(Q) ) {
        V = DeleteQ(Q);  /* 彈出V */
        for( W=0; W<Graph->Nv; W++ ) /* 對圖中的每個頂點W */
            /* 若W是V的鄰接點並且未訪問過 */
            if ( !Visited[W] && IsEdge(Graph, V, W) ) {
                /* 訪問頂點W */
                Visit( W );
                Visited[W] = true; /* 標記W已訪問 */
                AddQ(Q, W); /* W入佇列 */
            }
    } /* while結束*/
}

但在做題時往往需要用陣列模擬佇列來進行bfs,其鄰接表的實現方法如下

void BFS ( LGraph Graph, Vertex S, void (*Visit)(Vertex) )
{
    int queue[1010];
    int l=0,r=0;//l是隊頭,r是隊尾
    queue[r++]=S;//插入到隊尾
    Visit (S);
    Visited[S]=true;
    PtrToAdjVNode tmp;//邊表結點指向下一個臨界點的指標,其實就是next
    while(l!=r)//就是隊不空
    {
        tmp=Graph->G[queue[l++]].FirstEdge;//找到當前頂點邊錶鏈表頭指標,queue[l++]就是每次迴圈隊頭都要出隊。l++在這裡相當於邊表遍歷完了之後開始遍歷下一個頂點表
        while(tmp)
        {
            Vertex pos=tmp->AdjV;//pos為鄰接點下標
            if(!Visited[pos])//沒訪問就訪問它
            {
                Visit(pos);
                Visited[pos]=true;
                queue[r++]=pos;//插入到隊尾
            }
            tmp=tmp->Next;//指標指向下一個鄰接點
        }
    }
}

 

鄰接表實現

void BFSTravel(GraphAdjList *g)
{
    int i;
    int tmp;
    EdgeNode *p;
    queue q;
    for(i=0;i<g->numVertexes;i++)
        visited[i]= 0;
    initQueue(&q);
    for(i=0;i<g->numVertexes;i++)
    {
        if(!visited[i])
        {
            visited[i]=1;
            printf("%c ",g->adjList[i].data);
            push(&q,i);
            while(!isEmpty(&q))
            {
                tmp  = pop(&q);
                p = g->adjList[tmp].firstedge;
                while(p)
                {
                    if(!visited[p->adjvex])
                    {
                        visited[p->adjvex]=1;
                        printf("%c ",g->adjList[p->adjvex].data);
                        push(&q,p->adjvex);
                    }                   
                    p = p->next;
                }
            }
        }
    }
}

思考 :如果圖不連通怎麼辦

 

五.圖的建立詳解 

參考這篇部落格就成:https://blog.csdn.net/weixin_42110638/article/details/84195985

一下三個較難,至少要學會原理,演算法實現儘量學會 

六.最小生成樹

同樣參考這篇連結就成:https://blog.csdn.net/weixin_42110638/article/details/84223131

七.最短路徑

同樣參考這篇連結就成:https://blog.csdn.net/weixin_42110638/article/details/84350009

八.拓撲排序與關鍵序列

同樣參考這篇連結就成:https://blog.csdn.net/weixin_42110638/article/details/84246833