圖:
圖中涉及的定義:
有向圖:
頂點之間的相關連接具有方向性;
無向圖:
頂點之間相關連接沒有方向性;
完全圖:
若G是無向圖,則頂點數n和邊數e滿足:0<=e<=n(n-1)/2,當e=n(n-1)/2時,稱之為完全無向圖;若G是有向圖,則0<=e<=n(n-1);當e=n(n-1)時,稱之為完全有向圖;即此時圖中邊數最多;
頂點的度:
無向圖中定義為關聯頂點的邊數,有向圖中分入度和出度;度D(G)和邊數e滿足關系:2*e=D(G),即邊數的兩倍等於圖中度數和;
圖的實現:
鄰接矩陣實現圖:
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#define MAX_SIZE 10005
using namespace std;
int tmp[MAX_SIZE][MAX_SIZE]; //由於數組可能會比較大,定義在main里面可能會爆,所以先定義在全局;
typedef struct agraph* Graph;
typedef struct agraph
{
int n; //頂點數
int e; //邊數
int a[MAX_SIZE][MAX_SIZE];
}Agraph;
Graph GraphInit(int n,Graph G) // 對圖G的初始化;
{
G->n=n;
G->e=0;
return G;
}
void GraphAdd(int i,int j,int val,Graph G) //插入頂點之間的連接關系;
{
G->a[i][j]=val;G->e++;
}
void GraphDelete(int i,int j,Graph G) //刪除頂點之間的連接關系;
{
G->a[i][j]=0;G->e--;
}
int OutDegree(int i,Graph G)
{
int j,sum=0;
for(j=1;j<G->n;j++)
{
if(G->a[i][j]!=0)sum++;
}
return sum;
}
int InDegree(int i,Graph G)
{
int j,sum=0;
for(j=1;j<=G->n;j++)
{
if(G->a[j][i]!=0)sum++;
}
return sum;
}
void GraphOut(Graph G)
{
int i,j;
for(i=1;i<=G->n;i++)
{
for(j=1;j<=G->n;j++)
printf("%3d",G->a[i][j]);
printf("\n");
}
}
int main()
{
int i,j,n;
scanf("%d",&n);
Graph G=new Agraph;
G=GraphInit(n,G);
GraphAdd(1,3,6,G);
GraphAdd(1,5,55,G);
GraphAdd(2,3,12,G);
GraphAdd(2,4,50,G);
GraphAdd(3,4,3,G);
GraphAdd(1,2,47,G);
GraphAdd(3,6,21,G);
GraphAdd(3,2,31,G);
GraphAdd(4,1,19,G);
printf("%d %d\n",InDegree(2,G),OutDegree(2,G));
printf("\n\n\n");
printf("鄰接矩陣中圖G的關系輸出:\n");
GraphOut(G);
return 0;
}
輸出截圖:

鄰接表實現圖:
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
typedef struct anode* Node; //可以看成是建立一條邊;
typedef struct anode
{
int v; //邊的另一個頂點;
int val; //邊權;
Node next; //鄰接表指針;
}Anode;
typedef struct agraph* Graph;
typedef struct agraph
{
int n;
int edges;
Node *nodes;
}Agraph;
Node NewNext(int v,int val,Node next) //新建一個節點建立邊的連接關系;
{
Node x=new Anode;
x->v=v;
x->val=val;
x->next=next;
return x;
}
Graph GraphInit(int n,Graph G) //對圖G的初始化;
{
int i;
G->n=n;
G->edges=0;
G->nodes=(Node*)malloc((n+1)*sizeof(Node));
for(i=0;i<=n;i++)
{
G->nodes[i]=0;
}
return G;
}
void GraphAdd(int i,int j,int val,Graph G)
{
G->nodes[i]=NewNext(j,val,G->nodes[i]); //相當於i的鄰接頂點不斷增加時,其鄰接表從表頭加入,先加入的關系不斷后移;
G->edges++;
}
void GraphDelete(int i,int j,Graph G)
{
Node p,q;
p=G->nodes[i];
if(p->v==j)
{
G->nodes[i]=p->next;
free(p);
}
else
{
while(p&&p->next->v!=j)p=p->next;
if(p)
{
q=p->next;
p->next=q->next;
free(q);
}
}
G->edges--;
}
int InDegree(int i,Graph G)
{
int j,sum=0;
for(j=1;j<=G->n;j++)
{
/*判斷當前有向圖G中的邊(i,j)是否存在*/
Node p=G->nodes[i];
while(p&&p->v!=j)p=p->next;
if(p)sum++;
}
return sum;
}
int OutDegree(int i,Graph G)
{
Node p=G->nodes[i];
int sum=0;
while(p)
{
sum++;
p=p->next;
}
return sum;
}
void GraphOut(Graph G)
{
Node p;
int i;
for(i=1;i<=G->n;i++)
{
if(G->nodes[i])printf("%d的鄰接表:",i);
p=G->nodes[i];
while(p)
{
printf("%3d",p->v);
p=p->next;
}
printf("\n");
}
}
int main()
{
int i,j,n;
scanf("%d",&n);
Graph G=new Agraph;
G=GraphInit(n,G);
GraphAdd(1,3,6,G);
GraphAdd(1,5,55,G);
GraphAdd(2,3,12,G);
GraphAdd(2,4,50,G);
GraphAdd(3,4,3,G);
GraphAdd(1,2,47,G);
GraphAdd(3,6,21,G);
GraphAdd(3,2,31,G);
GraphAdd(4,1,19,G);
printf("%d %d\n",InDegree(2,G),OutDegree(2,G));
printf("\n\n\n");
printf("鄰接表中圖G的關系圖輸出:\n");
GraphOut(G);
return 0;
}
輸出截圖:

圖的遍歷:
圖的遍歷一般有兩種方法:
1、廣度優先搜索;2、深度優先搜索;
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<queue>
#define MAX_SIZE 10005
using namespace std;
int vis[10005]={0};
int cnt;
int tmp[MAX_SIZE][MAX_SIZE]; //由於數組可能會比較大,定義在main里面可能會爆,所以先定義在全局;
typedef struct agraph* Graph;
typedef struct agraph
{
int n; //頂點數
int e; //邊數
int a[MAX_SIZE][MAX_SIZE];
}Agraph;
Graph GraphInit(int n,Graph G) // 對圖G的初始化;
{
G->n=n;
G->e=0;
return G;
}
void GraphAdd(int i,int j,int val,Graph G) //插入頂點之間的連接關系;
{
G->a[i][j]=val;G->e++;
}
void GraphDelete(int i,int j,Graph G) //刪除頂點之間的連接關系;
{
G->a[i][j]=0;G->e--;
}
int OutDegree(int i,Graph G)
{
int j,sum=0;
for(j=1;j<G->n;j++)
{
if(G->a[i][j]!=0)sum++;
}
return sum;
}
int InDegree(int i,Graph G)
{
int j,sum=0;
for(j=1;j<=G->n;j++)
{
if(G->a[j][i]!=0)sum++;
}
return sum;
}
void GraphOut(Graph G)
{
int i,j;
for(i=1;i<=G->n;i++)
{
for(j=1;j<=G->n;j++)
printf("%3d",G->a[i][j]);
printf("\n");
}
}
/*圖的遍歷:廣度優先搜索*/
void bfs(Graph G,int i)
{
int j;
queue<int>q;
q.push(i); //從i頂點開始當前的搜索,即用頂點i初始化隊列q;
vis[i]=cnt++; //vis數組儲存的是編號為j的這個頂點被訪問時的序號;即第幾個被訪問到的頂點;
while(!q.empty())
{
i=q.front();
q.pop();
for(j=1;j<=G->n;j++) //即對每次的i的相鄰的未被訪問過的頂點進行訪問(符合廣度優先搜索的由近到遠搜索)
{
if((G->a[i][j])&&(vis[j]==0))
{
q.push(j);
vis[j]=cnt++;
}
}
}
}
void GraphSearchByBfs(Graph G)
{
int i;
cnt=1;
for(i=1;i<=G->n;i++)
{
if(vis[i]==0)
bfs(G,i); //n個頂點中可能存在多個聯通塊,對其做一次循環之后可以保證將圖中的所有的頂點都訪問到;
}
}
//上述鄰接矩陣實現的圖的廣度優先搜索遍歷因為在搜索時需要確保對所有的頂點都訪問,因此所需要的時間為O(n*n);
/*圖的遍歷:深度優先搜索*/
void dfs(Graph G,int i) //鄰接矩陣實現的圖的深搜
{
int j;
vis[i]=cnt++;
for(j=1;j<=G->n;j++)
{
if(G->a[i][j])
{
if(vis[j]==0)dfs(G,j); //即遞歸走到盡頭才停止這一次的搜索;
}
}
}
/*這段鄰接表深搜函數函數適用的是鄰接表實現的圖,在此無效*/
//void dfs2(Graph G,int i)
//{
// Node p;
// vis[i]=cnt++;
// for(p=G->nodes[i];p;p=p->next)
// {
// if(vis[p->v]==0)
// dfs(G,p->v);
// }
//}
void GraphSearchByDfs(Graph G)
{
int i;
cnt=1;
for(i=1;i<=G->n;i++)
{
if(vis[i]==0)
dfs(G,i); //n個頂點中可能存在多個聯通分支,對其做一次循環之后可以保證將圖中的所有的路徑都訪問到;
}
}
int main()
{
int i,j,n;
scanf("%d",&n);
Graph G=new Agraph;
G=GraphInit(n,G);
GraphAdd(1,3,6,G);
GraphAdd(1,5,55,G);
GraphAdd(2,3,12,G);
GraphAdd(2,4,50,G);
GraphAdd(3,4,3,G);
GraphAdd(1,2,47,G);
GraphAdd(3,6,21,G);
GraphAdd(3,2,31,G);
GraphAdd(4,1,19,G);
GraphAdd(7,3,12,G);
GraphAdd(2,9,1,G);
GraphAdd(3,8,47,G);
GraphAdd(7,9,99,G);
GraphAdd(7,10,11,G);
GraphAdd(9,8,23,G);
printf("%d %d\n",InDegree(2,G),OutDegree(2,G));
printf("\n\n\n");
printf("鄰接表中圖G的關系圖輸出:\n\n");
GraphOut(G);
// printf("\n\n");
// printf("對圖進行遍歷,廣度優先搜索輸出:\n\n");
// GraphSearchByBfs(G);
// for(i=1;i<=n;i++)
// printf("%d是第%d個遍歷到的頂點\n\n",i,vis[i]);
printf("\n\n");
printf("對圖進行遍歷,深度優先搜索輸出:\n\n");
GraphSearchByDfs(G);
for(i=1;i<=n;i++)
printf("%d是第%d個遍歷到的頂點\n\n",i,vis[i]);
return 0;
}
1、廣度優先搜索:
基本思想:從圖中的某個頂點i出發,在訪問了頂點i之后,接着就盡可能的先橫向搜索i的鄰接頂點,在一次訪問過i的各個未被訪問過的鄰接頂點之后,分別從這些鄰接頂點出發,遞歸以廣度優先方式搜索圖中的其他頂點,直至圖中的所有頂點都被訪問到為止;即以i為起始頂點,由近到遠(即路徑長度為1,2,3,.....)依次訪問和頂點i有路相通的頂點;
輸出結果:

2、深度優先搜索:
從i開始搜索,標記i為已訪問再遞歸的用深度優先搜索依次搜索i的每一個未被訪問的鄰接頂點,如果從i出發有路可達的頂點都已被訪問過,則從i開始的搜索過程結束;此時,如果圖中海油未被訪問的節點,則再任選一個並從該節點開始做新的搜索;直至圖中所有的節點都被訪問過為止;
輸出結果:

圖的實際問題應用:
最短路徑問題:1、單源最短路徑;2、所有頂點對之間的最短路徑;
單源最短路徑(Dijkstra算法/Bellman-Ford算法):
Dijktra(不能存在負權邊):
V是賦權有向圖,設置一個集合S並不斷作貪心算法擴充該集合,其中的頂點當且僅當源到該頂點的最短路徑已知;用dist[]數組記錄當前每個頂點所對應的最短特殊路徑長度(特殊路徑:從源到頂點u且中間只經過S中頂點的路徑稱為從源到u的特殊路徑)。該算法每次從V-S中取出具有最短路徑長度的頂點u,將其添加到S中,同時對數組dist作必要的修改,當S中包含了V中所有頂點,dist就記錄了從源到其他所有頂點的最短路徑長度;
Bellman-Ford:
對圖中除源頂點s外的任一頂點u,依次構造
從s到u的最短路徑長度序列dist1[u],dist2[u],...,dist n-1[u].其中dist i[u]表示從s到u的最多經過i條邊的最短路徑長度;若G中沒有從源可達的負權圈,則從s到u的最多有n-1條邊;因此,dist n-1[u]就是從s到u的最短路徑長度;
實現時:
用ub[]數組記錄頂點的最短路徑長度更細狀態,count[]數組記錄頂點的最短路徑長度更新次數,隊列que[]存儲最短路徑更新過的頂點,算法依次從que中取出待更新的頂點u,按照計算dist i[u]的遞歸式進行計算,一旦發現存在頂點k有count[k]>n,說明圖G中有一個從k出發的負權圈,算法終止,否則,當隊列que為空時,算法得到圖G的各頂點的最短路徑長度;
具體題目應用代碼:后續詳細復習補上;
所有頂點對之間的最短路徑(floyd算法):
Dijsktra * n:
每次以一個頂點為源,重復執行Dijkstra算法n次;時間復雜度為O(n* n *n);
Floyd:
設置一個n*n的矩陣c,初始時c[i][j]=a[i][j];a是權值數組;在矩陣c上做n次迭代,經k次迭代后,c[i][j]值是從頂點i到頂點j且中間不經過編號大於k的頂點的最短路徑長度。做迭代時,公式:c[i][j] = min{ a[i][j] , c[i][k]+a[k][j] };
第十一次作業:
1、(給定一個圖,求圖中所有頂點對之間的最短路徑長度,用Floyd算法實現)
#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 0x3f3f3f3f
#define MAX 405
using namespace std;
int pre[MAX][MAX],dist[MAX][MAX];
void Floyd(int n,int gra[][MAX])
{
int i,j,k;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
dist[i][j]=gra[i][j];pre[i][j]=j;
}
}
for(k=1;k<=n;k++)
{
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(dist[i][k]!=INF&&dist[k][j]!=INF&&dist[i][k]+dist[k][j]<dist[i][j])
{
dist[i][j]=dist[i][k]+dist[k][j];
pre[i][j]=pre[i][k];
}
}
}
}
}
int gra[MAX][MAX];
int main()
{
int n,m,i,j,k;
int u,v,val,tmp,que;
scanf("%d%d",&n,&m);
for(i=0;i<=n;i++)
{
for(j=0;j<=n;j++)
{
gra[i][j]=(i==j?0:INF);
}
}
for(i=0;i<m;i++)
{
scanf("%d%d%d",&u,&v,&val);
gra[u][v]=gra[v][u]=val;
}
Floyd(n,gra);
scanf("%d",&que);
for(i=0;i<que;i++)
{
scanf("%d%d",&u,&v);
if(dist[u][v]!=INF)
printf("%d\n",dist[u][v]);
else printf("-1\n");
}
return 0;
}
2、(判斷圖中是否存在負權圈--深搜做法:但是要會開類似三維數組vector<pair<int,int>>vv[2005];)
#include<stdio.h>
#include<vector>
#include<memory.h>
using namespace std;
bool vis[2005];
vector<pair<int,int> >vv[2005];
int n,m,s;
bool flag=false;
int dis[2005];
void dfs(int pos)
{
if(flag) return;
vis[pos]=true;
vector<pair<int,int> >::iterator it;
for(it=vv[pos].begin();it!=vv[pos].end();it++)
{ int to=it->first;
if(dis[to]>dis[pos]+it->second)
{
dis[to]=dis[pos]+it->second;
if(vis[to]) {
flag=true;return;}
else
{
dfs(to);
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
int i,j;
memset(vis,false,sizeof(vis));
memset(dis,0,sizeof(dis));
for(i=1;i<=n;i++) vv[i].clear();
for(i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
vv[u].push_back(make_pair(v,w));
}
scanf("%d",&s);
dis[s]=0;
dfs(s);
if(flag) printf("EL PSY CONGROO");
else printf("ttt");
}
