1.图:图G是由两个集合V(G)和E(G)组成的,记为G=(V,E);其中V(G)是顶点的非空有限集;E(G)是边的有限集合,边是顶点的有序对或无序对。
有向图中E(G)是有向边的有限集合,记为 < v,w >
无向图中V(G)是无向边的有限集合,记为(v,w)
性质:无向图最多有n(n-1)/2条边;有向图最多有n(n-1)条边
n个顶点的有向图有n(n-1)条边,则此图称为有向完全图
n个定点的无向图有n(n-1)/2条边,则此图称为无向完全图
2.
邻接点:若(vi,vj)是一条无向边,则成vi和vj互为邻接点
关联:vi和vj互为邻接点,称边(vi,vj)关联于顶点vi,vj
顶点的度 :一个顶点v的度是与它相关联的边的条数。记作TD(v)。
入度:顶点 v 的入度是以 v 为终点的有向边的条数, 记作 ID(v)。
出度:顶点 v 的出度是以 v 为始点的有向边的条数, 记作 OD(v)。
边数与度的关系:
- ∑TD(vi)=2e ( i=1, 2, …, n ,e为图的边数)
性质:有向图各顶点入度之和等于出度之和,等于边数
子图——如果图G(V,E)和图G’(V’,E’),满足:V’属于V,E’属于E, 则称G’为G的子图
路径:在图 G=(V, E) 中, 若从顶点 vi 出发, 沿一些边经过一些顶点 vp1, vp2, …, vpm,到达顶点vj。则称顶点序列 (vi vp1 vp2 … vpm vj) 为从顶点vi 到顶点 vj 的路径。它经过的边(vi, vp1)、(vp1, vp2)、…、(vpm, vj) 应是属于E的边。
路径长度:非带权图的路径长度是指此路径上边的条数。带权图的路径长度是指路径上各边的权之和。
简单路径 :若路径上各顶点 v1,v2,…,vm 均不互相重复, 则称这样的路径为简单路径。
回路:若路径上第一个顶点 v1 与最后一个顶点vm 重合, 则称这样的路径为回路或环。
简单回路:在一个回路中,若除第一个与最后一个顶点外,其余顶点不重复出现的回路称为简单回路(简单环)。
连通图 :在无向图中, 若从顶点v1到顶点v2有路径, 则称顶点v1与v2是连通的。如果图中任意一对顶点都是连通的, 则称此图是连通图。
连通分量:非连通图的极大连通子图叫做连通分量。
“极大”的含义:指的是对子图再增加图G中的其它顶点,子图就不再连通。
考点:
(1)n个顶点的无向图最少需要多少条边,才能保证连通。答案:n-1条
(2)要保证n个顶点的无向图G在任何情况下都是连通的,至少需要多少条边?
答案:(n-1)(n-2)/2+1
强连通图与强连通分量:在有向图中, 若对于每一对顶点vi和vj, 都存在一条从vi到vj和从vj到vi的路径, 则称此图是强连通图。非强连通图的极大强连通子图叫做强连通分量。
考点:n个顶点的有向图是强连通图,至少需要多少条边?答案:n条(回路)
权:与图的边或弧相关的数叫权
网(络):带权的图叫网
3.一个连通图的生成树是一个极小连通子图,它含有图中全部顶点,但只有足以构成一棵树的n-1条边。
一棵有n个顶点的生成树有且仅有n-1条边。
——>
4.无回路的图称为树或自由树或无根树
有向树:只有一个顶点的入度为0,其余顶点的入度为1的有向图。(有向树是弱连通的)
5.图的存储结构:
- 邻接矩阵(数组表示)
图的任意两个结点(顶点)之间都可能有关系,因此用二维数组来表示;
(1)二维数组中元素aij= 0表示vi和vj不邻接; aij = 1表示vi和vj邻接。
(2)对于网,可以用aij表示权。若vi和vj不邻接,则可以用无限大∞表示权。
typedef int EdgeType;
typedef char VertexType;
typedef struct{
VertexType vexs[MaxNode];
EdgeType arcs[MaxNode][MaxNode];
int vexnum,arcnum;
}MGraph;
int find(MGraph G,VertexType c){
for(int i=0;i<G.vexnum;i++){
if(G.vexs[i]==c){
return i;
}
}
return -1;
}
void CreateGraph(MGraph &G){
char v1,v2;
scanf("%d%d",&G.vexnum,&G.arcnum);
getchar();
for(int i=0;i<G.vexnum;i++){
scanf("%c",&G.vexs[i]);
getchar();
}
memset(G.arcs,0,sizeof(G.arcs));
for(int i=0;i<G.arcnum;i++){
scanf("%c",&v1);
getchar();
scanf("%c",&v2);
getchar();
int k=find(G,v1);
int t=find(G,v2);
G.arcs[k][t]=G.arcs[t][k]=1;
}
for(int i=0;i<G.vexnum;i++){
for(int j=0;j<G.vexnum;j++)
printf("%d ",G.arcs[i][j]);
printf("\n");
}
}
对于无向图:
邻接矩阵一定是一个对称矩阵,可压缩存储;有n个顶点的无向图需存储空间为n(n+1)/2
行(列)非零元素个数,表示度
对于有向图:
矩阵不一定是一个对称矩阵,有n个顶点的有向图需存储空间为n²
行非零元素个数,表示出度
列非零元素个数,表示入度
邻接矩阵的存储空间只和顶点个数有关,和边数无关
邻接矩阵存储优点:
容易实现图的操作,如:求某顶点的度、判断顶点之间是否有边(弧)、找顶点的邻接点等等。
邻接矩阵存储缺点:
n个顶点需要n*n个单元存储边(弧);空间效率为O(n2)。 对稀疏图而言尤其浪费空间
- 邻接表
对图中每个顶点建立一个邻接关系的单链表,并将其表头指针用向量(一维数组)存储,该结构称为邻接表。
无向图的第i个链表将图中与顶点vi相邻接的所有顶点链接起来。
有向图的第i个链表,链接了以顶点vi为弧尾(射出)的所有顶点。
typedef int EdgeType;
typedef int VertexType;
typedef struct ArcNode{ //边(弧)结点的类型定义
int adjvex; //边(弧)的另一顶点的在数组中的位置
ArcNode *nextarc;//指向下一边(弧)结点的指针
}ArcNode,*ArcLink;
typedef struct Vnode{//顶点结点及其数组的类型定义
VertexType data; //顶点信息
ArcNode * firstarc; //指向关联该顶点的边(弧)链表
} Vnode, AdjList[MaxNode];
typedef struct {
AdjList vertices;
int vexnum, arcnum; //图的当前顶点数和弧数
} ALGraph;
void CreateAlGraph(ALGraph &G){
int i,j,k;
ArcLink s;
scanf("%d%d",&G.vexnum,&G.arcnum);
for(i=0;i<G.vexnum;i++){
scanf("%d",&G.vertices[i].data);
G.vertices[i].firstarc=NULL;
}
for(i=0;i<G.arcnum;i++){
scanf("%d%d",&j,&k);
s=(ArcLink)malloc(sizeof(ArcNode));
s->adjvex=k;
s->nextarc=G.vertices[j].firstarc;
G.vertices[j].firstarc=s;
}
}
- 有向图的十字链表表示法
typedef struct ArcBox{
int tailvex,headvex;//弧头和弧尾在表头数组中的位置
struct ArcBox *hlink;//指向弧头相同的下一条弧
struct ArcBox *tlink;//指向弧尾相同的下一条弧
}ArcBox,*Arclink;//弧结点
typedef struct VexNode{
int data;
Arclink fistin;//指向以该顶点为弧头的第一个弧结点
Arclink fistout;//指向以该顶点为弧尾的第一个弧结点
}VexNode;//顶点结点
typedef struct{
VexNode xlist[MaxNode];
int vexnum,arcnum;
}OLGraph;
void CreateOLGraph(OLGraph &G){
Arclink p;
int k,j;
scanf("%d%d",&G.vexnum,&G.arcnum);
for(int i=0;i<G.vexnum;i++){
scanf("%d",&G.xlist[i].data);
G.xlist[i].firstin=NULL;
G.xlist[i].firstout=NULL;
}
int i,j;
for(int k=0;k<G.arcnum;k++){
scanf("%d%d",&i,&j);
p=(Arclink)malloc(sizeof(ArcBox));
p->headvex=j;
p->tailvex=i;
p->hlink=G.xlist[j].firstin;
p->tlink=G.xlist[i].firstout;
G.xlist[i].firstout=p;
G.xlist[j].firstin=p;
}
}
6.图的遍历
图的遍历是从图中某个顶点出发,按照某种方式系统地访问图中的所有顶点,使每个顶点仅被访问一次。
图的遍历通常有两种方法:深度优先搜索和广度优先搜索。它们对有向图和无向图都适用。
深度优先遍历(DFS)
typedef struct ArcNode
{
int adjvex;
struct ArcNode * nextarc;
}ArcNode,*ArcLink;
typedef struct Vnode
{
int data;
int isfirst;
ArcLink firstarc;
}Vnode,AdjList[MaxSize];
typedef struct
{
AdjList vertices;
int vexnum,arcnum;
}ALGraph;
void DFS(ALGraph &G,int v)
{
ArcLink p;
G.vertices[v].isfirst=1;
printf("%d ",G.vertices[v].data);
p=G.vertices[v].firstarc;
while(p){
if(G.vertices[p->adjvex].isfirst==0)
DFS(G,p->adjvex);
p=p->nextarc;
}
}
void WholeDFS(ALGraph G)
{
for(int i=0;i<G.vexnum;i++){
if(G.vertices[i].isfirst==0){
DFS(G,i);
}
}
printf("\n");
}
广度优先遍历(BFS)
void BFS(ALGraph &G)
{
ArcLink p;
bool isfirst[MaxSize];
SqQueue Q;
int t;
InitQueue(Q);
for(int i=0;i<G.vexnum;i++)
isfirst[i]=false;
for(int i=0;i<G.vexnum;i++){
if(!isfirst[i]){
printf("%d ",G.vertices[i].data);
EnQueue(Q,i);
isfirst[i]=true;
while(!Empty(Q)){
DnQueue(Q,t);
p=G.vertices[t].firstarc;
while(p){
if(!isfirst[p->adjvex]){
printf("%d ",G.vertices[p->adjvex].data);
isfirst[p->adjvex]=true;
EnQueue(Q,p->adjvex);
}
p=p->nextarc;
}
}
}
}
}
Prime算法从联通网中找最小生成树(邻接矩阵)
typedef struct
{
int weight[MaxSize][MaxSize];
int vexnum,arcnum;
}ArlGraph;
typedef struct
{
int adjvex;//边依附于U中顶点
int lowcost;//该边的权值
}closedge[MaxSize];
typedef struct
{
int vex1,vex2;
int weight;
}MSTEdge,*MSTLink;//存储最小生成树的边
void Prime(ArlGraph &G,int u,MSTLink &TE)
{
closedge ce;
int min,t;
for(int i=0;i<G.vexnum;i++){
ce[i].adjvex=u;
ce[i].lowcost=G.weight[i][u];
}
ce[u].lowcost=0;
TE=(MSTLink)malloc(sizeof(MSTEdge)*(G.vexnum-1));
for(int j=0;j<G.vexnum-1;j++){
min=MaxSize;
for(int k=0;k<G.vexnum;k++){
if(ce[k].lowcost!=0&&ce[k].lowcost<min){
min=ce[k].lowcost;
t=k;
}
}
TE[j].vex1=ce[t].adjvex;
TE[j].vex2=t;
TE[j].weight=min;
ce[t].lowcost=0;
for(int i=0;i<G.vexnum;i++){
if(G.weight[i][t]<ce[i].lowcost){
ce[i].lowcost=G.weight[i][t];
ce[i].adjvex=t;
}
}
}
}
拓扑排序(检验图中是否存在环,若输出个数与顶点个数相同则无环)
邻接表实现,可用栈,可用队列
int Toplogic_Sort(ALGraph G)
{
SqStack S;S.top=-1;
int t,count=0;
ArcLink p;
int indegree[MaxSize];
getIndegree(G,indegree);
for(int i=0;i<G.vexnum;i++)
if(indegree[i]==0)
S.data[++S.top]=i;
while(S.top!=-1){
t=S.data[S.top--];
printf("%d ",G.vertices[t].data);
count++;
p=G.vertices[t].firstarc;
while(p){
indegree[p->adjvex]--;
if(indegree[p->adjvex]==0)
S.data[++S.top]=p->adjvex;
p=p->nextarc;
}
}
if(count<G.vexnum)
return -1;
return 0;
}
关键路径
int ve[MaxSize],vl[MaxSize];
int ToplogicOrder(ALGraph G,SqStack &T)
{
SqStack S;S.top=-1;
T.top=-1;
int t,count=0;
ArcLink s;
int indegree[MaxSize];
getIndegree(G,indegree);
for(int i=0;i<G.vexnum;i++){
if(indegree[i]==0)
Push(S,i);
ve[i]=0;
}
while(!IsEmpty(S)){
Pop(S,t);
printf("%d ",G.vertices[t].data);
Push(T,t);
count++;
s=G.vertices[t].firstarc;
while(s){
if(!--indegree[s->adjvex])
Push(S,s->adjvex);
if(ve[t]+s->weight>ve[s->adjvex])
ve[s->adjvex]=ve[t]+s->weight;
s=s->nextarc;
}
}
if(count<G.vexnum)
return -1;
return 0;
}
int criticalPath(ALGraph &G,SqStack &T)
{
ArcLink p;
int t,ee,el;
char tag;
if(ToplogicOrder(G,T)==-1)return -1;
for(int i=0;i<G.vexnum;i++)
vl[i]=ve[G.vexnum-1];
while(!IsEmpty(T)){
Pop(T,t);
p=G.vertices[t].firstarc;
while(p){
if(vl[p->adjvex]-p->weight<vl[t])
vl[t]=vl[p->adjvex]-p->weight;
p=p->nextarc;
}
}
for(int i=0;i<G.vexnum;i++){
printf("\n%d %d",ve[i],vl[i]);
}
for(int j=0;j<G.vexnum;j++){
for(p=G.vertices[j].firstarc;p;p=p->nextarc){
ee=ve[j];el=vl[p->adjvex]-p->weight;
tag=(ee==el)?'*':'X';
printf("\n%d-%d=%c",j,p->adjvex,tag);
}
}
return 0;
}
最短路径Dijkstra
int D[MaxSize];//从vs到vi的当前最短路径
int P[MaxSize];//从vs到其他顶点的最短路径
bool final[MaxSize];//标识一个顶点是否已加入S中
void Dijkstra(MGraph G,int v0)
{
int min,v;
for(int i=0;i<G.vexnum;i++){
P[i]=v0;final[i]=false;
D[i]=G.arcs[v0][i];
D[v0]=0;final[v0]=true;
}
for(int i=1;i<G.vexnum;i++){
min=MaxSize;
for(int j=0;j<G.vexnum;j++){
if(!final[j]&&D[j]<min){
min=D[j];v=j;
}
}
final[v]=true;
for(int j=0;j<G.vexnum;j++){
if(!final[j]&&(D[v]+G.arcs[v][j]<D[j])){
D[j]=D[v]+G.arcs[v][j];
P[j]=v;
}
}
}
}
void ShowPath(int P[],int v0,int vt)
{
if(v0==vt) return;
else{
printf("%d ",P[vt]);
ShowPath(P,v0,P[vt]);
}
}