[數據結構]圖的基本概念、存儲結構(通俗易懂)

一、圖的定義

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

前面我們學習了線性表、樹,到現在學圖的相關知識,針對圖的定義,簡單地瞭解一下圖的幾個特點:

(1)線性表中數據元素叫元素;樹中數據元素叫結點;在圖中的數據元素,我們則稱之爲頂點。

(2)線性表中可以沒有數據元素,稱爲空表;樹中可以沒有結點,叫做空樹;但是在圖結構中,不允許沒有頂點。

(3)在線性表中,相鄰的數據元素之間具有線性關係;樹結構中,相鄰兩層的結點具有層次關係;在圖中,任意兩個頂點之間都可能有關係,頂點之間的邏輯關係用邊來表示,邊集可以爲空。

1、各種圖定義

無向邊:若頂點vi到vj之間的邊沒有方向,可用有序偶對(vi,vj)來表示。

有向邊(弧):若從頂點vi到v之間j的邊有方向,可用有序偶對<vi,vj>表示。

無向完全圖:任意兩個頂點之間都存在邊。n個頂點的無向完全圖有n*(n-1)/2條邊。

有向完全圖:任意兩個頂點之間都存在方向互爲相反的兩條弧。n個頂點的有向完全圖有n*(n-1)條邊。

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

--此圖摘取數據結構PPT中

路徑:如無向圖頂點1到頂點5的路徑爲v1,v2,v5。

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

迴路或環:路徑的起點和終點相同。

網路或網:帶權的圖。

二、圖的存儲方式

1、鄰接矩陣:表示頂點之間的相鄰關係的矩陣。無向圖的鄰接矩陣一定對稱。

#define N ?///最大頂點
typedef struct{
   char vex[N];///頂點名
   int  e[N][N];///鄰接矩陣
}Mgraph;

void creat_Mgraph(Mgraph *G,int n){
        for(int i=0;i<n;i++) scanf("%c",&G->vex[i])///輸入頂帶你
        for(int i=0;i<n;i++)
            for(int j=0j<n;j++)
               G->e[i][j]=0;///初始化
        for(int k=0;k<n;k++){///帶權無向圖
            scanf("%d%d",&a,&b);
            G->e[a][b]=1;
            G->e[b][a]=1;
        }
}

注意:鄰接矩陣比較浪費內存,一般只適用邊數比較少時;當數據較大時,往往會超時間或者超內存。

2、鄰接表:數組與鏈表相結合的存儲方法。  

(1)鄰接表的鏈式實現:它的存儲由頂點表和邊表這兩部分組成。

      1)頂點表的各個結點由datafirstedge兩個域組成。

      2)邊表結點由adjvexnext兩個域組成。

#define N 100
typedef struct EdgeNode{///邊表結點
    int adjvex;///鄰接點域,存儲該頂點對應的下標
    int weight;///權值
    EdgeNode *next;///鏈域,指示下一條邊或弧,指向下一個鄰接點
}EdgeNode;

typedef struct{///頂點結點
  char data;///頂點信息
  struct EdgeNode *firstedge;///指向第一個鄰接點,邊表頭指針
}AdjList[N];

typedef struct ALGraph{
   AdjList adj;
   int n,m;///頂點數和邊數
}ALGraph;

void GreatALGraph(ALGraph *G){///圖的鄰接表的結構
   EdgeNode *p,*q;
   cin>>G->n>>G->m;
   getchar();
   for(int i=0;i<G->n;i++){
    cin>>G->adj[i].data;///字符類型的頂點
    G->adj[i].firstedge=NULL;
   }
   getchar();
   for(int k=0;k<G->m;k++){
     char u,v;
     EdgeNode *p,*q;
    cin>>u>>v;
    int i,j;
    for(int s=0;s<G->n;s++){///找輸入頂點的下標
        if(u==G->adj[s].data)
                i=s;
        if(v==G->adj[s].data)
                j=s;
   }

   p=(EdgeNode *)malloc(sizeof(EdgeNode));
   p->adjvex=j;
   p->next=G->adj[i].firstedge;
   G->adj[i].firstedge=p;

   q=(EdgeNode *)malloc(sizeof(EdgeNode));///從鏈表頭插入
   q->adjvex=i;
   q->next=G->adj[j].firstedge;
   G->adj[j].firstedge=q;
 }
}

(2)鄰接表的邊集數組(鏈式前向星)實現,不用鏈表,但是類似鏈表的原理,非常容易實現;此方法一般用於數據類型爲int的頂點,較爲簡便。它主要由firstnext這兩個數組組成。first存儲的時第一條邊的編號(下標),next存儲下一條邊的編號(下標)。

前向星是一種特殊的邊集數組,邊集數組中的每一條邊按起點從小到大排序,如果起點相同按終點從大到小排序,並記錄下以某個點爲起點的所有邊在數組中的起始位置和存儲長度。

///通俗寫法。
struct EdgeNode
{
    int to;///這條邊到達的點
    int w;///邊權值
    int next;///下一條邊的編號
}e[N];

///memset(head,-1,sizeof(head));初始化head
void add(int i,int j,int w)///添加邊
{
    e[k].w = w;///邊權值
    e[k].to = j;///下一條邊的編號
    e[k].next = head[i];
    head[i] = k++;
}

void traver(int m)///遍歷邊集合
{
    int i,p;
    for(p=1;p<=m;p++)
    for(i=head[p];i!=-1;i=e[i].next)///遍歷的關鍵
    {
        printf("%d %d %d\n",p,e[i].to,e[i].w);
    }
}

我們可以理解一下《啊哈算法》中實現此方法(都是一樣的道理,比較簡易),存儲執行過程如下:

1)逐一輸入特定無序邊。

2)對於有多個鄰接點的頂點,先next存儲從輸入編號低到輸入編號高的,最後一條邊first存儲。比如頂點1有2個鄰接點2、3.。

它的遍歷順序是先鄰接點2後鄰接點3。

///核心僞代碼
for(int i<=1;i<=n;i++) first[i]=-1;///n爲頂點數,初始化數組first
for(int i=1;i<=m;i++){///邊數
    scanf("%d%d%d",&u[i],&v[i],&w[i])///輸入邊和權值
    next[i]=first[u[i]];///i是輸入時的順序,先找出下一邊,然後才能更新first,先進先考慮的原則
    first[u[i]]=i;///因爲當前頂點可能有多個鄰接點,更新first;否則first就是唯一的
}
///怎麼去遍歷這些邊呢
for(int i=1;i<=n;i++){
    k=first[i];
    while(k!=-1){///遇到-1,當前頂點的鄰接點遍歷結束
        printf("%d %d %d\n",u[k],v[k],w[k]);
        k=next[k];
    }
}

 

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