数据结构与算法学习笔记-8.图

8.图

8.1 图的定义

图论(Graph Theory)是离散数学的一个分支,是一门研究图(Graph)的学问。

图是用来对对象之间的成对关系建模的数学结构,由"节点"或"顶点"(Vertex)以及连接这些顶点的"边"(Edge)组成。

图(Graph)是由顶点的有穷非空集合和顶点之间的边的集合组成的,通常表示为G(V, E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

对于图的定义,我们需要注意:

  1. 线性表中我们把数据元素叫元素,树中将数据元素叫结点,在图中,数据元素,我们称之为顶点(Vertex)。
  2. 线性表中可以没有数据元素,称为空表。树中可以没有结点,叫做空树。但在图中,不允许没有顶点。在定义中,若V是顶点的集合,则强调了顶点集合V有穷非空。
  3. 线性表中,相邻的数据元素之间具有线性关系,树结构中,相邻两层的结点具有层次关系,而在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

值得注意的是,**图的顶点集合不能为空,但边的集合可以为空。图可能是无向的,这意味着图中的边在连接顶点时无需区分方向。否则,称图是有向的。**下面左图是一个典型的无向图结构,右图则属于有向图。本章节介绍的图都是无向图。

img

图的分类:无权图和有权图,连接节点与节点的边是否有数值与之对应,有的话就是有权图,否则就是无权图。

**图的连通性:**在图论中,连通图基于连通的概念。在一个无向图 G 中,若从顶点 i 到顶点 j 有路径相连(当然从j到i也一定有路径),则称 i 和 j 是连通的。如果 G 是有向图,那么连接i和j的路径中所有的边都必须同向。如果图中任意两点都是连通的,那么图被称作连通图。如果此图是有向图,则称为强连通图(注意:需要双向都有路径)。图的连通性是图的基本性质。

**完全图:**完全是一个简单的无向图,其中每对不同的顶点之间都恰连有一条边相连。

**自环边:**一条边的起点终点是一个点。

**平行边:**两个顶点之间存在多条边相连接。


各种概念术语:

无向边:若顶点Vi到Vj之间的边没有方向,则称这条边为无向边(Edge),用无序偶对(Vi,Vj)来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。

有向边:若顶点Vi到Vj之间的边有方向,则称这条边为有向边,也称为弧(Arc),用0有序偶对<Vi,Vj>来表示。如果图中任意两个顶点之间的边都是有向边,则称该图为有向图。

​ 在图中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图。

​ 在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图

​ 在有向图中,如果任意两个顶点之间都存在方向相反的两条弧,则称该图为有向完全图

​ 有很少条边或弧的图称为稀疏图,反之称为稠密图。

顶点的度:与该顶点相关联的边的数目。在有向图中,顶点的度等于该顶点的入度和出度之和。顶点v的入度是以v为终点的有向边的条数。顶点的出度是以v为起点的有向边的条数。

在这里插入图片描述

网:

有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权(weight)。这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网。

子图:

假设有两个图G=(V,{E})、G1=(V1,{E1}),若V1包含于V,E1包含于E,则称G1是G的子图。

在这里插入图片描述

连通图:

在无(有)向图G=(V,{E})中,若对任何两个顶点v,u都存在从v到u的路径,则称G是连通图(强连通图)。
在这里插入图片描述

连通分量:(强连通分量)

无向图G的极大连通子图称为G的连通分量。

极大连通子图意思是:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。

在这里插入图片描述

有向图G的极大强连通子图称为G的强连通分量。

极大强连通子图意思是:该子图是G的强连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。
在这里插入图片描述

极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,该子图不再连通。

生成树:包含无向图G所有顶点的极小连通图。

生成森林:对非连通图,由各个连通分量的生成树的集合。

在这里插入图片描述

图的总结:

在这里插入图片描述

8.2 图的储存结构

1.邻接矩阵

​ 图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

在这里插入图片描述

图的表达方式:

**邻接矩阵:**1 表示相连接,0 表示不相连。

img

在这里插入图片描述

2.邻接表

**邻接表:**只表达和顶点相连接的顶点信息

img

邻接表适合表示稀疏图 (Sparse Graph)

邻接矩阵适合表示稠密图 (Dense Graph)
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.十字链表

在这里插入图片描述

​ 十字链表是有向图的另一种链式存储结构。我们也可以把它看成是将有向图的邻接表和逆邻接表结合起来形成的一种链表。

​ 有向图中的每一条弧对应十字链表中的一个弧结点,同时有向图中的每个顶点在十字链表中对应有一个结点,叫做定点结点。

在这里插入图片描述

4.邻接多重表

在这里插入图片描述

在这里插入图片描述

8.3 图的遍历方式

遍历定义:

​ 从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算。

图的特点:

​ 图中可能存在回路,且图中的任一顶点都可能与其它顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。

​ 怎么避免重复访问:

解决思路:设置辅助数组visited[n],用来标记每个被访问过的顶点。

  • 初始状态为:visited[i] = 0;
  • 顶点i被访问,改为visited[i] = 1,防止被多次访问。
8.3.1 深度优先遍历

​ 深度优先遍历(Depth First Search) ,也有称为深度优先搜索,简称为DFS。
深度优先遍历可定义如下:首先访问出发点v,并将其标记为已访问过;然后依次从v出发搜索v的每个邻接点w。若w未曾访问过,则以w为新的出发点继续进行深度优先遍历,直至图中所有和源点v有路径相通的顶点均已被访问为止。若此时图中仍有未访问的顶点,则另选一个尚未访问的顶点为新的源点重复上述过程,直至图中所有的顶点均已被访问为止。

​ 深度优先遍历创建步骤(Depth First Search)
1.访问初始节点 v,并标记为已访问

2.查找结点v的第一个邻接点 w

3.若w存在 则执行4 若不存在回到第一步 再找 v结点的下一个结点

4.判断w是否被访问 未访问对w深度优先遍历(即把w当作另一个v ,再进行1 2 3);

5.查找v 在w 后面是否还有下一个邻接点 再执行步骤3

邻接矩阵表示的无向图深度遍历实现:

在这里插入图片描述

算法代码:

//深度优先遍历  邻接矩阵
#include <iostream>
using namespace std;

#define MaxVnum 100  //顶点数最大值
bool visited[MaxVnum];  //访问标志数组,其初值为"false"
typedef char VexType;  //顶点的数据类型,根据需要定义
typedef int EdgeType;  //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct {
    VexType Vex[MaxVnum];
    EdgeType Edge[MaxVnum][MaxVnum];
    int vexnum, edgenum; //顶点数,边数
}AMGragh;

int locatevex(AMGragh G, VexType x)
{
    for (int i = 0; i < G.vexnum; i++)//查找顶点信息的下标
        if (x == G.Vex[i])
            return i;
    return -1;//没找到
}

void CreateAMGraph(AMGragh& G)//创建无向图的邻接矩阵
{
    int i, j;
    VexType u, v;
    cout << "请输入顶点数:" << endl;
    cin >> G.vexnum;
    cout << "请输入边数:" << endl;
    cin >> G.edgenum;
    cout << "请输入顶点信息:" << endl;
    for (int i = 0; i < G.vexnum; i++)//输入顶点信息,存入顶点信息数组
        cin >> G.Vex[i];
    for (int i = 0; i < G.vexnum; i++)//初始化邻接矩阵所有值为0,如果是网,则初始化邻接矩阵为无穷大
        for (int j = 0; j < G.vexnum; j++)
            G.Edge[i][j] = 0;
    cout << "请输入每条边依附的两个顶点:" << endl;
    while (G.edgenum--)
    {
        cin >> u >> v;
        i = locatevex(G, u);//查找顶点u的存储下标
        j = locatevex(G, v);//查找顶点v的存储下标
        if (i != -1 && j != -1)
            G.Edge[i][j] = G.Edge[j][i] = 1; //邻接矩阵储置1,若有向图G.Edge[i][j]=1
        else
        {
            cout << "输入顶点信息错!请重新输入!" << endl;
            G.edgenum++;//本次输入不算
        }
    }
}

void print(AMGragh G)//输出邻接矩阵
{
    cout << "图的邻接矩阵为:" << endl;
    for (int i = 0; i < G.vexnum; i++)
    {
        for (int j = 0; j < G.vexnum; j++)
            cout << G.Edge[i][j] << "\t";
        cout << endl;
    }
}

void DFS_AM(AMGragh G, int v)//基于邻接矩阵的深度优先遍历
{
    int w;
    cout << G.Vex[v] << "\t";
    visited[v] = true;
    for (w = 0; w < G.vexnum; w++)//依次检查v的所有邻接点
    {
        if (G.Edge[v][w] && !visited[w])//v,w邻接且w未被访问
        {
            DFS_AM(G, w);//从w顶点开始递归深度优先遍历
        }
    }
}

//如果不是一个连通图,还需要进行验证.
void DFS_AM(AMGragh G)
{
    for (int i = 0; i < G.vexnum; i++)//检查未被访问的顶点
    {
        if (!visited[i])
        {
            DFS_AM(G, i);
        }
    }
}

int main()
{
    int v;
    VexType c;
    AMGragh G;
    CreateAMGraph(G);
    print(G);
    cout << "请输入遍历连通图的起始点: ";
    cin >> c;
    v = locatevex(G, c);//查找顶点u的存储下标
    if (v != -1)
    {
        cout << "深度优先搜索遍历连通图结果: " << endl;
        DFS_AM(G);
    }
    else {
        cout << "输入顶点信息错误!请重新输入!" << endl;
    }
    return 0;
}

//深度优先遍历  邻接表
#include <iostream>
using namespace std;

const int MaxVnum = 100;//顶点数最大值
bool visited[MaxVnum];  //访问标志数组,其初值为"false"
typedef char VexType;//顶点的数据类型为字符型

typedef struct AdjNode { //定义邻接点类型
    int v; //邻接点下标
    struct AdjNode* next; //指向下一个邻接点
}AdjNode;

typedef struct VexNode { //定义顶点类型
    VexType data; // VexType为顶点的数据类型,根据需要定义
    AdjNode* first; //指向第一个邻接点
}VexNode;

typedef struct {//定义邻接表类型
    VexNode  Vex[MaxVnum];
    int vexnum, edgenum; //顶点数,边数
}ALGragh;

int locatevex(ALGragh G, VexType x)
{
    for (int i = 0; i < G.vexnum; i++)//查找顶点信息的下标
        if (x == G.Vex[i].data)
            return i;
    return -1;//没找到
}

void insertedge(ALGragh& G, int i, int j)//插入一条边
{
    AdjNode* s;
    s = new AdjNode;
    s->v = j;
    s->next = G.Vex[i].first;
    G.Vex[i].first = s;
}

void printg(ALGragh G)//输出邻接表
{
    cout << "----------邻接表如下:----------" << endl;
    for (int i = 0; i < G.vexnum; i++)
    {
        AdjNode* t = G.Vex[i].first;
        cout << G.Vex[i].data << ": ";
        while (t != NULL)
        {
            cout << "---->";
            cout << "[" << G.Vex[t->v].data << "]";
            t = t->next;
        }
        cout << endl;
    }
}
void CreateALGraph(ALGragh& G)//创建无向图邻接表
{
    int i, j;
    VexType u, v;
    cout << "请输入顶点数和边数:" << endl;
    cin >> G.vexnum >> G.edgenum;
    cout << "请输入顶点信息:" << endl;
    for (i = 0; i < G.vexnum; i++)//输入顶点信息,存入顶点信息数组
        cin >> G.Vex[i].data;
    for (i = 0; i < G.vexnum; i++)
        G.Vex[i].first = NULL;
    cout << "请依次输入每条边的两个顶点u,v" << endl;
    while (G.edgenum--)
    {
        cin >> u >> v;
        i = locatevex(G, u);//查找顶点u的存储下标
        j = locatevex(G, v);//查找顶点v的存储下标
        if (i != -1 && j != -1)
        {
            insertedge(G, i, j);
            //insertedge(G, j, i);//无向图多插入一条边
        }
        else
        {
            cout << "输入顶点信息错!请重新输入!" << endl;
            G.edgenum++;//本次输入不算
        }
    }
}

void DFS_AL(ALGragh G, int v)//基于邻接表的深度优先遍历
{
    int w;
    AdjNode* p;//辅助指针
    cout << G.Vex[v].data << "\t";
    visited[v] = true;
    p = G.Vex[v].first;//获取该顶点的第一个邻接点
    while (p)//依次检查v的所有邻接点
    {
        w = p->v;//w为v的邻接点.
        if (!visited[w])//w未被访问,则从w出发,递归深度优先遍历
        {
            DFS_AL(G, w);
        }
        p = p->next;
    }
}

void DFS_AL(ALGragh G) //非连通图,基于邻接表的深度优先遍历
{
    for(int i = 0; i < G.vexnum; i++)//检查未被访问的点.
        if (!visited[i])
        {
            DFS_AL(G, i);
        }
}

int main()
{
    ALGragh G;
    int v;
    VexType c;
    CreateALGraph(G);//创建有向图邻接表
    printg(G);//输出
    cout << "请输入遍历连通图的起始点: ";
    cin >> c;
    v = locatevex(G, c);
    if (v != -1)
    {
        cout << "深度优先搜索遍历连通图结果: " << endl;
        DFS_AL(G, v);
        DFS_AL(G);
    }
    else
    {
        cout << "输入顶点信息错误!请重新输入!" << endl;
    }
    return 0;
}

DFS算法效率分析:

  • 用邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在行,时间复杂度为O(n^2).
  • 用邻接表来表示图,虽然有2e个表结点,但只需扫描e个结点即可完成遍历,加上访问n个头结点的时间,时间复杂度为O(n + e)。

结论:稠密图适于在邻接矩阵上进行深度遍历;稀疏图适于在邻接表上进行深度遍历。

​ 对于非连通图:只需要对它的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次深度优先遍历后,若图中尚有顶点未被访问,则选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

8.3.2 广度优先遍历

​ 广度优先遍历(Breadth First Search),又称为广度优先搜索,简称BFS。
广度优先遍历可定义如下:首先访问出发点v,接着依次访问v的所有邻接点w1、w2…wt,然后依次访问w1、w2…wt邻接的所有未曾访问过的顶点。以此类推,直至图中所有和源点v有路径相通的顶点都已访问到为止。此时从v开始的搜索过程结束。

创建的步骤 :

广度优先遍历步骤
(需要用到队列来实现对 顺序的访问他的所有邻接点)

  1. 访问初始节点 v,并标记为已访问

  2. 结点v 入队列

  3. 当队列非空时 继续执行,否则结束

  4. 出队列 ,取得头结点u

  5. 查找结点u 的第一个邻接点w

  6. 若结点u 邻接点不存在 则执行 步骤3 否则循环执行以下三个步骤

​ 6.1若结点w 未被访问 则访问节点w 并标记为已访问

​ 6.2结点w 入队

​ 6.3查找结点u的 除了w 之后的下一个其他邻接点

​ 对于非连通图:只需要对它的连通分量分别进行广度优先遍历,即在先前一个顶点进行一次广度优先遍历后,若图中尚有顶点未被访问,则选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

广度优先遍历邻接矩阵代码:

#include <iostream>
#include <queue>//引入队列头文件
using namespace std;

#define MaxVnum 100  //顶点数最大值
bool visited[MaxVnum];  //访问标志数组,其初值为"false"
typedef char VexType;  //顶点的数据类型,根据需要定义
typedef int EdgeType;  //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct {
    VexType Vex[MaxVnum];
    EdgeType Edge[MaxVnum][MaxVnum];
    int vexnum, edgenum; //顶点数,边数
}AMGragh;

int locatevex(AMGragh G, VexType x)
{
    for (int i = 0; i < G.vexnum; i++)//查找顶点信息的下标
        if (x == G.Vex[i])
            return i;
    return -1;//没找到
}

void CreateAMGraph(AMGragh& G)//创建有向图的邻接矩阵
{
    int i, j;
    VexType u, v;
    cout << "请输入顶点数:" << endl;
    cin >> G.vexnum;
    cout << "请输入边数:" << endl;
    cin >> G.edgenum;
    cout << "请输入顶点信息:" << endl;
    for (int i = 0; i < G.vexnum; i++)//输入顶点信息,存入顶点信息数组
        cin >> G.Vex[i];
    for (int i = 0; i < G.vexnum; i++)//初始化邻接矩阵所有值为0,如果是网,则初始化邻接矩阵为无穷大
        for (int j = 0; j < G.vexnum; j++)
            G.Edge[i][j] = 0;
    cout << "请输入每条边依附的两个顶点:" << endl;
    while (G.edgenum--)
    {
        cin >> u >> v;
        i = locatevex(G, u);//查找顶点u的存储下标
        j = locatevex(G, v);//查找顶点v的存储下标
        if (i != -1 && j != -1)
            G.Edge[i][j] = G.Edge[j][i] = 1; //邻接矩阵储置1,若无向图G.Edge[i][j]=G.Edge[j][i]=1
        else
        {
            cout << "输入顶点信息错!请重新输入!" << endl;
            G.edgenum++;//本次输入不算
        }
    }
}

void print(AMGragh G)//输出邻接矩阵
{
    cout << "图的邻接矩阵为:" << endl;
    for (int i = 0; i < G.vexnum; i++)
    {
        for (int j = 0; j < G.vexnum; j++)
            cout << G.Edge[i][j] << "\t";
        cout << endl;
    }
}

void BFS_AM(AMGragh G, int v)
{
    int u, w;
    queue<int> Q;//创建一个队列,里面存放节点的下标
    cout << G.Vex[v] << "\t";
    visited[v] = true;
    Q.push(v);//源点v入队
    while (!Q.empty())
    {
        u = Q.front();//取对头
        Q.pop();
        for (w = 0; w < G.vexnum; w++)//一次检查u的所有邻接点
        {
            if (G.Edge[u][w] && !visited[w])//是邻接点但没有访问则进行访问
            {
                cout << G.Vex[w] << "\t";
                visited[w] = true;
                Q.push(w);
            }
        }
    }
}

void BFS_AM(AMGragh G){
	for(int i = 0; i < G.vexnum; i++){
		if(!visited[i]){
			BFS_AM(G,i); 
		}
	}
}

int main()
{
    int v;
    VexType c;
    AMGragh G;
    CreateAMGraph(G);
    print(G);
    cout << "请输入遍历连通图的起始点: ";
    cin >> c;
    v = locatevex(G, c);//查找顶点u的存储下标
    if (v != -1)
    {
        cout << "广度优先搜索遍历连通图结果: " << endl;
        BFS_AM(G, v);
        BFS_AM(G);
    }
    else {
        cout << "输入顶点信息错! 请重新输入!" << endl;
    }
    return 0;
}

广度优先遍历邻接表代码:

#include <iostream>
#include <queue>//引入队列头文件
using namespace std;

const int MaxVnum = 100;//顶点数最大值
bool visited[MaxVnum];  //访问标志数组,其初值为"false"
typedef char VexType;//顶点的数据类型为字符型

typedef struct AdjNode { //定义邻接点类型
    int v; //邻接点下标
    struct AdjNode* next; //指向下一个邻接点
}AdjNode;

typedef struct VexNode { //定义顶点类型
    VexType data; // VexType为顶点的数据类型,根据需要定义
    AdjNode* first; //指向第一个邻接点
}VexNode;

typedef struct {//定义邻接表类型
    VexNode  Vex[MaxVnum];
    int vexnum, edgenum; //顶点数,边数
}ALGragh;

int locatevex(ALGragh G, VexType x)
{
    for (int i = 0; i < G.vexnum; i++)//查找顶点信息的下标
        if (x == G.Vex[i].data)
            return i;
    return -1;//没找到
}

void insertedge(ALGragh& G, int i, int j)//插入一条边
{
    AdjNode* s;
    s = new AdjNode;
    s->v = j;
    s->next = G.Vex[i].first;
    G.Vex[i].first = s;
}

void printg(ALGragh G)//输出邻接表
{
    cout << "----------邻接表如下:----------" << endl;
    for (int i = 0; i < G.vexnum; i++)
    {
        AdjNode* t = G.Vex[i].first;
        cout << G.Vex[i].data << ": ";
        while (t != NULL)
        {
            cout << "---->";
            cout << "[" << G.Vex[t->v].data << "]";
            t = t->next;
        }
        cout << endl;
    }
}

void CreateALGraph(ALGragh& G)//创建有向图邻接表
{
    int i, j;
    VexType u, v;
    cout << "请输入顶点数和边数:" << endl;
    cin >> G.vexnum >> G.edgenum;
    cout << "请输入顶点信息:" << endl;
    for (i = 0; i < G.vexnum; i++)//输入顶点信息,存入顶点信息数组
        cin >> G.Vex[i].data;
    for (i = 0; i < G.vexnum; i++)
        G.Vex[i].first = NULL;
    cout << "请依次输入每条边的两个顶点u,v" << endl;
    while (G.edgenum--)
    {
        cin >> u >> v;
        i = locatevex(G, u);//查找顶点u的存储下标
        j = locatevex(G, v);//查找顶点v的存储下标
        if (i != -1 && j != -1)
            insertedge(G, i, j);
        else
        {
            cout << "输入顶点信息错!请重新输入!" << endl;
            G.edgenum++;//本次输入不算
        }
    }
}

void BFS_AL(ALGragh G, int v)//基于邻接表的广度优先遍历
{
    int u, w;
    AdjNode* p;
    queue<int> Q;
    cout << G.Vex[v].data << "\t";
    visited[v] = true;
    Q.push(v);
    while (!Q.empty())
    {
        u = Q.front();//取对头元素
        Q.pop();
        p = G.Vex[u].first;
        while (p)
        {
            w = p->v;
            if (!visited[w])//w未被访问
            {
                cout << G.Vex[w].data << "\t";
                visited[w] = true;
                Q.push(w);
            }
            p = p->next;
        }
    }
}

void BFS_AL(ALGragh G)//非连通图,基于邻接表的广度优先遍历
{
    for (int i = 0; i < G.vexnum; i++)//查漏点.
    {
        if (!visited[i])//未被访问则进行访问
        {
            BFS_AL(G, i);
        }
    }
}

int main()
{
    ALGragh G;
    int v;
    VexType c;
    CreateALGraph(G);//创建有向邻接表
    printg(G);//输出
    cout << "请输入遍历连通图的起始点: ";
    cin >> c;
    v = locatevex(G, c);//查找顶点u的存储下标
    if (v != -1)
    {
        cout << "广度优先搜索遍历连通图结构: " << endl;
        BFS_AL(G, v);
        BFS_AL(G);
    }
    else
    {
        cout << "输入顶点错!请重新输入!" << endl;
    }
    return 0;
}

BFS算法效率分析:

  • 基于邻接矩阵的BFS算法

    查找每个顶点的邻接点需要O(n)时间,一共n个顶点,总的时间复杂度为O(n^2),使用了一个辅助队列,最坏的情况下每个顶点入队一次(访问完就入队),空间复杂度为O(n)。

  • 基于邻接表的BFS算法

    查找顶点vi的邻接点需要O(d(vi))时间,d(vi)为vi的出度(无向图为度),对有向图而言,所有顶点的出度之和等于边数e,对无向图而言,所有顶点的度之和等于2e,因此查找邻接点的时间复杂度为O(e),加上初始化时间O(n),总的时间复杂度为O(n+e),使用了一个辅助队列,最坏的情况下每个顶点入队一次,空间复杂度为O(n)。

8.4 图的应用

8.4.1 最小生成树

生成树:所有顶点均由边连接在一起,但不存在回路的图。

在这里插入图片描述

一个图可以有许多棵不同的生成树

所有生成树具有以下共同特点:

  • 生成树的顶点个数与图的顶点个数相同;
  • 生成树是图的极小连通子图,去掉一条边则非连通;
  • 一个有n个顶点的连通图的生成树有n - 1条边;
  • 在生成树中再加一条边必然形成回路;
  • 生成树中任意两个顶点间的路径是唯一的;
  • 含n个顶点n-1条边的图不一定是生成树。

无向图的生成树:

在这里插入图片描述

在这里插入图片描述

无向网的生成树:

在这里插入图片描述

​ **最小生成树:**给定一个无向网络,在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树,也叫最小代价生成树。

构造最小生成树(Minimum Spanning Tree):

构造最小生成树的算法很多,其中多数算法都利用了MST性质。

在这里插入图片描述

构造最小生成树方法一:普利姆(Prim)算法

算法思想:

在这里插入图片描述

构造最小生成树方法二:克鲁斯卡尔(Kruskal)算法

算法思想:(此方法构造出的最小生成树可能不唯一)

在这里插入图片描述

两种构造算法的比较:

在这里插入图片描述

8.4.2 最短路径

​ 问题抽象:有向网中A点(源点)到达B点(终点)的多条路径中,寻找一条各边权值之和最小的路径,即最短路径。

​ 最短路径与最小生成树不同,路径上不一定包含n个顶点,也不一定包含n-1条边。

1、单源最短路径-用Dijkstra(迪杰斯特拉)算法

2、所有顶点间的最短路径-用Floyd(弗洛伊德)算法


Dijistra算法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


弗洛伊德(Floyd)算法(时间复杂度n^3)

算法思想:逐个顶点试探、从vi的vj的所有可能存在的路径、选出一条长度最短的路径

在这里插入图片描述

8.4.3 拓扑排序
有向无环图及其应用

有向无环图:无环的有向图,简称DAG(Directed Acyclic Graph)


AOV网: 拓扑排序

​ 用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称AOV网(Activity On Vertex network).


拓扑排序:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


8.4.4 关键路径

AOE网: 关键路径

​ 用一个有向图表示一个工程的各子工程及其相互制约的关系,以弧表示活动以顶点表示活动的开始或结束事件,称这种有向图为边表示活动的网,简称AOE网(Activity On Edge)。

关键路径:

​ 把工程计划表示为边表示活动的网络,即AOE网,用顶点表示事件,弧表示活动,弧的权表示活动持续时间。

事件表示它之前的活动已经完成,在它之后的活动可以开始。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值