图是一种非线性结构
图的特点:
子图(不用包含所有顶点)、生成子图:若包含所有顶点的子图,就称为生成子图。并不是任意几个点,任意几条边都能构成子图(例如不构成图的情况)。
连通分量:无向图中极大连通子图(子图必须连通,且包含尽可能多的顶点和边)称为连通分量。
强连通分量:有向图中有极大强连通子图(子图必须强连通,同时保留尽可能多的边)称为有向图的强连通分量。
生成树:连通图的生成树是包含图中全部顶点的一个极小连通子图(边尽可能的小,但要保持连通)若图中顶点数是n,则它的生成树含有n-1条边,对于生成树而言,若砍去一条边,则会变成非连通图,若加上一条边则会形成一个回路(环)。
生成森林:在非连通图中,连通分量的生成树构成了非连通图的生成森林。
边的权,带权图(网)
无向完全图:无向图中任意两个顶点之间都存在边。
有向完全图:有向图中任意两个顶点之间都存在方向相反的两条弧。
稀疏图,稠密图:这二者是相对的
树:不存在回路,且连通的无向图,n个顶点的树,必有n-1条边。
有向树:一个顶点的入度为0,其余顶点的入度均有1的有向图,称为有向树。
n个顶点的图,若|E|>n-1,则一定有回路。
① 对于无向图,顶点vi的度是邻接矩阵中第i行(或第i列)的元素之和, TD(vi)= ∑ a[i][j]。
② 对于有向图,顶点VI的出度OD(vi)是邻接矩阵中第i行的元素之和,顶点vi的出度ID(vi)是邻接矩阵中第j列)的元素之和。
③ 邻接矩阵法求顶点的度。入度。出度的时间复杂度为O(|V|)。
#define INFINITY INT_MAX
#define MAX_VERTEXT_NUM 20
typedef enum{DG,DN,AG,AN}GraphKind;
typedef struct ArcCell{
VRType adj;//对无向图,用0,1表示是否相邻,对于带权图,为权值
InfoType *info;//该弧相关信息的指针
}RrcCell,AdjMatrix[MAX_VERTEXT_NUM][MAX_VERTEXT_NUM];//邻接矩阵
(1)代码:
//定义图的结构
typedef struct {
VertextType exs[MAX_VERTEXT_NUM]; //顶点
AdjMatrix arcs[MAX_VERTEXT_NUM][MAX_VERTEXT_NUM]; //边的邻接矩阵
int vexnum,arcnum; //个数
GraphKind kind;//有向图?无向图?
}MGraph;
status CreateGraph(MGraph &G)
{
scanf(&G.kind)
{
case DG:return CreateDG(G);
case DN:return CreateDN(G);
case UDG:return CreateUDG(G);
case UDN:return CreateDGN(G);
default:return ERROR;
}
}
//用无向图为例
status CreateUDN(MGraph &G)
{
scanf(&G.arcnum,&G.vexnum,&IncInfo);//输入点数和边数
//给顶点进行数字化编号
for(i=0;i<G.vexnum;i++)
{ scanf(&G.exs[i]);//定义顶点数组(如果顶点本身就是1~n的数字无需这一步)
}
//初始化邻接矩阵
for(i=0;i<G.vexnum;i++)
{
for(j=0;j<G.vexnum;j++)
{
G.arcs[i][j]={ININITY,NULL};
}
}
//通过边数进行遍历
for(k=0;k<G.arcnum;K++)
{
scanf(&V1,&V2,&W);//输入邻接的连个顶点
i=locatteVex(G,V1);j=locateVex(G,V2);//查找V1,V2的位置
G.arcs[i][j].adj=w;//给邻接矩阵赋值
if(IncInfo)
{
INPUT(*G.arcs[i][j].info);
}
G.arcs[j][i]=G.arcs[i][j];//由于是无向图,对称
}
return ok;
}
优点:
缺点:
【1】顶点的结点结构
———————
| data | firstarc |
———————
data数据域:储存顶点vi
firstarc链域:指向链表中第一个结点
【2】弧的结点结构
——————————
| adjvex | info | nextarc |
——————————
adjvex邻接点域:与顶点vi邻接的点在图中的位置
info数据域:储存和边相关的信息,如权值
nextarc链域:与顶点vi的点在图中的位置
#define MAX_VERTEXT_NUM 20
//建立边结点
typedef struct ArcNode {
int adjvex; // 该弧所指向的顶点的位置
struct ArcNode *nextarc; // 指向下一条弧
InfoType *info; // 该弧相关信息(可选)
}ArcNode;
// 顶点结点
typedef struct VNode{
VertexType data; // 顶点信息
ArcNode *firstarc; // 指向第一条依附该顶点的弧
}VNode,AdjList[MAX_VERTEXT_NUM];
//邻接表
typedef struct {
Adjlist vertices;
int vexnum,arcnum;
int kind;
}ALGraph;
//建立邻接表算法
//初始化一个结点总数为num的图,k为图的类型,num为结点总数
void InitG(ALGraph G,enum GraphKind k,int num)
{
G.kind=k;
G.vexnum=num;
G.vertices=new VNode[vexnum];
for(int i=0;i<G.vexnum;i++)
{G.vertices[i].Firstarc=NULL;
cin>>G.vertics[i].data;
}
}
//有向图(网)增加弧的算法,将弧(from,to,weight)加入图
void InsertArc(ALGragh G,int from,int to,int weight)
{
ArcNode *s=new ArcNode;
s->weight=weight;
s->adjvex=to;
s->nextarc=G.vertices[from].firstarc;//插到链表vertices[from]的头
G.vertices[from].firstarc=s;
}
(1)在邻接表中,同一条边对应两个结点。
(2)无向图中顶点v的度:等于v 对应的链表的长度;但是,在有向图中,要求顶点A的的入度,则需要遍历所有的顶点连接的链表,判断有几个存在顶点A;求出度,则是A顶点链表有几个点。
(3)判定两顶点v,w是否邻接:要看v对应的链表中有无对应的结点w(相反判断也行);
(4)对于一个图,给定的邻接表是并不唯一的(区分与邻接矩阵)
(5)增减边:要在两个单链表插入、删除结点;
(6)占用存储空间与顶点数、边数均有关;适用于边稀疏的图
注意,在有向图的邻接表中不易找到指向该顶点的弧。
邻接矩阵表示唯一,邻接表表示不唯一
(无向边)<有向边>
思路:
①无向图:邻接矩阵,判断aij是否为1,邻接表,i点的邻接表是否有j点;
②有向图类似
时间复杂度
假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1
1、定义——从某顶点出发,沿着一些边访问连通图中所有顶点,且使每个顶点仅访问一次的运算。
2、为避免重复访问,可设置辅助数组Visited[ ],各分量初值为0,当顶点被访问,对应分量被置为1。
3、方法
使用邻接矩阵(唯一)所以广度优先遍历序列唯一
使用邻接表(不唯一)所以广度优先遍历序列不唯一
采用邻接表存储实现无向图的广度优先遍历
//visited是访问标记数组
//处理非连通图的情况
bool BFSTraverse(Graph G){
for(int i=0;i<G.vexnum;++i)
visited[i] = false;
InitQueue(Q);
for(int i=0;i<G.vexnum;++i){
if(!visited[i])
BFS(G,i);
}
}
void BFS(Graph G,int v){
visit(v); //访问v顶点
visited[v] = True; //修改该顶点对应数组的值为true
EnQueue(Q,v); //入队
while(!isEmpty(Q)){ //不空还有未遍历到的节点
DeQueue(Q,v); //出队v
for(w = FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) //找到所有符合条件的邻接节点
if(!visited[w]){ //w是否被访问
visit[w]; //访问
visited[w] = true; //修改该顶点对应数组的值为true
EnQueue(Q,w); //入队
}
}
}
bool BFSTraverse(Graph G,int v){
for(int i=0;i<G.vexnum;++i)
visited[i] = false;
InitQueue(Q);
for(int i=0;i<G.vexnum;++i){
if(!visited[i])
visit(v); //访问v顶点
visited[v] = True; //修改该顶点对应数组的值为true
EnQueue(Q,v); //入队
while(!isEmpty(Q)){ //不空还有未遍历到的节点
DeQueue(Q,v); //出队v
for(w = FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) //找到所有符合条件的邻接节点
if(!visited[w]){ //w是否被访问
visit[w]; //访问
visited[w] = true; //修改该顶点对应数组的值为true
EnQueue(Q,w); //入队
}
}
}
}
① 如果使用邻接表,则从同一个顶点广度优先遍历序列会随着链接表不同而不同,但是由于邻接矩阵是唯一的,所以从同一个广度优先遍历得到的顺序是唯一的。
②
③ 空间复杂度:O(|V|) 辅助队列的最坏的情况(所有结点都入队)
④ 时间复杂度:
① 通过广度优先遍历可以的得到一棵遍历树
② 由于邻接表不唯一,则树不唯一;由于邻接矩阵唯一,则树唯一。
遍历非连通图,可以得到广度优先生成森林。
从图中某个顶点V0 出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到。
邻接矩阵
int visited[MAX];//设置一个数组,判断是否遍历过,false/1为遍历过
void DFGTraverse(Graph G,int v)
{
for(v=0;v<G.vexnum;v++)
visited[v]=0;//初始化判断数组
for(v=0;v<G.vexnum;v++)
{
if(!visited[v])//如果没有遍历过
DFS(G,V);//进行遍历
}
}
void DFS(Graph G,int v)//进行递归遍历
{
visited[v]=1;printf(v);//改变判断数组,输出点
for(w=FirstVex(G,v);w!=0;w=NextVex(G,v))//从每一行第一个邻接矩阵值为1的,跳转到下一个值为1的
{
if(!visited[w])
DFS(G,v);
}
}
int FirstVex(Graph G,int v)//判断第一个不是0的
{
int i;
for(i=0;i<G.vexnum;i++)
{
if(G.arcs[v][i]==1&&visited[i]==False)
return i;
}
return -1;
}
void NextVex(Graph G,int v)//判断下一个不是0的
{
int i;
for(i=w;i<G.vexnum;i++)
{
if(G.arcs[v][i]==1&&visited[i]!=False)
return i;
}
return -1;
}
2.邻接表
void DFS(Graph G,int v)
{
cout<<G.vertices[v].data<<" ";
visited[v]=true;
ArcNode *p=G.vertices[v].firstarc;
while(p!=NULL)
{
int w=p->adjvex;
if(!visited[w])
DFS(G,w);
p=p->nextarc;a
}
}
对于一个带权连通无向图G = (V,E),生成树,每棵树的权(即树中所有边上的权值之和)也可能不同。设R为G的所有生成树的集合,若T为R中边的权值之和最小的生成树,则T称为G的最小生成树(Minimum-Spannino-Tree,MST).。
时间复杂度: O(l E l * log2l E l )
适合用于边稀疏图
使用 BFS算法求无权图的最短路径问题,需要使用三个数组
这是寻找结点2的最短路径的数组情况
#define MAX_LENGTH 2147483647 //地图中最大距离,表示正无穷
// 求顶点u到其他顶点的最短路径
void BFS_MIN_Disrance(Graph G,int u){
for(i=0; i<G.vexnum; i++){
visited[i]=FALSE; //初始化访问标记数组
d[i]=MAX_LENGTH; //初始化路径长度
path[i]=-1; //初始化最短路径记录
}
InitQueue(Q); //初始化辅助队列
d[u]=0;
visites[u]=TREE;
EnQueue(Q,u);
while(!isEmpty[Q]){ //BFS算法主过程
DeQueue(Q,u); //队头元素出队并赋给u
for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w)){
if(!visited[w]){
d[w]=d[u]+1;
path[w]=u;
visited[w]=TREE;
EnQueue(Q,w); //顶点w入队
}
}
}
}
BFS算法的局限性:BFS算法求单源最短路径只适⽤于⽆权图,或所有边的权值都相同的图。
Dijkstra算法能够很好的处理带权图的单源最短路径问题,但不适⽤于有负权值的带权图
使用 Dijkstra算法求最短路径问题,需要使用三个数组:
#define MAX_LENGTH = 2147483647;
// 求顶点u到其他顶点的最短路径
void BFS_MIN_Disrance(Graph G,int u){
for(int i=0; i<G.vexnum; i++){ //初始化数组
final[i]=FALSE;
dist[i]=G.edge[u][i];
if(G.edge[u][i]==MAX_LENGTH || G.edge[u][i] == 0)
path[i]=-1;
else
path[i]=u;
final[u]=TREE;
}
for(int i=0; i<G.vexnum; i++){
int MIN=MAX_LENGTH;
int v;
// 循环遍历所有结点,找到还没确定最短路径,且dist最⼩的顶点v
for(int j=0; j<G.vexnum; j++){
if(final[j]!=TREE && dist[j]<MIN){
MIN = dist[j];
v = j;
}
}
final[v]=TREE;
// 检查所有邻接⾃v的顶点路径长度是否最短
for(int j=0; j<G.vexnum; j++){
if(final[j]!=TREE && dist[j]>dist[v]+G.edge[v][j]){
dist[j] = dist[v]+G.edge[v][j];
path[j] = v;
}
}
}
}
Floyd算法:求出每⼀对顶点之间的最短路径,使⽤动态规划思想,将问题的求解分为多个阶段。
Floyd算法可以⽤于负权值带权图,但是不能解决带有“负权回路”的图(有负权值的边组成回路),这种图有可能没有最短路径。
Floyd算法使用到两个矩阵:
int dist[MaxVertexNum][MaxVertexNum];
int path[MaxVertexNum][MaxVertexNum];
void Floyd(MGraph G){
int i,j,k;
// 初始化部分
for(i=0;i<G.vexnum;i++){
for(j=0;j<G.vexnum;j++){
dist[i][j]=G.Edge[i][j];
path[i][j]=-1;
}
}
// 算法核心部分
for(k=0;k<G.vexnum;k++){
for(i=0;i<G.vexnum;i++){
for(j=0;j<G.vexnum;j++){
if(dist[i][j]>dist[i][k]+dist[k][j]){
dist[i][j]=dist[i][k]+dist[k][j];
path[i][j]=k;
}
}
}
}
}
若⼀个有向图中不存在环,则称为有向⽆环图,简称 DAG图(Directed Acyclic Graph)
用DAG图(有向无环图)表示一个工程。顶点表示活动,有向边
在图论中,由⼀个有向⽆环图的顶点组成的序列,当且仅当满⾜下列条件时,称为该图的⼀个拓扑排序:
#define MaxVertexNum 100 //图中顶点数目最大值
typedef struct ArcNode{ //边表结点
int adjvex; //该弧所指向的顶点位置
struct ArcNode *nextarc; //指向下一条弧的指针
}ArcNode;
typedef struct VNode{ //顶点表结点
VertexType data; //顶点信息
ArcNode *firstarc; //指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{
AdjList vertices; //邻接表
int vexnum,arcnum; //图的顶点数和弧数
}Graph; //Graph是以邻接表存储的图类型
// 对图G进行拓扑排序
bool TopologicalSort(Graph G){
InitStack(S); //初始化栈,存储入度为0的顶点
for(int i=0;i<g.vexnum;i++){
if(indegree[i]==0)
Push(S,i); //将所有入度为0的顶点进栈
}
int count=0; //计数,记录当前已经输出的顶点数
while(!IsEmpty(S)){ //栈不空,则存入
Pop(S,i); //栈顶元素出栈
print[count++]=i; //输出顶点i
for(p=G.vertices[i].firstarc;p;p=p=->nextarc){
//将所有i指向的顶点的入度减1,并将入度为0的顶点压入栈
v=p->adjvex;
if(!(--indegree[v]))
Push(S,v); //入度为0,则入栈
}
}
if(count<G.vexnum)
return false; //排序失败
else
return true; //排序成功
}
在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如 完成活动所需的时间),称之为⽤边表示活动的⽹络,简称 AOE ⽹ (Activity On Edge NetWork)。
在 AOE ⽹中仅有⼀个⼊度为 0 的顶点,称为开始顶点(源点),它表示整个⼯程的开始;== 也仅有⼀个出度为 0 的顶点,称为结束顶点(汇点)==,它表示整个⼯程的结束。