圖的簡介與創建


一、基本術語
 
圖:由有窮、非空點集和邊集合組成,簡寫成G(V,E);
Vertex:圖中的頂點;
 
無向圖:圖中每條邊都沒有方向;
有向圖:圖中每條邊都有方向;
 
無向邊:邊是沒有方向的,寫為(a,b)
有向邊:邊是有方向的,寫為
有向邊也成為弧;開始頂點稱為弧尾,結束頂點稱為弧頭;
 
簡單圖:不存在指向自己的邊、不存在兩條重復的邊的圖;
 
無向完全圖:每個頂點之間都有一條邊的無向圖;
有向完全圖:每個頂點之間都有兩條互為相反的邊的無向圖;
 
稀疏圖:邊相對於頂點來說很少的圖;
稠密圖:邊很多的圖;
 
權重:圖中的邊可能會帶有一個權重,為了區分邊的長短;
網:帶有權重的圖;
 
度:與特定頂點相連接的邊數;
出度、入度:對於有向圖的概念,出度表示此頂點為起點的邊的數目,入度表示此頂點為終點的邊的數目;
 
環:第一個頂點和最后一個頂點相同的路徑;
簡單環:除去第一個頂點和最后一個頂點后沒有重復頂點的環;
 
連通圖:任意兩個頂點都相互連通的圖;
極大連通子圖:包含竟可能多的頂點(必須是連通的),即找不到另外一個頂點,使得此頂點能夠連接到此極大連通子圖的任意一個頂點;
連通分量:極大連通子圖的數量;
強連通圖:此為有向圖的概念,表示任意兩個頂點a,b,使得a能夠連接到b,b也能連接到a 的圖;
 
生成樹:n個頂點,n-1條邊,並且保證n個頂點相互連通(不存在環);
最小生成樹:此生成樹的邊的權重之和是所有生成樹中最小的;
 
AOV網:結點表示活動的網;
AOE網:邊表示活動的持續時間的網;
--------------------------------------------------------------------------------
二、圖的創建存儲:
 
1、鄰接矩陣法:
 //圖邊數多時,有向圖/無向圖/有向網/無向網都可用其來存儲。
 //不過當邊數較少時不適合用此方法,會造成空間浪費
 

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

    設圖G有n個頂點,則鄰接矩陣是一個n*n的方陣,定義為:

    

    看一個實例,下圖左就是一個無向圖。

    

    從上面可以看出,無向圖的邊數組是一個對稱矩陣。所謂對稱矩陣就是n階矩陣的元滿足aij = aji。即從矩陣的左上角到右下角的主對角線為軸,右上角的元和左下角相對應的元全都是相等的。

    從這個矩陣中,很容易知道圖中的信息。

    (1)要判斷任意兩頂點是否有邊無邊就很容易了;

    (2)要知道某個頂點的度,其實就是這個頂點vi在鄰接矩陣中第i行或(第i列)的元素之和;

    (3)求頂點vi的所有鄰接點就是將矩陣中第i行元素掃描一遍,arc[i][j]為1就是鄰接點;

    而有向圖講究入度和出度,頂點vi的入度為1,正好是第i列各數之和。頂點vi的出度為2,即第i行的各數之和。

    若圖G是網圖,有n個頂點,則鄰接矩陣是一個n*n的方陣,定義為:

    

    這里的wij表示(vi,vj)上的權值。無窮大表示一個計算機允許的、大於所有邊上權值的值,也就是一個不可能的極限值。下面左圖就是一個有向網圖,右圖就是它的鄰接矩陣。

    

下面是其代碼實現:
 
#include
#include
#include
#define NUM 20;
#define BIG 22365;
 
typedef enum{DG,UNG,DN,UDN} graphtype;   //圖的類型
typedef int vrtype;      //頂點類型
typedef int *infotype;    //邊的相關信息類型
 
//定義頂點關系類型
typedef struct Arccell{
  int arckink;      //邊的類型,即頂點關系類型,無向圖為1/0,表是否相鄰。無向網為權值
 //infotype info;   //指向該邊所有相關信息
}arccell;
 
//定義無向圖/網
typedef struct{
   graphtype gkind;  //圖的類型
   vrtype vr[NUM];   //儲存所有頂點
   arccell arc[NUM][NUM];  //儲存所有邊
   int vrnum,arcnum;  //頂點和邊的個數
}graph;
 
---------------------------------
  //點位某個頂點在數組中的位置
int locate(graph G,vrtype v){
   int i;
   for(i=0;ivrnum;i++)
      if(G->vr[i]==v)
         break;
    if(i>=G->vrnum)
        return -1;
    else
        return i;
}
--------------------------------
  //創建無向圖/網
int  createUDN(graph G){
   scanf("%d %d ",&vrnum,&arnum );  // 輸入頂點和邊數量以及邊相關信息數量
   for(i=0;i
      scanf("%d",&vr[i]);
   for(i=0;i
     for(j=0;j
        arc[i][j]={BIG,NULL};
    for(i=0;i
        scanf("%d %d %d",&vr1,&vr2,&arckink);  //輸入頂點關系類型
        j=locate(G,vr1);  //定位倆頂點
        k=locate(G,vr2);  //輸入頂點關系類型
        if(i!=-1&&j!=-1){  //倆頂點均存在
            G->arc[j][k]->arckink;
         
            G->arc[j][i]->artype=G->arc[i][j]->artype;  //將信息復制到其對稱邊上
            G->arc[j][i]->info=G->arc[i][j]->info;
        }
    }
    return 0;
}
----------------------------
  //創建有向圖/網。
與創建無向圖/網類似,不過最后不用復制信息到其對稱弧上。再次不作重復創建
 
 int createDN(graph G){
 
    return 0;
}
 
-------------------------
int creategraph(graph G){   //選擇要儲存的圖的類型
   switch(G->gtype){
      case DG:createNG(G);
      case UNG:createUNG(G);
      case DN:createDN(G);
      case UDN:createUDN(G);
      default:return ERROR;
   }
}
 
int main()    //主函數。
{
    graph G;
    creategraph(G);
    return 0;
}
 
--------------------------------------------------------------------------------------------------
 
2、鄰表法:
//鄰表法解決了鄰接矩陣的缺點,但對於每個頂點而已,查找其出度容易,而要找入讀卻得遍歷整個圖才能找到。故鄰表法不適合有向圖/網存儲。無向圖/網可以其來存儲,比十字鏈表法簡單
 

    鄰接表的處理方法是這樣的:

    (1)圖中頂點用一個一維數組存儲,當然,頂點也可以用單鏈表來存儲,不過,數組可以較容易的讀取頂點的信息,更加方便。

    (2)圖中每個頂點vi的所有鄰接點構成一個線性表,由於鄰接點的個數不定,所以,用單鏈表存儲,無向圖稱為頂點vi的邊表,有向圖則稱為頂點vi作為弧尾的出邊表。

    例如,下圖就是一個無向圖的鄰接表的結構。

    

    從圖中可以看出,頂點表的各個結點由data和firstedge兩個域表示,data是數據域,存儲頂點的信息,firstedge是指針域,指向邊表的第一個結點,即此頂點的第一個鄰接點。邊表結點由adjvex和next兩個域組成。adjvex是鄰接點域,存儲某頂點的鄰接點在頂點表中的下標,next則存儲指向邊表中下一個結點的指針。

    對於帶權值的網圖,可以在邊表結點定義中再增加一個weight的數據域,存儲權值信息即可。如下圖所示。

    

 

對於鄰接表結構,圖的建立代碼如下。

#define BIG 26655;
#define NUM 20;
 
typedef char vrtype; //頂點為字符時不能是換行符,因為輸出字符要換行
typedef int *infotype;
 
 
//定義頂點關系類型
typedef struct Arccell{
  int tail;
  int weight;      //無向網為權值,無向圖不需要。
 //infotype info;   //指向該邊所有相關信息
  struct Arccell *next;
}arccell;
 
//定義頂點類型
typedef struct {
   vrtype data;
   arccell *firstarc;
}vrcell;
 
 
//定義無向圖/網類型
typedef struct{
   vrcell vr[NUM];   //儲存所有頂點
   int vrnum,arcnum;  //頂點和邊的個數
}graph;
 
 
void CreateGraph(Graph g)
{
    int i, j, k;
    EdgeNode *e;
    EdgeNode *f;
  //  printf("輸入頂點數和邊數:\n");
    scanf("%d,%d", &g->numVertexes, &g->numEdges);
 
    for(i = 0; i < g->numVertexes; i++)    //輸入頂點信息
    {
       // printf("請輸入頂點%d:\n", i);
        g->adjList[i].data = getchar();
        g->adjList[i].firstedge = NULL;     //將邊表置為空表
 
        while(g->adjList[i].data == '\n')
        {
            g->adjList[i].data = getchar();
        }
    }
 
    //建立邊表
    for(k = 0; k < g->numEdges; k++)
    {
        printf("輸入邊(vi,vj)上的頂點序號:\n");
        char p, q;
        p = getchar();
        while(p == '\n')
        {
            p = getchar();
        }
        q = getchar();
        while(q == '\n')
        {
            q = getchar();
        }
        int m, n;
        m = Locate(g, p);
        n = Locate(g, q);
        if(m == -1 || n == -1)
        {
            return;
        }
        //向內存申請空間,生成邊表結點
        e = (EdgeNode *)malloc(sizeof(EdgeNode));
        if(e == NULL)
        {
            fprintf(stderr, "malloc() error.\n");
            return;
        }
        //鄰接序號為j
        e->adjvex = n;
        //將e指針指向當前頂點指向的結構
        e->next = g->adjList[m].firstedge;
        //將當前頂點的指針指向e
        g->adjList[m].firstedge = e;
 
 
        f = (EdgeNode *)malloc(sizeof(EdgeNode));  //有向圖/網則不需要進行這一步
        if(f == NULL)
        {
            fprintf(stderr, "malloc() error.\n");
            return;
        }
        f->adjvex = m;
        f->next = g->adjList[n].firstedge;
        g->adjList[n].firstedge = f;
    }
}
 
----------------------------------------------------------------------------------------
 
3、十字鏈表法:
 
十字鏈表(Orthogonal List)是有向圖的一種存儲方法,它實際上是鄰接表與逆鄰接表的結合,即把每一條邊的邊結點分別組織到以弧尾頂點為頭結點的鏈表和以弧頭頂點為頭頂點的鏈表中。在十字鏈表表示中,頂點表和邊表的結點結構分別如圖8.13 的(a)和(b)所示。
 
        在弧結點中有五個域:其中尾域(tailvex)和頭(headvex)分別指示弧尾和弧頭這兩個頂點在圖中的位置,鏈域hlink 指向弧頭相同的下一條弧,鏈域tlink 指向弧尾相同的下一條弧,info 域指向該弧的相關信息。弧頭相同的弧在同一鏈表上,弧尾相同的弧也在同一鏈表上。它們的頭結點即為頂點結點,它由三個域組成:其中vertex 域存儲和頂點相關的信息,如頂點的名稱等;firstin 和firstout 為兩個鏈域,分別指向以該頂點為弧頭或弧尾的第一個弧結點。例如,圖8.14(a)中所示圖的十字鏈表如圖8.14(b)所示。若將有向圖的鄰接矩陣看成是稀疏矩陣的話,則十字鏈表也可以看成是鄰接矩陣的鏈表存儲結構,在圖的十字鏈表中,弧結點所在的鏈表非循環鏈表,結點之間相對位置自然形成,不一定按頂點序號有序,表頭結點即頂點結點,它們之間而是順序存儲。有向圖的十字鏈表存儲表示的形式描述如下:
 
#define MAX_VERTEX_NUM 20
typedef struct ArcBox {
int tailvex,headvex;
struct ArcBox * hlink, tlink; /分別為弧頭相同和弧尾相財的弧的鏈域*/
InfoType info;
}ArcBox;
typedef struct VexNode {
  VertexType vertex:
  ArcBox fisrin, firstout;
} VexNode;
typedef struct {
   VexNode xlist[MAX_VERTEX_NUM];
   int vexnum,arcnum;
}OLGraph;
 
 
       下面給出建立一個有向圖的十字鏈表存儲的算法。通過該算法,只要輸入n 個頂點的信息和e 條弧的信息,便可建立該有向圖的十字鏈表,其算法內容如下。
void CreateDG(LOGraph **G)
{
       scanf (&(*G->brcnum),&(*G->arcnum),&IncInfo);
       for (i=0;i<*G->vexnum;++i) {
     scanf(&(G->xlist[i].vertex));
    *G->xlist[i].firstin=NulL;*G->xlist[i].firstout =NULL;
           }
for(k=0;k
{   scanf(&v1,&v2);
i=LocateVex(*G,v1); j=LocateVex(*G,v2);
p=(ArcBox*) malloc (sizeof(ArcBox));
*p={ i,j,*G->xlist[j].fistin,*G->xlist[i].firstout,NULL}
*G->xlist[j].fisrtin=*G->xlist[i].firstout=p;
if (IncInfo) Input( p->info);
}
}
 
在十字鏈表中既容易找到以為尾的弧,也容易找到以vi 為頭的弧,因而容易求得頂點的出度和入度(或需要,可在建立十字鏈表的同時求出)。同時,由算法8.3 可知,建立十字鏈表的時間復雜度和建立鄰接表是相同的。在某些有向圖的應用中,十字鏈表是很有用的工具。


免責聲明!

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



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