基本概念

图的定义

  • 图由顶点集V和边集E组成,记G=(V,E)

    ​ V(G)表示图G中顶点的有限非空集。

    ​ E(G)表示图G中顶点间关系(边)集合。

    ​ E={(u,v)|u∈V,v∈V},用|E|表示图中边的个数。

  • 若V={v1,v2,…,vn},则用|V|表示图G中顶点的个数,也称为图G的阶。

  • 线性表可以是空表,树可以是空树,但图不可以是空图。

    图中不可能一个顶点也没有,图的顶点集一定非空,但边集E可以为空,此时图中只有顶点没有边。

    图和树的区别是逻辑上的区别,而不是边数的区别,图的边数也可能小于树的边数。

有向图
  • 若E是有向边的有限集合时,则图G为有向图。

  • 顶点集,弧集

  • 是顶点的有序对,记为<v,w>

    ​ 其中v,w是顶点,v称为弧尾,w称为弧头

    ​ <v,w>称为从顶点v到顶点w的弧,也称v邻接到w,或w邻接自v。

无向图
  • 若E是无向边的有限集合时,则图G为有向图。

  • 顶点集,边集

  • 边是顶点的无序对,记为(v,w)或(w,v)

    ​ 其中v,w是顶点,可以说顶点w和顶点v互为邻接点。

    ​ 边(v,w)依附于顶点w和v,或者说边(v,w)和顶点v,w相关联

简单图
  • 不存在重复边。

  • 不存在顶点到自身的边

  • G1和G2均为简单图

完全图(简单完全图)
  • 无向完全图:任意两个顶点之间都存在边。
  • 含有n个顶点的无向完全图有n(n-1)/2条边。
  • 有向完全图:任意两个顶点之间都存在方向相反的两条弧。
  • 含有n个顶点的有向完全图有n(n-1)条有向边
子图
  • 设有两个图G=(V,E)和G'=(V',E'),若V'是V的子集,且E'是E的子集,则称G'是G的子图
  • 若有满足V(G')=V(G)的子图G',则称其为G的生成子图
  • 并非V和E的任何子集都能构成G的子图,这样的子集可能不是图,即E的子集中的某些边关联的顶点可能不在这个V的子集中。
连通图
  • v和w连通:从顶点v到顶点w有路径存在。

  • 连通图:图中任意两个顶点都是连通的。

    若一个图有n个顶点,并且边数小于n-1,此图必为非连通图。

    连通图可能是树也可能存在环。

    图的遍历要求每个结点只能被访问一次,若图非联通,则从某一顶点出发无法访问到其他全部顶点,

  • 连通分量:无向图中的极大连通子图。

    对无向连通图做一次深度优先搜索,可以访问到该连通图的所有顶点。

  • 极大连通子图:无向图的连通分量。

  • 极小连通子图:既要保持图连通又要使得边数最少的子图。

强连通图
  • 强连通:有向图中,从顶点v到顶点w和从顶点w到顶点v之间都有路径。

  • 强连通分量:有向图中的极大强连通子图。

  • 强连通图和强连通分量只针对与有向图。

    无向图中讨论连通性,有向图中考虑强连通性。

  • 无向连通图中边最少构成一棵树。

    无向连通图对应的生成树也是无向连通图,但此时边数等于顶点数减1。

    强连通有向图,边最少构成一个有向环。

生成树、 生成森林
  • 生成树:包含图中全部顶点的一个极小连通子图。
  • 图中顶点数为m,则它的生成树含有n-1条边。
  • 砍去生成树的一条边会变成非连通图,加上一条边会形成一个回路。
  • 非连通图中,连通分量的生成树构成了非连通图的生成森林。
顶点的度、入度和出度
  • :以该顶点为一个端点的边的数目。

  • 无向图中的度:依附于顶点的边的条数,\(TD(v)\)

    具有n个顶点、e条边的无向图中,\(\displaystyle \sum^{n}_{i=1}{TD(v_i)=2e}\),

    即无向图的全部顶点的度的和等于边数的2倍。

  • 有向图顶点v的度分别为入度和出度,

    入度是以顶点v为终点的有向边的数目,记为\({ID(v_i)}\)

    出度是以顶点v为起点的有向边的数目,记为\(OD(v)\)

    顶点v的度等于其入度和出度之和,即\(TD(v)=ID(v)+OD(v)\)

  • 在具有n个顶点、e条边的有向图中,\(\displaystyle \sum^{n}_{i=1}{ID(v_i)=\displaystyle \sum^{n}_{i=1}{OD(v_i)}=e}\)

    即有向图的全部顶点的入度和出度之和相等,并且等于边数。

边的权和网
  • 带权图(网):边上带权值的图。

    有向网,无向网

稠密图、稀疏图
  • 稠密图:边数很多的图。
  • 稀疏图:当图G满足\(|E|<|V|log|V|\)\(e<nlogn\)
路径、路径长度和回路
  • 路径:顶点\(V_p\)到顶点\(V_q\)之间的顶点序列。
  • 路径长度:路径上边的数目。
  • 第一个顶点和最后一个顶点相同的路径称为回路
  • 若一个图有n个顶点,并且有大于n-1条边,则此图一定有环。
简单路径,简单回路
  • 简单路径:在路径序列中顶点不重复的路径。
  • 简单回路:除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路。
距离
  • 距离:从顶点v到顶点u的最短路径。

    若根本不存在路径记为∞无穷

有向树
  • 有向树:顶点的入度为0、其余顶点的入度均为1的有向图。

存储结构

顺序存储

邻接矩阵
  1. 用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。
  2. 邻接矩阵中的元素仅表示相应的边是否存在时,EdgeType可定义为值为0和1的枚举类型。
  3. 对规模特大的邻接矩阵可采用压缩存储。
  4. 空间复杂度为\(O(n^2)\),其中n是图的顶点数|V|。
  5. 无向图的邻接矩阵一定是一个对称矩阵(并且唯一)。实际存储邻接矩阵只需存储上(或者下)三角矩阵的元素。
  6. 无向图的邻接矩阵第i行(或第i列)非零元素(或非∞元素)的个数正好是第i个顶点的度\(TD(v_i)\)
  7. 有向图的邻接矩阵第i行(或第i列)非零元素(或非∞元素)的个数正好是第i个顶点的出度\(OD(v_i)\)或入度\(ID(v_i)\)
  8. 优点:便于确定图中任意两个顶点之间是否有边相连。
    缺点:①确定边的总数费时间,须按行按列对每个元素进行检测。
  9. 稠密图适合用邻接矩阵的存储表示。
  10. 设图G的邻接矩阵为\(A\)\(A^n\)的元素\(A^n[i][j]\)等于由顶点i到顶点j的长度为n的路径的数目。

链式存储

邻接表法
  1. 顺序存储和链式存储。

  2. 对比树的孩子表示法

  3. 对图G中每个顶点\(v_i\)建立一个单链表,第i个单链表中的结点表示依附于顶点\(v_i\)的边(对于有向图则是以顶点\(v_i\)为尾的弧)的单链表称为边表。

  4. 边表的头指针和顶点的数据信息采用顺序存储(称为顶点表)

    邻接表中存在两种结点:顶点表结点和边表结点。

    data firstarc
    顶点域 边表头指针

    【顶点表】

    adjvex nextarc
    邻接点域 指针域

    【边表 】

  5. 顶点表结点由顶点域和指向第一条邻接边的指针构成,

    边表结点由邻接结点域和指向下一条邻接边的指针域构成。

邻接矩阵 邻接表
存储方式 顺序存储 顺序存储+链式存储
应用 稠密图 稀疏图
表示方式 唯一 不唯一
广度优先遍历 唯一 不唯一
节点数量 无向图:$2
空间复杂度 $O( V^2
时间复杂度 $O( V^2
计算度 必须遍历对应行或列 计算有向图的度和入度不方便
找相邻边 必须遍历对应行或列 找有向图的入边不方便
十字链表法
  1. 存储有向图
  2. O(|V|+|E|)
邻接多重表
  1. 存储无向图
  2. O(|V|+|E|)

基本操作

  1. Adjacent(G,x,y):判断图G是否存在有向边<x,y>或无向边(x,y)。

  2. Neighbors(G,x):列出图G中与结点x邻接的边。

  3. InsertVertex(G,x):在图G中插入顶点x。

  4. DeleteVertex(G,x) :从图G中删除顶点x。

  5. AddEdge(G,x,y):若(x,y)或<x,y>不存在,则向图中添加该边。

  6. RemoveEdge(G,x,y):若(x,y)或<x,y>存在,则从图中删除该边。

  7. FisrtNeighbor(G,x,y):求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回-1.

  8. NextNeighbor(G,x,y):假设图中顶点y是顶点x的一个邻接点,返回y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,返回-1.

  9. Get_edge_value(G,x,y):获取图G中(x,y)或<x,y>的对应权值。

  10. Set_edge_value(G,x,y,v):设置图G中(x,y)或<x,y>的对应权值。

邻接矩阵 邻接表
Adjacent(G,x,y) O(1) O(1)--O(|V|)
Neighbors(G,x) O(|V|) O(1)--O(|V|)
有向图入边:O(|E|)
InsertVertex(G,x) O(1) O(1)
DeleteVertex(G,x) O(|V|) O(1)--O(|E|)
出边:O(1)--O(|V|) 入边:O(|E|)
AddEdge(G,x,y) O(1) O(1)
RemoveEdge(G,x,y) O(|V|) O(|E|)
FirstNeighbor(G,x,y) O(1)--O(|V|) O(1)
出边:O(1) 入边:O(1)--O(|E|)
NextNeighbor(G,x,y) O(1)--O(|V|) O(1)
Get_edge_value(G,x,y) O(1) O(1)--O(|V|)
Set_edge_value(G,x,y,v) O(1) O(1)--O(|V|)

图的遍历

广度优先遍历(BFS)

与树的广度优先(层序)遍历之间的联系
从根结点出发, 横向查找所有子结点 从某一结点出发,查找有关联的结点
不存在回路 有回路
算法实现

①找到一个顶点相邻的所有顶点

②标记哪些顶点被访问过

③需要一个辅助队列

//无向图
bool visited[MAX_VERTEX_NUM];	//访问标记数组
void BFSTraverse(Graph G){		//对图G进行广度优先遍历
    for(i=0;i<G.vexnum;i++)
        visited[i]=FALSE;		//访问标记数组初始化
    INitQueue(Q);				//初始化辅助队列Q
    for(i=0;i<G.vexnum;++i)		//从0号顶点开始遍历
        if(!visited[i])			//对每个连通分量调用一次BFS
            BFS(G,i);			//vi未访问过,从vi开始BFS
}
//广度优先遍历
void BFS(Graph G,int v){		//从顶点v出发,广度优先遍历图G
    visit(V);					//访问初始顶点v
    visited[v]=TRUE;			//对v做已访问标记
    Enqueue(Q,v);				//顶点v入队列Q
    while(!isEmpty(Q)){
        DeQueue(Q,v);			//顶点v出队列
        for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
            //检测v所有邻接点
       		 if(!visited[w]){ 		//w为v的尚未访问的邻接顶点
                visit(w);			//访问顶点w
                visited[w]=TRUE;	//对w做已访问标记
                EnQueue(Q,w);		//顶点w入列
            }//if
    }//while
}

//对于无向图,调用BFS函数的次数=连通分量数

//有向图

广度优先生成树

广度优先生成树由广度优先遍历过程确定。由于邻接表的表示方式不唯一,因此基于邻接表的广度优先生成树也不唯一。

对非连通图的广度优先遍历,可得到广度优先生成森林。

深度优先遍历(DFS)

与树的深度优先遍历之间的联系
//树的先根遍历
void PreOrder(Tree *R){
    if(R!=NULL){
        visit(R);
        while(R还有下一个子树T)
    }
}

//
void DFSTraverse(Graph G){
    for(v=0;v<G.vexnum;++v)
        visited[v]=FALSE;		//初始化
    for(v=0;v<G.vexnum;++v)
        if(!visited[v])
            DFS(G,v);
}
void DFS(Graph G,int v){
    visit(V);					//访问顶点v
    visited[v]=TRUE;			//已访问顶点
    for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
        if(!visited[w]){			//w尚未访问临界顶点
            DFS(G,w);
        }
}
图的遍历和图的连通性

对与强连通图,从任意节点出发都只需调用一次BFS/DFS

最小生成树

所有地方都联通,且成本尽可能的低

最小生成树可能有多个,但边的权值之和总是唯一且最小的

最小生成树的边数=顶点数-1。砍掉一条则不连通,增加一条则出现回路。

如果连通图本身就是一棵树,最小生成树就是它本身。

算法 bfs dfs Kruskal Prime Dijkstra floyd
dijkstra;prime 每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的不选)直到所有结点都联通。 从某个顶点开始构建生成树,每次将代价最小新顶点纳入生成树,直到所有顶点都纳入。 从某个顶点开始,寻找到其他顶点的最小路径,每找到一个最短路径就更新其他路径距离,直到所有顶点距离都最小。 求出每一对顶点之间的的最短路径,其间允许中转的顶点数逐次增加
时间复杂度 \(O(|V^2|)\) \(O(|E|log_2|E|)\) \(O(|V^2|)\) \(O(|V^2|)\) \(O(|V^3|)\)
适用类型 无权图 稀疏图 稠密图· 非负权值 非负权值
最短路径 单源最短路径 总路径最短 总路径最短- 单源最短路径 各顶点间最短路径
最小生成树 最小生成树

最短路径

单源最短路径
BFS
//求顶点u到其他顶点的最短路径
void BFS_MIN_Distance(Graph G,int u){
    //d[i]表示从u到i结点的最短路径
    for(i=0;i<G.vexnum;++i){
        d[i]=∞;			//初始化路径长度
        path[i]=-1;		//最短路径从哪个顶点过来
    }
    d[u]=0;
    visited[u]=TRUE;
    EnQueue(Q,u);
    while(!isEmpty(Q)){	
        DeQueue(Q,u);		//队头元素u出队
        for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
            if(!visited[w]{		//w为u的尚未访问的邻接顶点
                d[w]=d[u]+1;	//路径长度+1
                path[w]=u;		//最短路径u到v,记录前驱顶点
                visited[w]=TRUE;//设已访问标记
                EnQueue(Q,w);	//顶点w入队
            }//if
    }//while
}

AOV网

用DAG图(有向无环图)表示一个工程。顶点表示活动,有向边表示活动进行顺序

拓扑排序

定义Ⅰ:找到做事的先后顺序

实现:1.从AOV网中选择一个没有前驱的顶点并输出

2.从网中删除该顶点和所有以它为起点的有向边。

3.重复1和2直到当前的AOV网为空或当前网中不存在无前驱的顶点为止。

定义Ⅱ:由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑序列:①每个顶点出现且只出现一次。②若每个顶点A在序列中排在顶点B的前面,则在图中不存在从顶点B到顶点A的路径。

定义Ⅲ:对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。每个AOV网都有一个或多个拓扑排序序列。

#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;							//以邻接表存储的图类型
 
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)){			//栈不空,则入度为0的顶点
        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;
} 

有向无环图(DAG)

posted @ 2021-12-26 12:48  wangqr  阅读(247)  评论(0)    收藏  举报