第六章 图

图的基本概念

图分为有向图和无向图

弧:类似于树中的边,不过是有方向的边

入度和出度:略

有向完全图:具有n(n - 1)条边的有向图

无向完全图:具有n(n - 1) / 2条边的无向图

路径:相邻顶点序偶所构成的序列

路径长度:路径上面边的个数

简单路径:序列中不重复出现的路径

回路:路径中第一个顶点和最后一个顶点相同

连通图:图中任意两个顶点都连通

极大连通子图:连通图自身就是极大连通子图;非连通图中能包含所有顶点的边构成的连通子图

生成树:包含必不可少的边

权:略

邻接矩阵(重点)

图的顺序存储结构

邻接矩阵中,数组下标表示顶点,0表示顶点不相连,1表示顶点相连

一般将行视为出,列视为入(有向图)

无向图中,邻接矩阵是对称的;对应顶点的度即其所在行 / 列元素之和;矩阵中1的个数是图边数的两倍

有向图中,矩阵中1的个数是图的边数;对应行的元素之和为出度,对应列的元素之和为入度

对于无权图而言,主对角线上的元素统一是0

对于有权图而言,主对角线上的元素统一是0,其他原本为0的设置为∞

// 邻接矩阵的定义
typedef struct{
    int edges[maxsize][maxsize];
    int n , e;			// n代表顶点数,e代表边数
    int vex[maxsize];	// 存放结点信息
}Graph;
邻接表(重点)

图的链式存储结构

每个顶点的邻接顶点信息都采用单链表的形式存储,所有的顶点构成一张邻接表

// 边表
typedef struct ARC{
    int adjvex;				// 此边指向的顶点位置
    struct ARC *nextarc;	// 指向下一条边的指针
}ARC;

// 顶点表
typedef struct{
    int data;				// 顶点信息
    ARC *firstarc;			// 指向第一条边的指针
}VNode;

// 邻接表
typedef struct{
    VNode adj[maxsize];
    int n , e;				// n代表顶点数,e代表边数
}Graph;
深度优先搜索遍历DFS(重点)

类似于树的先序遍历

基本思想:

<1>先访问出发点v,并标记为已被访问过;

<2>选取v的任一邻接点w,访问并标记为已访问过;

<3>再选取w的任一邻接点,重复以上操作;

<4>当此顶点所有的邻接点都被访问时,依次退回到最近访问的顶点,重复这一过程。

// 深度优先遍历
bool visited[maxsize];		// 标记数组
void DFS(Graph G , int v){
    visit(v);			// 访问顶点v
    visited[v] = true;	// 标记顶点v
    for(int w = firstNeighbor(G , v) ; w >= 0 ; w = nextNeighbor(G , v , w))
        if(!visited[w])
            DFS(G , w);		// 递归访问下一未到达的邻接点
}

void DFSTravel(Graph G){
    for(int v = 0 ; v < G.maxvex ; ++v){
        visited[v] = false;		// 初始化标记数组
    }
    for(int v = 0 ; v < G.maxvex ; ++v){
        if(!visited[v])
            DFS(G , v);			// 对图中所有顶点都要DFS一遍
    }
}
广度优先搜索遍历BFS(重点)

类似于树的层次遍历,需要借助一个队列

基本思想:

<1>取一个顶点访问,入队,标记该顶点已经被访问过;

<2>队列不空则循环执行:出队,将该出队顶点所有未被访问的顶点依次访问,标记,并入队;

<3>队列空则跳出循环,BFS遍历结束。

// 广度优先遍历
// 广度优先遍历类似于树的层次遍历,需要借助一个辅助队列Q
bool visited[maxsize];		// 标记数组
void BFS(Graph G , int v){
    visit(v);		// 访问顶点
    visited[v] = true;
    Enqueue(Q , v);		// 顶点v入队
    while(!isEmpty(Q)){
        Dequeue(Q , v);	// 顶点v出队
        for(int w = firstNeighbor(G , v) ; w >= 0 ; w = nextNeighbor(G , v , w)){
            if(!visiter[w]){		// 检查v的所有邻接点
                visit(w);	// 访问顶点w
                visited[w] = true;
                Enqueue(Q , w);
            }
        }
    }
}

void BFSTravel(Graph G){
    for(int i = 0 ; i < G.maxvex ; ++i)
        visited[i] = false;
    Init(Q);		// 初始化辅助队列Q
    for(int i = 0 ; i < G.maxvex ; ++i){
        if(!visited[v])			// 遍历图G中所有的顶点v
            BFS(G , i);
    }
}

例题1:设计算法,求出带权无向连通图G中距离顶点v最远的一个顶点。

// 由BFS算法可知,BFS最后一个遍历的顶点一定是距离当前顶点v最远的一个顶点
int BFS(Graph G , int v){
    int w;
    bool visited[maxsize];				// maxsize是一个宏定义的常数,值为图G的顶点数
    for (int i = 0 ; i < maxsize ; ++i)	// 初始化标记数组
        visited[i] = false;
    Init(Q);							// 初始化一个队列
    visited[v] = true;
    Enqueue(Q , v);						// v被访问且入队
    while(!isEmpty(Q)){
        Dequeue(Q , v);					// 顶点v出队
        for(w = firstNeighbor(G , v) ; w >= 0 ; w = nextNeighbor(G , v , w)){
            if(!visiter[w]){			// 检查v的所有邻接点
                visited[w] = true;
                Enqueue(Q , w);
            }
        }
    }
    return w;							// 当队列为空时,w保存了最后一个被访问的顶点
}

例题2:设计算法,判断无向图G是否是一棵树,是则返回1,否则返回0。

// 如果一个无向图是树,则其一定是有n - 1条边的连通图
// n - 1条边通过图的信息确定,是否连通看一次DFS能否遍历所有顶点作为依据
void DFS(Graph G , int v , int &vex , int &edge){
    int w;
    int visited[maxsize];					// maxsize是一个宏定义的常数,值为图G的顶点数
    for(int i = 0 ; i < maxsize ; ++i)		// 初始化标记数组
        visited[i] = false;
    visited[v] = true;
    ++vex;									// 顶点数自增1
    for(w = firstNeighbor(G , v) ; w >= 0 ; w = nextNeighbor(G , v , w)){
        ++edge;								// 边数自增1
    	if(!visited[w])
        	DFS(G , w);						// 递归访问下一未到达的邻接点
    }
}

int isTree(Graph G){
    int vex = 0;
    int edge = 0;
    DFS(G , 1 , vex , edge);				// 进行一次DFS即可
    
    //此时若顶点数为n且边数为n - 1,则返回1,否则返回0 
    if(vex == maxsize && (maxsize - 1) == (edge / 2))
        return 1;
    else
        return 0;
}
最小生成树(重点)

下面给出两种构建最小生成树的算法:Prim算法和Kruskal算法。

(1)Prim算法

算法思想:

<1>任取一个顶点,视作一棵树,从这棵树相邻的边中选一条最短的边,将边以及相连的顶点并入这棵树中;

<2>再从这棵树相邻的边中选一条最短的边,和上述一样的操作;

<3>重复以上步骤,直到所有的顶点都被并入该树中结束,此时这棵树即最小生成树。

Prim算法时间复杂度:邻接矩阵存储结构下为 O(n的平方)

(2)Kruskal算法

算法思想:

<1>将图中所有的边按权值大小排序;

<2>从最小的边开始并入树中,如果构成回路则不并入;

<3>重复以上步骤,直到所有的边都已经被检测完为止。

Kruskal算法时间复杂度:取决于选取的排序算法的时间性能,因此适用于稀疏图

最短路径(重点)

下面给出两种寻找最短路径的算法:Dijkstra算法和Floyd算法。

(1)Dijkstra算法

基于贪心算法,求出特定顶点到其余各顶点的最短路径。注意,Dijkstra算法并不适用于图边上带有负权值时的情况

算法思想:

<1>先将出发点的相邻边进行比较,最短的边被划入,并将这个点算进最短路径;

<2>从这个最新划入的点开始,比较其相邻边,这里比较 A -> C 和 A -> B-> C的距离,最短的则划入最短路径中;

<3>重复上述步骤。

Dijkstra算法计算完成后,会形成了三个表,分别记录:

<1>是否已访问;

<2>出发点到对应顶点的距离;

<3>最短路径。

(2)Floyd算法

求出任意一对顶点间的最短路径

算法思想:三重循环,k为中转点,v为起点,w为终点。如果D[v] [k] + D[k] [w]为更小的长度,则将D[v] [k] + D[k] [w]覆盖掉D[v] [w]中。

算法通过不断的更新D1,D2,…等表,最终得出一个可以判断两两顶点最短路径的表。

建表规律:沿对角线元素依次建表,每个表无穷元素和对角元素填自己,剩下的则比较相交走小一点还是原本的值小一点,保留最小的值即可

拓扑排序

AOV网:反映一个工程各个活动之间的先后次序关系的有向图

AOV网没有回路

对于有向无环图G,拓扑排序后,任意一对顶点u和v,只要存在u到v的路径,则拓扑排序中u一定在v的前面

算法思想:

<1>从有向图中选一个没有前驱的顶点输出;

<2>删除此顶点,并删除从该顶点出发的所有边;

<3>重复以上步骤,直到图中不存在没有前驱的顶点为止。

注意:拓扑排序可能不唯一

逆拓扑排序

拓扑排序考虑的是顶点的入度问题

逆拓扑排序考虑的是顶点的出度问题

算法思想:

<1>在网中选择一个没有后继的顶点输出;

<2>删除此顶点,并删除所有到达该顶点的边;

<3>重复上述步骤,直到网中不存在出度为0的顶点为止。

关键路径

AOE网(Activity On Edge):边表示活动,顶点表示结束

关键路径:边上活动相加最大的一条路径。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值