一、图的定义和术语
(1)无向图、有向图
(2)顶点、边、弧、弧头、弧尾:数据元素vi称为顶点(vertex );(vi, vj)表示在顶点vi和顶点vj之间有一条直接连线。 在无向图中,则称这条连线为边;边用顶点的无序偶对(vi, vj)来表示,称顶点vi和顶点vj互为邻接点,边(vi, vj)依附于顶点vi与顶点vj; 在有向图中,一般称这条连线为弧。弧用顶点的有序偶对<vi, vj>来表示,有序偶对的第一个结点vi被称为始点(或弧尾),在图中就是不带箭头的一端;有序偶对的第二个结点vj被称为终点(或弧头),在图中就是带箭头的一端。
(3)无向完全图:无向图中任意两点都有一条边相连接,n个顶点,n(n-1)/2条边
(2)有向完全图:任意两顶点之间都有方向互为相反的两条弧相连,n个顶点,n(n-1)条边
(4)稠密图,稀疏图:边数多少
(5)顶点的度、入度、出度
(6)边的权、网:与边有关的数据信息为权
(7)路径、路径长度:路径上边的数目为长度
(8)回路、简单路径、简单回路:序列中顶点不重复出现的路径为简单路径。
(9)子图
(10)连通的、连通图、连通分量:在无向图中
(11)强连通图、强连通分量:有向图中
(12)生成树:假设一个连通图有 n 个顶点和 e 条边, 其中 n-1 条边和 n 个顶点构成一个极小连通子图, 称该极小连通子图为此连通图的生成树。
(13)生成森林:对非连通图,称各个连通分量生成树的集合为此非连通图的生成森林。
二、图的存储结构
1.领接矩阵(数组)
(1)图的数组(邻接矩阵)存储表示
#define INFINITY INT_MAX //最大值 ∞
#define MAX_VERTEX_NUM 20 //最大顶点个数
typedef enum { DG,DN,UDG,UDN} GraphKind;
typedef struct ArcCell{
VRType adj; //弧是否相通,或权值
InfoType *info; //弧的相关信息
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct {
VertexType vexs[MAX_VERTEX_NUM]; //顶点向量
AdjMatrix arcs; //邻接矩阵
int vexnum, arcnum; //图的当前顶点和弧数
GraphKind kind; //图的种类标志
}Mgraph;
在邻接矩阵的存储结构上创建图
Status CreateGraph( MGraph &G )
{ scanf(&G.kind); // 自定义输入函数,读入一个随机值
switch (G.kind)
{
case DG: return CreateDG(G); // 构造有向图G
case DN: return CreateDN(G); // 构造有向网G
case UDG: return CreateUDG(G); // 构造无向图G
case UDN: return CreateUDN(G); // 构造无向网G
default : return ERROR;
}
} // CreateGraph
2.邻接表(链表)
邻接表存储表示:
#define MAX_VERTEX_NUM 20
typedef struct ArcNode
{ int adjvex;
struct ArcNode *nextarc;
InfoType *info;
}ArcNode; //边表结点
typedef struct VNode
{ VertexType data;
ArcNode *firstarc;
}Vnode, AdjList[MAX_VERTEX_NUM]; //表头结点
typedef struct
{ AdjList vertices;
int vexnum , arcnum; //顶点数和弧数
int kind; //图的种类标志
}ALGraph;
建立无向图的邻接表:
CreatAdjList(ALGraph ga ) //顶点数目n,边的数目e
{ int i,j,k; ArcNode *s;
for (i=0; i<n; i++)
{ ga. vertices [i]. data =getchar( );
ga. vertices [i]. firstarc =NULL; }
for (k=0; k<e; k++)
{ scanf(“%d%d”,&i,&j);
s=(ArcNode *)malloc(sizeof(ArcNode)); //采用头插入法
s->adjvex=j; s-> nextarc = ga. vertices [i]. firstarc;
ga. vertices [i]. firstarc =s;
s=(ArcNode *)malloc(sizeof(ArcNode));
s->adjvex=i; s-> nextarc = ga. vertices [j]. firstarc;
ga. vertices [j]. firstarc =s;
}
}
3.十字链表
4.邻接多重表
三、图的遍历
1.深度优先搜索DFS:
(1)基本思想:
(2)非连通图的DFS
(3)算法实现
void DFS(ALGraph *G , int v)
{ LinkNode *p ;
Visited[v]=TRUE ;
Visit[v] ; /* 置访问标志,访问顶点v */
p=G->AdjList[v].firstarc; /* 链表的第一个结点 */
while (p!=NULL)
{ if (!Visited[p->adjvex]) DFS(G, p->adjvex) ;
/* 从v的未访问过的邻接顶点出发深度优先搜索 */
p=p->nextarc ;
}
}
void DFS_traverse (ALGraph *G)
{ int v ;
for (v=0 ; v<G->vexnum ; v++)
Visited[v]=FALSE ; /* 访问标志初始化 */
for (v=0 ; v<G->vexnum ; v++)
if (!Visited[v]) DFS(G , v);
}
2.广度优先搜索BFS:
(1)从图中某个顶点v0出发,首先访问v0 ;
(2)依次访问v0各个未被访问的邻接点;
(3)分别从这些邻接点出发,依次访问它们的各个未被访问的邻接点。
(4)算法实现
typedef enum {FALSE , TRUE} BOOLEAN ;
BOOLEAN Visited[MAX_VEX] ;
typedef struct Queue
{ int elem[MAX_VEX] ;
int front , rear ;
}Queue ; /* 定义一个队列保存将要访问顶点 */
void BFS_traverse (ALGraph *G)
{ int k ,v , w ; LinkNode *p ; Queue *Q ; Q=(Queue *)malloc(sizeof(Queue)) ;
Q->front=Q->rear=0 ; /* 建立空队列并初始化 */
for (k=0 ; k<G->vexnum ; k++) Visited[k]=FALSE ; /* 初始化 */
for (k=0 ; k<G->vexnum ; k++)
{ v=G->AdjList[k].data ; /* 单链表的头顶点 */
if (!Visited[v]) /* v尚未访问 */
{ Q->elem[++Q->rear]=v ; /* v入队 */
while (Q->front!=Q->rear)
{ w=Q->elem[++Q->front] ;
Visited[w]=TRUE ; /* 访问标志 */
Visit(w) ; /* 访问队首元素 */
p=G->AdjList[w].firstarc ;
while (p!=NULL)
{ if (!Visited[p->adjvex])
Q->elem[++Q->rear]=p->adjvex ;
p=p->nextarc ; } } } } }
四、图的连通性问题
1.无向图的连通分量和生成树
树的孩子兄弟表示法:
typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild,*nextsibling;
}CSNode, *CSTree;
建立无向图G的DFS生成森林的孩子兄弟链表T
void DFSForest(Graph G, CSTree &T)
{
T = NULL;
for (v=0; v<G.vexnum; ++v) visited[v] = FALSE;
for (v=0; v<G.vexnum; ++v)
if (!visited[v])
{ // 第v顶点为新的生成树的根结点
p= (CSTree)malloc(sizeof(CSNode));//分配根结点
p->data=GetVex(G,v); // 给该结点赋值
p->firstchild=NULL;
p->nextsibling=NULL;
if (!T) T = p; // 是第一棵生成树的根(T的根)
else // 其它生成树的根(前一棵的根的“兄弟”)
q->nextsibling = p;
q = p; // q指示当前生成树的根
DFSTree(G, v, p); // 建立以p为根的生成树
} //if
} // DFSForest
2.最小生成树
1.Prim算法(生成树不唯一)
(1)基本思想:找最小的,最后每个顶点都被找到(按照找到点的顺序,不能任意跳)
记录从顶点集U到V-U的代价最小的边的辅助数组定义:
struct {
VertexType adjvex;
VRType lowcost;
} closedge[MAX_VERTEX_NUM];
用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边:
void MiniSpanTree_PRIM(MGraph G, VertexType u)
{ int i,j,k;
k = LocateVex ( G, u );
for ( j=0; j<G.vexnum; ++j )
// 辅助数组初始化
if (j!=k)
{ closedge[j].adjvex=u;
closedge[j].lowcost=G.arcs[k][j].adj;
}
closedge[k].lowcost = 0;
for (i=1; i<G.vexnum; ++i)
{ // 选择其余G.vexnum-1个顶点
k = minimum(closedge); // 求出T的下一个结点:第k顶点
printf(closedge[k].adjvex, G.vexs[k]);// 输出生成树的边
closedge[k].lowcost = 0; // 第k顶点并入U集
for (j=0; j<G.vexnum; ++j)
if (G.arcs[k][j].adj < closedge[j].lowcost)
{
// 新顶点并入U后重新选择最小边
closedge[j].adjvex=G.vexs[k];
closedge[j].lowcost=G.arcs[k][j].adj;
}
}
} // MiniSpanTree
2.Kruskal算法(生成树不唯一)
(1)基本思想:找权值小的且不构成回路的。(可以任意挑)
#define INFINITY MAX_VAL /* 最大值∞ */
#define MAX_VEX 30 /* 最大顶点数 */
#define MAX_EDGE 100 /* 最大边数 */
typedef struct MSTEdge
{ int vex1 , vex2 ; /* 边所依附的两个顶点 */
WeightType weight ; /* 边的权值*/
} MSTEdge ; /* 边表元素类型定义 */
typedef struct
{ int vexnum , edgenum ; /* 顶点数和边数 */
VexType vexlist[MAX_VEX] ; /* 顶点表 */
MSTEdge edgelist[MAX_EDGE] ; /* 边表 */
}ELGraph ;
MSTEdge *Kruskal_MST(ELGraph *G)
/* 用Kruskal算法构造图G的最小生成树 */
{ MSTEdge TE[] ;
int j, k, v, s1, s2, Vset[] ; WeightType w ;
Vset=(int *)malloc(G->vexnum*sizeof(int)) ;
for (j=0; j<G->vexnum; j++)
Vset[j]=j ; /* 初始化数组Vset[n] */
sort(G->edgelist) ; /* 对表按权值从小到大排序 */
j=0 ; k=0 ;
while (j<G->vexnum-1&&k< G->edgenum)
{ s1=Vset[G->edgelist[j].vex1] ;
s2=Vset[G->edgelist[j].vex2] ;
/* 若边的两个顶点的连通分量编号不同, 边加入到TE中 */
if (s1!=s2)
{ TE[k].vex1=G->edgelist[j].vex1 ;
TE[k].vex2=G->edgelist[j].vex2 ;
TE[k].weight=G->edgelist[j].weight ;
k++ ;
for (v=0; v<G->vexnum; v++)
If (Vset[v]==s2) Vset[v]=s1 ;
}
j++ ;
}
free(Vset) ;
return(TE) ;
} /* 求最小生成树的Kruskal算法 */
五、有向无环图及其应用
1.拓扑排序
(1)DAG:有向无环图;
(2)从有向图中选取一个没有前驱的顶点, (入度为0 的顶点),并输出之;
从有向图中删去此顶点以及所有以它为尾的弧;
重复上述步骤
(3)拓扑排序算法描述
算法:PPT111
(4)逆拓扑排序:选取没有后继的顶点删除此顶点以及所有以他为头的弧
2.关键路径
(1)AOV网:顶点表示活动,弧表示活动间优先关系,带权有向无环图。可用来估计工程完成的时间。
(2)关键路径:完成整个工程所需的时间取决于从开始点到结束点的最长路径长度,此长度最大的路径叫做关键路径。算法时间复杂度O(n+e)
六、最短路径
(1)Dijkstra算法 O (n^2):