一、圖的定義
圖(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)頂點表的各個結點由data和firstedge兩個域組成。
2)邊表結點由adjvex和next兩個域組成。
#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的頂點,較爲簡便。它主要由first和next這兩個數組組成。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];
}
}