图不同于树,树是一种具有层次关系的结构,而在图中的任意两个顶点都可能有关系。
图(Graph),顶点(Vertex),边(Edge)
因此图可以表示为: G=(V,E),每一条边是一顶点对(V,W)。
图和线性表,树的比较:
1.线性表中的数据叫元素,树中的数据叫结点,图中的数据叫顶点
2.线性表中没有元素叫空表,树中没有结点叫空树,图中必须至少有一个顶点,但是边集可以为空
图的相关术语:
1.无向图(Undirected Graphs)
2.有向图(Directed Graphs)
3.简单图(Simple Graph):无重边,无自回路边
4.邻接点(Adjacent Vertices)
5.路径,简单路径,回路,无环图
6.完全图:任意两个顶点都有边相连
7.顶点的度,出度,入度
8.稠密图(Dense Graph),稀疏图(Sparse Graph)
9.权,网图:通常情况下,网图简称图
10.子图(Subgraph)
11.
在无向图中:
连通图,连通分量(包括子图,连通,极大顶点数,极大边数)
12:
在有向图中:
强连通图,强连通分量
13.生成树(Spanning Tree)
14.生成森林(Spanning Forest)
图的操作集:
- Graph CreateGraph(int VertexNum);
- void InsertEdge(Graph G, Edge E);
- void DeleteEdge(Graph G, Edge E);
- void IsEmpty(Graph G);
- void DFS(Graph G, Vertex V, (* Visit)(Vertex));
- void BFS(Graph G, Vertex V, (*Visit)(Vertex));
图的存储结构:
邻接矩阵:(Adjacency Matrix)
在无向图中,用邻接矩阵可能会造成空间的浪费,所以我们用一个一维数组来存储,长度为N*(N+1)/2,那么Gij在数组中的对应的下标为:i*(i+1)/2+j
用邻接矩阵存储图的时候,除了用一组二维数组存储表示顶点间相邻关系的邻接矩阵外,还需要一个一维数组来存储顶点信息,另外还有图的顶点数和边数
#define MaxVertexNum 100
#define INFINITY 65535
typedef int Vertex;
typedef int WeightType;
typedef char DataType;
typedef struct GNode* PtrToGNode;
struct GNode
{
int Nv;
int Ne;
WeightType G[MaxVertexNum][MaxVertexNum];
DataType Data[MaxvertexNum];
};
typedef PtrToGNode MGraph;
无向网图的初始化程序:
//边的定义
typedef struct ENode* PtrToENode;
struct ENode
{
Vertex V1,V2;
WeightType Weight;
};
typedef PtrToENode Edge;
MGraph CreateGraph(int VertexNum)
{
Vertex V, W;
MGraph Graph;
Graph =(MGraph)malloc(sizeof(struct GNode));
Graph->Nv = VertexNum;
Graph->Ne = 0;
//初始化邻接矩阵
for(V=0;V<Graph->Nv;W++)
for(W=0;W<Graph->Nv;W++)
Graph->G[V][W]=INFINITY;
return Graph;
}
void InsertEdge(MGraph Graph, Edge E)
{
Graph->G[E->V1]{E->V2] = E->Weight;
Graph->G[E->V2][E->V1] = E->Weight;
}
MGraph BuildGraph()
{
MGraph Graph;
Edge E;
Vertex V;
int Nv,i;
scanf("%d",&Nv);
Graph = CreateGraph(Nv);
scanf("%d",&(Graph->Ne));
if(Graph->Ne!=0)
{
E = (Edge)malloc(sizeof(struct ENode));
for(i = 0; i<Graph->Ne;i++)
{
scanf("%d %d %d",&E->V1,&E->V2,&E->Weight);
InsertEdge(Graph, E);
}
}
for(V=0;V<Graph->Nv;V++)
scanf("%c",&(Graph->Data[V]));
return Graph;
}
对于稠密图 ,这是一个很好的存储方法,但是对于稀疏图就会造成空间的浪费。
邻接表:(Adjacency Lists)
邻接表是图的一种顺序存储和链式存储相结合的存储方式
邻接表中有两种结点结构:一是顶点表的结点结构,由数据域和指向第一条邻接边的指针域构成。二是边表结点,由临界点域和指向下一条邻接边的指针域构成
邻接表结构的表示和声明:
#define MaxVertexNum 100
typedef int Vertex;
typedef int WeightType;
typedef char DataType;
//边的定义
typedef struct ENode* PtrToENode;
struct ENode
{
Vertex V1,V2;
WeightType Weight;
}
typedef PtrToENode Edge;
//邻接点的定义
typedef struct AdjNode* PtrToAdjVNode;
struct AdjVNode
{
Vertex Adjv;
WeightType Weight;
PrtToAdjVNode Next;
};
//顶点表头结点的定义
typedef struct Vnode
{
PtrToAdjVNode FirstEdge;
DataType Data;
}AdjList[MaxVertexNum];
//图结点的定义
typedef struct GNode* PtrToGNode;
struct GNode
{
int Nv;
int Ne;
AdjList G;
};
typedef PtrToGNode LGraph;
邻接表无向图的初始化:
LGraph CreateGraph(int VertexNum)
{
Vertex V;
LGraph Graph;
Graph=(LGraph)malloc(sizeof(struct GNode));
Graph->Nv = VertexNum;
Graph->Ne = 0;
for(v = 0;V<Graph->Nv;V++)
Graph->G[V].FirstGraph = NULL;
return Graph;
}
void InsertEdge(LGraph Graph, Edge E)
{
PtrToAdjVNode NewNode;
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->Adjv = E->V2;
NewNode->Weight = E->Weight;
NewNode->next = Graph->G[E->V1].FirstEdge;
Graph->G[E->V1].FirstEdge = NewNode;
//如果是无向图还需要插入边<V2,V1>
NewNode = (PtrTiAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->Adjv = E->V1;
NewNode->Weight = E->Weight;
NewNode->next = Graph->G[E->V2].FirstEdge;
Graph->G[E->V2].FirstEdge = NewNode;
}
LGraph BuildGraph()
{
LGraph Graph;
Edge E;
Vertex V;
int Nv,i;
scanf("%d",&Nv);
Graph = CreateGraph(Nv);
scanf("%d",&(Graph->Ne));
if(Graph->Ne!=0)
{
E =(Edge)malloc(sizeof(struct ENode));
for(i=0; i<Graph->Ne;i++)
{
scanf("%d %d %d", &E->V1, &E->V2,&E->Weight);
InsertEdge(Graph,E);
}
}
for(V=0;V<Graph->Nv;V++)
scanf("%c",&(Graph->G[V].Data));
return Graph;
}
图的遍历:
深度优先搜索:(Depth First Search,DFS)
类似于树的先序遍历,是先序遍历的推广,假设所有的结点都未被访问,从某个顶点开始一直访问接下来未被访问的邻接点,如果到底了,则进行递归退回操作,检查之前是否有没有被访问的邻接点。
邻接表存储图的深度优先遍历:
void Visit(Vertex V)
{
printf("正在访问顶点%d\n",V);
}
//设置一个全局变量Visited[],来检查结点是否被访问,初始化为false
void DFS(LGraph Graph, Vertex V, void(*Visit)(Vertex))
{
PtrToAdjVNode W;
Visit(V);
Visited[V] = true;
for(W = Graph->G[V].FirstEdge;W;W=W->next)
if(!Visited[W->AdjV])
DFS(Graph, W->AdjV,Visit);
}
遍历图的过程实质上是对每个顶点查找其邻接点的过程。其耗费的时间取决于所采用的存储结构。当用邻接矩阵作为图的存储结构时,查找所有顶点的邻接点所需时间为(顶点数的平方),而以邻接表作为存储结构的时候,找邻接点的所需时间为(边的数量),所以当以邻接表作为存储结构,深度优先遍历的时间复杂度为(边的数量加上顶点的数量)
广度优先搜索:(Breadth First Search, BFS)
类似于树的层序遍历过程
通俗的说,广度优先搜索就像一个圆规,来画圆一样,一圈一圈的搜索,一圈一圈的扩大。
邻接矩阵存储图的广度优先遍历:
bool IsEdge(MGraph Graph, Vertex V, Vertex W)
{
return Graph->G[V][W]<INFINITY?true:false;
}
void BFS(MGraph Graph, Vertex S, void(*Visit)(Vertex))
{
Queue Q;
Vertex V,W;
Q= CreateQueue(MaxSize);
Visit(S);
Visited[S] = true;
AddQ(Q,S);
while(!IsEmpty(Q))
{
V=DeleteQ(Q);
for(W =0; W<Graph->Nv;W++)
if(!Visited[W]&&IsEdge(Graph,V,W))
{
Visit(W);
Visited[W] = true;
AddQ(Q,W);
}
}
}
最短路径:
单源最短路径:
这是一个按照距离递增的顺序逐步寻找源点到各个顶点最短路径的过程。
分别有两个数组dist[], path[]来存储源点到其余各顶点的“当前最短距离”和相应的最短路径邻接于哪个顶点。
在无权图中,dist和path数组首先初始化为-1
无权图的单源最短路径算法 :
//邻接表存储
//dist[]和path[]全部初始化为-1
void Unweighted(LGraph Graph, int dist[], int path[], Vertex S)
{
Queue Q;
Vertex V;
PtrToAdjNode W;
Q = CreateQueue(Graph->Nv);
dist[S] = 0;
AddQ(Q,S);
while(!IsEmpty(Q))
{
V = Delete(Q);
for(W =Graph->G[V].FirstEdge;W;W = W->Next)
if(dist[W->AdjV]==-1)
{
dist[W->AdjV]=dist[V]+1;
path[W->AdjV]=V;
AddQ(Q,W->AdjV);
}
}
}
有权图的最短路径算法:
Dijkstra算法(迪杰斯特拉算法):
//邻接矩阵存储
#define INFINITY 65535
bool Dijkstra(MGraph Graph, int dist[], int path[], Vertex S)
{
int collected[MaxVertexNum];
Vertex V,W;
//初始化,默认邻接矩阵中没有边的邻接点为INFINITY,有边的邻接点,存储权值
for(V = 0; V<Graph->Nv;V++)
{
dist[V] = Graph->G[S][V];
if(dist[V]<INFINITY)
path[V]=S;
else
path[V]=-1;
collected[V] = false;
}
dist[S] = 0;//源点到源点的距离为0
collected[S] = true;
while(1)
{
//V=未被收录顶点中dist最小者
V = FindMinDist(Graph,dist,collected);
if(V==ERROR) break;
collected[V] = true;
for(W=0;W<Graph->Nv;W++)
{
if(collected[W]==false&&Graph->G[V][W]<INFINITY)
if(Graph->G[V][W]<0)//若有负边情况
return false;
if(dist[V]+Graph->G[V][W]<dist[W])
{
dist[W] = dist[V]+Graph->G[V][W];
path[W]=V;
}
}
}
return true;
}
Vertex FindMinDist(MGraph Graph,int dist[], int collected[])
{
Vertex MinV,V;
int MinDist = INFINITY;
for(V=0;V<Graph->Nv;V++)
{
if(collected[V]==false&&dist[V]<MinDist)
{
MinDist = dist[V];
MinV = V;
}
}
if(MinDist<INFINITY) return MinV;
else return ERROR;
}
对于稠密图来说,时间复杂度为O(|V|^2)
对于稀疏图来说可以改进为用邻接表作为存储结构,用优先队列(最小堆)的DeleteMin操作,查找最小值的时间为O(log|V|),时间界可以改为O(|E|log|V|)
多源最短路径:
如果用Dijkstra方法的话,时间复杂度会到O(|V|^3)
Floyd算法(弗洛伊德算法)
虽然时间复杂度为O(|V|^3),但是实际运行效率更高
bool Floyd(MGraph Graph, WeightType D[][MaxVertexNum],Vertex path[][MaxVertexNum])
{
Vertex i,j,k;
//初始化
for(i=0;i<Graph->Nv;i++)
for(j=0;j<Graph->Nv;j++)
{
D[i][j] = Graph->G[i][j];
path[i][j] = -1;
}
for(k=0;k<Graph->Nv;k++)
for(i=0;i<Graph->Nv;i++)
for(j=0;j<Graph->Nv;j++)
if(D[i][k]+D[k][j]<D[i][j])
{
D[i][j]=D[i][k]+D[k][j];
if(i==j&&D[i][j]<0)
return false;
path[i][j]=k;
}
return true;
}
最小生成树:
Prim算法:
类似与Dijkstra算法,但是Prim算法是收入和构造树有关的权值最小的边
设置辅助数组dist[ ], parent[ ],如果顶点属于构造树中,那么dist[V] = 0
dist数组用来存储最短路径,parent用来存储结点的父节点
用邻接表存储图(稀疏表)
#define ERROE -1
int Prim(MGraph Graph, LGraph MST)
{
int dist[MaxVertexNum], TotalWeight;
int parent[MaxVertexNum],V,W;
int VCount;
Edge E;
//初始化,默认初始点的下标为0
for(V=0;V<Graph->Nv;V++)
{
. //这里假设若V到W没有直接的边,那么Graph->G[V][W]定义为INFINITY
dist[V] = Graph->G[0][V];
parent[V] = 0;//暂且定义所有顶点的父节点都是初始点0
}
TotalWeight= 0;
Vcount = 0;
//创建一个没有边,只有顶点的邻接表
MST = CreateGraph(Graph->Nv);
E = malloc(sizeof(struct ENode));
//初始化点0进入最小生成树MST
dist[0] = 0;
VCount++;
parent[0] = -1;
while(1)
{
V = FindMinDist(Graph, dist);
if(V==ERROE)
break;
E->V1 = parent[V];
E->V2 = V;
E->Weight = dist[V];
InsertEdge(MST,E);
TotalWeight +=dist[V];
dist[V] = 0;
VCount++;
for(W=0;W<Graph->Nv;W++)
//如果W是V的邻接点,而且没有被收录都最小生成树
if(dist[W]!=0&&Graph->G[V][W]<INFINITY)
//更新路径
if(Graph->G[V][W]<dist[W])
{
dist[W]=Graph->G[V][W];
parent[W] = V;
}
}
if(VCount<Graph->Nv)
TotalWeight = ERROR;
return TotalWeight;
}
int FindMinDist(MGraph Graph, int dist[])
{
int MinV,V;
int MinDist = INFINITY;
for(V=0;V<Graph->Nv;V++)
{
if(dist[V]!=0&&dist[V]<MinDist)
{
MinDist= dist[V];
MinV = V;
}
}
if(MinDist<INFINITY)
return MinV;
else return ERROR;
}