数据结构(C语言版)-- 图笔记


1、图的定义和基本术语

①.图的定义

图(Graph )G由两个集合V和E组成,记为G=(V,E),其中V是顶点的有穷非空集合E是V中顶点偶对的有穷集合,这些顶点偶对称为边。V(G)和E(G)通常分别表示图G的顶点集合和边集合,E(G)可以为空集。若E(G)为空,则图G只有顶点而没有边。

有向图:

每条边都是有方向的,边也称作弧,如G1

无向图:

每条边都是无方向的,如G2

数据结构(C语言版)-- 图笔记

②.图的基本术语

设n表示图中顶点数目,e表示边的数目

完全图:任意两个点都有一条边相连

数据结构(C语言版)-- 图笔记

稀疏图:如果边或弧的个数满足e < n log2n ,则称作稀疏图,否则称作稠密图

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

数据结构(C语言版)-- 图笔记

权与网:图中边或弧所具有的数称为权。表明从一个顶点到另一个顶点的距离或耗费。带权的图称为网。

对无向图来说:
邻接点:若顶点v和顶点w之间存在一条边a,则称顶点v和w互为邻接点。边a与顶点v和w相关联。
:与顶点v关联的边的数目,记为TD(v)

对有向图来说:
<x,y>为有向边(弧),
x为有向边的起点(弧尾),y为有向边的终点(弧头)
顶点v的入度是以v为终点的有向边的条数,记作ID(v)
顶点v的出度是以v为始点的有向边的条数,记作OD(v)

路径:接续的边构成的顶点序列。

路径长度:路径上边或弧的数目。

简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径。

简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径.

数据结构(C语言版)-- 图笔记

若无向图中任意两个顶点之间都有路径相通,则称此图为连通图(G1);若无向图为非连通图,则图中各个连通子图称作此图的连通分量(G2)。

若有向图中任意两个顶点之间都存在一条有向路径,则称此有向图为强连通图,否则,其各个强连通子图称作它的强连通分量

数据结构(C语言版)-- 图笔记


2、图的类型定义

图的抽象数据类型定义:

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记


3、图的存储结构

由于图的结构比较复杂,任意两个顶点之间都可能存在联系,因此无法以数据元素在存储区中的物理位置来表示元素之间的关系,即图没有顺序存储结构,但可以借助二维数组来表示元素之间的关系,即邻接矩阵表示法。另一方面,由于图的任意两个顶点间都可能存在关系,因此,用链式存储表示图是很自然的事,图的链式存储有多种,有邻接表、十字链表和邻接多重表,应根据实际需要的不同选择不同的存储结构。

①.邻接矩阵

邻接矩阵表示法

邻接矩阵是表示顶点之间相邻关系的矩阵。设G(V,E)是具有n个顶点的图,则G的邻接矩阵是具有如下性质的n阶方阵。

数据结构(C语言版)-- 图笔记

数据结构(C语言版)-- 图笔记

在有向图的邻接矩阵中,

  • 第i行含义︰以结点v;为起点的边(即出度边);
  • 第i列含义:以结点v;为终点的边(即入度边)。

有向图的邻接矩阵可能是不对称的。

  • 顶点的出度-第i行元素之和
  • 顶点的入度-第i列元素之和
  • 顶点的度-第i行元素之和+第i列元素之和

数据结构(C语言版)-- 图笔记

无向图的邻接矩阵是对称的;

  • 顶点i的度=第i 行(列)中1的个数;
  • 图的边数=所有非0元素之和的一半;
  • 完全图的邻接矩阵中,对角元素为0,其余1。

若G是网,则邻接矩阵可以定义为:

数据结构(C语言版)-- 图笔记

其中,wi,j表示边上的权值;∞表示计算机允许的、大于所有边上权值的数。

数据结构(C语言版)-- 图笔记
邻接矩阵表示法的特点

  • 优点:容易实现图的操作。
    如:求某顶点的度、判断顶点之间是否有边、找顶点的邻接点…
  • 缺点:n个顶点需要n*n个单元存储边;空间效率为O(n2)。
    对稀疏图而言尤其浪费空间。
//图的邻接矩阵存储表示
#define MaxInt 32767     //表示极大值,即∞
#define MVNum 100        //最大顶点数
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType;     //假设边的权值类型为整型
typedef struct
{
    VerTexType vexs[MVNum];     //顶点表
    ArcType arcs[MVNum][MVNum]; //邻接矩阵
    int vexnum, arcnum;         //图的当前顶点数和边数
} AMGraph;

采用邻接矩阵表示法创建无向网

  1. 输入总顶点数和总边数。
  2. 依次输入点的信息存入顶点表中。
  3. 初始化邻接矩阵,使每个权值初始化为极大值。
  4. 构造邻接矩阵。依次输人每条边依附的顶点和其权值,确定两个顶点在图中的位置之后,使相应边赋予相应的权值,同时使其对称边赋予相同的权值。
Status CreateUDN(AMGraph &G)
{                                //采用邻接矩阵表示法,创建无向网G
    cin >> G.vexnum >> G.arcnum; //输入总顶点数,总边数
    for (i = 0; i < G.vexnum; ++i)
        cin >> G.vexs[i];          //依次输入点的信息
    for (i = 0; i < G.vexnum; ++i) //初始化邻接矩阵,边的权值均置为极大值
        for (j = 0; j < G.vexnum; ++j)
            G.arcs[i][j] = MaxInt;
    for (k = 0; k < G.arcnum; ++k) //构造邻接矩阵
    {
        cin >> v1 >> v2 >> w; //输入一条边依附的顶点及权值
        i = LocateVex(G, v1);
        j = LocateVex(G, v2);        //确定v1和v2在G中的位置
        G.arcs[i][j] = w;            //边<v1, v2>的权值置为w
        G.arcs[j][i] = G.arcs[i][j]; //置<v1, v2>的对称边<v2, v1>的权值为w
    }                                // for
    return OK;
} // CreateUDN

该算法的时间复杂度是O(n2)

若要建立无向图,只需对上述算法做两处小的改动:一是初始化邻接矩阵时,将边的权值均初始化为0;二是构造邻接矩阵时,将权值w改为常量值1即可。同样,将该算法稍做修改即可建立一个有向网或有向图。

②.邻接表

邻接表(Adjacency List)是图的一种链式存储结构。在邻接表中,对图中每个顶点v;建立一个单链表,把与v相邻接的顶点放在这个链表中。邻接表中每个单链表的第一个结点存放有关顶点的信息,把这一结点看成链表的表头,其余结点存放有关边的信息,这样邻接表便由两部分组成:表头结点表和边表。

数据结构(C语言版)-- 图笔记

无向图的邻接表表示

数据结构(C语言版)-- 图笔记

有向图的邻接表表示

数据结构(C语言版)-- 图笔记

//图的邻接表存储表示
#define MVNum 100 //最大顶点数
typedef struct ArcNode
{
    int adjvex;              // 该边所指向的顶点的位置
    struct ArcNode *nextarc; // 指向下一条边的指针
    OtherInfo info;          // 和边相关的信息
} ArcNode;
typedef struct VNode
{
    VertexType data;     // 顶点信息
    ArcNode *firstarc;   // 指向第一条依附该顶点的边
} VNode, AdjList[MVNum]; // AdjList表示邻接表类型
typedef struct
{
    AdjList vertices;   // vertices—vertex的复数
    int vexnum, arcnum; //图的当前顶点数和边数
} ALGraph;

采用邻接表表示法创建无向图

  1. 输人总顶点数和总边数。
  2. 依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL。
  3. 创建邻接表。依次输入每条边依附的两个顶点,确定这两个顶点的序号i和j之后,将此边结点分别插入vi和vj对应的两个边链表的头部。
Status CreateUDG(ALGraph &G)
{                                //采用邻接表表示法,创建无向图G
    cin >> G.vexnum >> G.arcnum; //输入顶点数和弧数
    for (i = 0; i < G.vexnum; ++i)
    {                                  //输入各点,构造表头结点表
        cin >> G.vertices[i].data;     //输入顶点值
        G.vertices[i].firstarc = NULL; //初始化表头结点的指针域为NULL
    }
    for (k = 0; k < G.arcnum; ++k) //输入各边,构造邻接表,头插法
    {
        cin >> v1 >> v2; //输入一条边依附的两个顶点
        i = LocateVex(G, v1);
        j = LocateVex(G, v2);
        p1 = new ArcNode; //生成一个新的边结点*p1
        p1->adjvex = j;   //邻接点序号为j
        p1->nextarc = G.vertices[i].firstarc;
        G.vertices[i].firstarc = p1;          //将新结点*p1插入到顶点vi的边表头部
        p2 = new ArcNode;                     //生成一个新的边结点*p2
        p2->adjvex = i;                       //邻接点序号为j
        p2->nextarc = G.vertices[j].firstarc; //插入弧结点到单链表
        G.vertices[j].firstarc = p2;          //将新结点*p2插入到顶点vj的边表头部
    }                                         //头插法
    return OK;
} // CreateUDG

该算法的时间复杂度是O(n+e)

建立有向图的邻接表与此类似,只是更加简单,每读入一个顶点对序号<i,j>,仅需生成一个邻接点序号为j的边表结点,并将其插入到v的边链表头部即可。若要创建网的邻接表,可以将边的权值存储在info域中。

邻接矩阵与邻接表的比较

数据结构(C语言版)-- 图笔记

例1:

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记

例2:

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记

④.十字链表

十字链表(Orthogonal List)是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。在十字链表中,对应于有向图中每一条弧有一个结点,对应于每个顶点也有一个结点。

在弧结点中有5个域:其中尾域( tailvex )和头域( headvex )分别指示弧尾和弧头这两个顶点在图中的位置,链域hlink指向弧头相同的下一条弧,而链域tlink 指向弧尾相同的下一条弧,info域指向该弧的相关信息。弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上。它们的头结点即为顶点结点,它由3个域组成:其中 data域存储和顶点相关的信息,如顶点的名称等;firstin和 firstout为两个链域,分别指向以该顶点为弧头或弧尾的第一个弧结点。

名称 解释
tailvex 指示弧尾顶点在图中的位置。
headtex 指示弧头顶点在图中的位置。
hlink 是指向弧头相同的下一条弧的指针。
tlink 是指向弧尾相同的下一条弧的指针。
Info 指向该弧的相关信息。

数据结构(C语言版)-- 图笔记

//有向图的十字链表存储表示
#define MAX_VERTEX_NUM 20
typedef struct ArcBox
{
    int tailvex, headvex;
    struct ArcBox *hlink, *tlink;
    InfoType *info;
} ArcBox;
typedef struct VexNode // ArcBox为弧结点变量
{
    VertexType data;
    ArcBox *firstin, *firstout;
} VexNode;
typedef struct // VexNode为顶点变量
{
    VexNode xlist[MAX_VERTEX_NUM]; // 表头向
    int vexnum, arcnum;            // 有向图的当前顶点数和弧数
} OLGraph;

例:

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记

④.邻接多重表

邻接多重表是无向图的另一种链式存储结构,由于用邻接表存储无向图时,虽然容易求出顶点和边的各种信息,但在邻接表中每一条边有两个结点,分别在第i和第j个链表中,给图的某些操作带来不便。在邻接多重表中,每一条边只有一个边结点,为有关边的处理提供了方便。

名称 解释
mark 为标志域,可用以标记该条边是否被搜索过
ivex和jvex 为该边依附的两个顶点在图中的位置;
ilnk 指向下一条依附于顶点ivex的边;
jlink 指向下一条依附于顶点jvex的边
info 为指向和边相关的各种信息的指针域。

例:

数据结构(C语言版)-- 图笔记

//无向图的邻接多重表存储表示
#define MAX_VERTEX_NUM 20
typedef enum
{
    unvisited,
    viseited
} ViseitIF;
typedef struct EBox
{
    VisitIf mark;               //访问标志域
    int ivex, jvex;             //该边依附的两个顶点在表头数组中位置
    struct EBox *ilink, *jlink; //分别指向依附于ivex和jvex的下一条边
    InfoType *info;
} Ebox;
typedef struct VexBox
{
    VertexType data; //存与顶点有关的信息
    EBox *firstedge; //指向第一条依附于该顶点的边
} VexBox;
typedef struct
{
    VexBox adjmulist[MAX_VERTEX_NUM]; // 表头向量
    int vexnum, edgenum;              // 无向图的当前顶点数和弧数
} AMLGraph;

例:画出无向图G的邻接多重表

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记


4、图的遍历

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

①.深度优先搜索

深度优先搜索( Depth First Search,DFS)遍历类似于树的先序遍历,是树的先序遍历的推广。对于一个连通图,深度优先搜索遍历的过程如下。

  1. 从图中某个顶点v出发,访问v。
  2. 找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复此步骤,直至刚访问过的顶点没有未被访问的邻接点为止。
  3. 返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点。
  4. 重复步骤(2)和(3),直至图中所有顶点都被访问过,搜索结束。

数据结构(C语言版)-- 图笔记

对无向连通图,如果将一次深度优先搜索时前进操作所经过的边保留下来则可构成一棵深度优先搜索生成树。

数据结构(C语言版)-- 图笔记

深度优先搜索遍历的算法实现

显然,深度优先搜索遍历连通图是一个递归的过程。为了在遍历过程中便于区分顶点是否已被访问,需附设访问标志数组visited[n],其初值为“false",一旦某个顶点被访问,则其相应的分量置为“true”。

  1. 从图中某个顶点v出发,访问v,并置visited[y]的值为true。
  2. 依次检查v的所有邻接点w,如果visited[w]的值为false,再从w出发进行递归遍历,直到图中所有顶点都被访问过。
//深度优先搜索遍历连通图的递归算法

#include <iostream>
using namespace std;

#define MVNum 100        //最大顶点数
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType;     //假设边的权值类型为整型

typedef struct
{
    VerTexType vexs[MVNum];     //顶点表
    ArcType arcs[MVNum][MVNum]; //邻接矩阵
    int vexnum, arcnum;         //图的当前点数和边数
} Graph;

bool visited[MVNum];                   //访问标志数组,其初值为"false"
int FirstAdjVex(Graph G, int v);       //返回v的第一个邻接点
int NextAdjVex(Graph G, int v, int w); //返回v相对于w的下一个邻接点

int LocateVex(Graph G, VerTexType v)
{
    //确定点v在G中的位置
    for (int i = 0; i < G.vexnum; ++i)
        if (G.vexs[i] == v)
            return i;
    return -1;
} // LocateVex

void CreateUDN(Graph &G)
{
    //采用邻接矩阵表示法,创建无向网G
    int i, j, k;
    cout << "请输入总顶点数,总边数 , 以空格隔开:";
    cin >> G.vexnum >> G.arcnum; //输入总顶点数,总边数
    cout << endl;

    cout << "输入点的名称,如 a:" << endl;

    for (i = 0; i < G.vexnum; ++i)
    {
        cout << "请输入第" << (i + 1) << "个点的名称:";
        cin >> G.vexs[i]; //依次输入点的信息
    }
    cout << endl;

    for (i = 0; i < G.vexnum; ++i) //初始化邻接矩阵,边的权值均置为极大值MaxInt
        for (j = 0; j < G.vexnum; ++j)
            G.arcs[i][j] = 0;
    cout << "输入边依附的顶点,如:a b" << endl;
    for (k = 0; k < G.arcnum; ++k)
    { //构造邻接矩阵
        VerTexType v1, v2;
        cout << "请输入第" << (k + 1) << "条边依附的顶点:";
        cin >> v1 >> v2; //输入一条边依附的顶点及权值
        i = LocateVex(G, v1);
        j = LocateVex(G, v2);            //确定v1和v2在G中的位置,即顶点数组的下标
        G.arcs[j][i] = G.arcs[i][j] = 1; //置<v1, v2>的对称边<v2, v1>的权值为w
    }                                    // for
} // CreateUDN

void DFS(Graph G, int v)
{ //从第v个顶点出发递归地深度优先遍历图G
    cout << G.vexs[v] << "    ";
    visited[v] = true; //访问第v个顶点,并置访问标志数组相应分量值为true
    int w;
    for (w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w))
        //依次检查v的所有邻接点w ,FirstAdjVex(G, v)表示v的第一个邻接点
        // NextAdjVex(G, v, w)表示v相对于w的下一个邻接点,w≥0表示存在邻接点
        if (!visited[w])
            DFS(G, w); //对v的尚未访问的邻接顶点w递归调用DFS
} // DFS

int FirstAdjVex(Graph G, int v)
{
    int i;
    for (i = 0; i < G.vexnum; ++i)
    {
        if (G.arcs[v][i] == 1 && visited[i] == false)
            return i;
    }
    return -1;
} // FirstAdjVex

int NextAdjVex(Graph G, int v, int w)
{
    int i;
    for (i = w; i < G.vexnum; ++i)
    {
        if (G.arcs[v][i] == 1 && visited[i] == false)
            return i;
    }
    return -1;
} // NextAdjVex

int main()
{
    cout << "************深度优先搜索遍历连通图的递归算法**************" << endl
         << endl;
    Graph G;
    CreateUDN(G);
    cout << endl;
    cout << "无向连通图G创建完成!" << endl
         << endl;

    cout << "请输入遍历连通图的起始点:";
    VerTexType c;
    cin >> c;

    int i;
    for (i = 0; i < G.vexnum; ++i)
    {
        if (c == G.vexs[i])
            break;
    }
    cout << endl;
    while (i >= G.vexnum)
    {
        cout << "该点不存在,请重新输入!" << endl;
        cout << "请输入遍历连通图的起始点:";
        cin >> c;
        for (i = 0; i < G.vexnum; ++i)
        {
            if (c == G.vexs[i])
                break;
        }
    }
    cout << "深度优先搜索遍历连通图结果:" << endl;
    DFS(G, i);

    cout << endl;
    return 0;
} // main

//深度优先搜索遍历非连通图

#include <iostream>
using namespace std;

#define MVNum 100        //最大顶点数
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType;     //假设边的权值类型为整型

//-------------图的邻接矩阵-----------------
typedef struct
{
    VerTexType vexs[MVNum];     //顶点表
    ArcType arcs[MVNum][MVNum]; //邻接矩阵
    int vexnum, arcnum;         //图的当前点数和边数
} Graph;

bool visited[MVNum];                   //访问标志数组,其初值为"false"
int FirstAdjVex(Graph G, int v);       //返回v的第一个邻接点
int NextAdjVex(Graph G, int v, int w); //返回v相对于w的下一个邻接点

int LocateVex(Graph G, VerTexType v)
{
    //确定点v在G中的位置
    for (int i = 0; i < G.vexnum; ++i)
        if (G.vexs[i] == v)
            return i;
    return -1;
} // LocateVex

void CreateUDN(Graph &G)
{
    //采用邻接矩阵表示法,创建无向网G
    int i, j, k;
    cout << "请输入总顶点数,总边数,以空格隔开:";
    cin >> G.vexnum >> G.arcnum; //输入总顶点数,总边数
    cout << endl;

    cout << "输入点的名称,如a" << endl;
    for (i = 0; i < G.vexnum; ++i)
    {
        cout << "请输入第" << (i + 1) << "个点的名称:";
        cin >> G.vexs[i]; //依次输入点的信息
    }
    cout << endl;
    for (i = 0; i < G.vexnum; ++i) //初始化邻接矩阵,边的权值均置为极大值MaxInt
        for (j = 0; j < G.vexnum; ++j)
            G.arcs[i][j] = 0;
    cout << "输入边依附的顶点,如a b" << endl;
    for (k = 0; k < G.arcnum; ++k)
    { //构造邻接矩阵
        VerTexType v1, v2;
        cout << "请输入第" << (k + 1) << "条边依附的顶点:";
        cin >> v1 >> v2; //输入一条边依附的顶点及权值
        i = LocateVex(G, v1);
        j = LocateVex(G, v2);            //确定v1和v2在G中的位置,即顶点数组的下标
        G.arcs[j][i] = G.arcs[i][j] = 1; //置<v1, v2>的对称边<v2, v1>的权值为w
    }                                    // for
} // CreateUDN

void DFS(Graph G, int v)
{
    //从第v个顶点出发递归地深度优先遍历图G
    cout << G.vexs[v] << "    ";
    visited[v] = true; //访问第v个顶点,并置访问标志数组相应分量值为true
    int w;
    for (w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w))
        //依次检查v的所有邻接点w ,FirstAdjVex(G, v)表示v的第一个邻接点
        // NextAdjVex(G, v, w)表示v相对于w的下一个邻接点,w≥0表示存在邻接点
        if (!visited[w])
            DFS(G, w); //对v的尚未访问的邻接顶点w递归调用DFS
} // DFS

void DFSTraverse(Graph G)
{
    //对非连通图G做深度优先遍历
    int v;
    for (v = 0; v < G.vexnum; ++v)
        visited[v] = false;        //访问标志数组初始化
    for (v = 0; v < G.vexnum; ++v) //循环调用算法6.3
        if (!visited[v])
            DFS(G, v); //对尚未访问的顶点调用DFS
} // DFSTraverse

int FirstAdjVex(Graph G, int v)
{
    //返回v的第一个邻接点
    int i;
    for (i = 0; i < G.vexnum; ++i)
    {
        if (G.arcs[v][i] == 1 && visited[i] == false)
            return i;
    }
    return -1;
} // FirstAdjVex

int NextAdjVex(Graph G, int v, int w)
{
    //返回v相对于w的下一个邻接点
    int i;
    for (i = w; i < G.vexnum; ++i)
    {
        if (G.arcs[v][i] == 1 && visited[i] == false)
            return i;
    }
    return -1;
} // NextAdjVex

int main()
{
    cout << "************深度优先搜索遍历非连通图**************" << endl
         << endl;
    Graph G;
    CreateUDN(G);
    cout << endl;
    cout << "无向图G创建完成!" << endl
         << endl;

    cout << "深度优先搜索遍历非连通图结果:" << endl;
    DFSTraverse(G);

    cout << endl;
    return 0;
} // main

②.广度优先搜索

广度优先搜索( Breadth First Search,BFS)遍历类似于树的按层次遍历的过程。

广度优先搜索遍历的过程如下。

  1. 从图中某个顶点v出发,访问v。
  2. 依次访问v的各个未曾访问过的邻接点。
  3. 分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤3,直至图中所有已被访问的顶点的邻接点都被访问到。

数据结构(C语言版)-- 图笔记
广度优先生成树:

数据结构(C语言版)-- 图笔记
广度优先搜索遍历连通图

  1. 从图中某个顶点v出发,访问v,并置 visited[y]的值为true,然后将v进队。
  2. 只要队列不空,则重复下述操作:
    - 队头顶点u出队;
    - 依次检查u的所有邻接点w,如果visited[w]的值为false,则访问w,并置visited[w]的值为true,然后将w进队。
//广度优先搜索遍历连通图

#include <iostream>
using namespace std;

#define MVNum 100                       	//最大顶点数
#define MAXQSIZE 100						//最大队列长度
						
typedef char VerTexType;              		//假设顶点的数据类型为字符型
typedef int ArcType;                  		//假设边的权值类型为整型
bool visited[MVNum];           				//访问标志数组,其初值为"false" 

//-----图的邻接矩阵存储表示----- 
typedef struct{ 
	VerTexType vexs[MVNum];            		//顶点表
	ArcType arcs[MVNum][MVNum];      		//邻接矩阵
	int vexnum,arcnum;                		//图的当前点数和边数
}Graph;

//----队列的定义及操作--------
typedef struct{
	ArcType *base;							//初始化的动态分配存储空间
	int front;								//头指针,若队列不空,指向队头元素
	int rear;								//尾指针,若队列不空,指向队尾元素的下一个位置
}sqQueue;

void InitQueue(sqQueue &Q){
	//构造一个空队列Q
	Q.base = new ArcType[MAXQSIZE];
	if(!Q.base)     exit(1);				//存储分配失败
	Q.front = Q.rear = 0;
}//InitQueue

void EnQueue(sqQueue &Q, ArcType e){
	//插入元素e为Q的新的队尾元素
	if((Q.rear + 1) % MAXQSIZE == Q.front)
		return;
	Q.base[Q.rear] = e;
	Q.rear = (Q.rear + 1) % MAXQSIZE;
}//EnQueue

bool QueueEmpty(sqQueue Q){
	//判断是否为空队
	if(Q.rear == Q.front)
		return true;
	return false;
}//QueueEmpty

void DeQueue(sqQueue &Q, ArcType &u){
	//队头元素出队并置为u 
	u = Q.base[Q.front];
	Q.front = (Q.front + 1) % MAXQSIZE;
}//DeQueue   								
//--------------------------------------------------

int LocateVex(Graph G , VerTexType v){
	//确定点v在G中的位置
	for(int i = 0; i < G.vexnum; ++i)
		if(G.vexs[i] == v)
			return i;
		return -1;
}//LocateVex

void CreateUDN(Graph &G){ 
    //采用邻接矩阵表示法,创建无向网G 
	int i , j , k;
	cout <<"请输入总顶点数,总边数,以空格隔开:";
    cin >> G.vexnum >> G.arcnum;							//输入总顶点数,总边数
	cout << endl;
	cout << "输入点的名称,如a" << endl;
    for(i = 0; i < G.vexnum; ++i){   
		cout << "请输入第" << (i+1) << "个点的名称:";
		cin >> G.vexs[i];                        			//依次输入点的信息 
	}
	cout << endl;
    for(i = 0; i < G.vexnum; ++i)                			//初始化邻接矩阵,边的权值均置为极大值MaxInt 
		for(j = 0; j < G.vexnum; ++j)   
			G.arcs[i][j] = 0; 
	cout << "输入边依附的顶点,如a b" << endl;
	for(k = 0; k < G.arcnum;++k){							//构造邻接矩阵 
		VerTexType v1 , v2;
		cout << "请输入第" << (k + 1) << "条边依附的顶点:";
		cin >> v1 >> v2;									//输入一条边依附的顶点
		i = LocateVex(G, v1);  j = LocateVex(G, v2);		//确定v1和v2在G中的位置,即顶点数组的下标 
		G.arcs[i][j] = 1;									//边<v1, v2>的权值置为w 
		G.arcs[j][i] = G.arcs[i][j];						//置<v1, v2>的对称边<v2, v1>的权值为w 
	}//for 
}//CreateUDN

int FirstAdjVex(Graph G , int v){
	//返回v的第一个邻接点
	int i;
	for(i = 0 ; i < G.vexnum ; ++i){
		if(G.arcs[v][i] == 1 && visited[i] == false)
			return i;
	}
	return -1;
}//FirstAdjVex

int NextAdjVex(Graph G , int u , int w){
	//返回v相对于w的下一个邻接点
	int i;
	for(i = w ; i < G.vexnum ; ++i){
		if(G.arcs[u][i] == 1 && visited[i] == false)
			return i;
	}
	return -1;
}//NextAdjVex

void BFS (Graph G, int v){ 
    //按广度优先非递归遍历连通图G 
	sqQueue Q;
	ArcType u;
	ArcType w;

    cout << G.vexs[v] << "  ";    visited[v] = true;     						//访问第v个顶点,并置访问标志数组相应分量值为true 
    InitQueue(Q);              													//辅助队列Q初始化,置空         
    EnQueue(Q, v);            													//v进队 
    while(!QueueEmpty(Q)){   													//队列非空 
		DeQueue(Q, u);       													//队头元素出队并置为u
		for(w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w)){
			//依次检查u的所有邻接点w ,FirstAdjVex(G, u)表示u的第一个邻接点 
			//NextAdjVex(G, u, w)表示u相对于w的下一个邻接点,w≥0表示存在邻接点 
			if(!visited[w]){	           										//w为u的尚未访问的邻接顶点 
				cout << G.vexs[w] << "  ";   visited[w] = true;					//访问w,并置访问标志数组相应分量值为true 
				EnQueue(Q, w);													//w进队 
			}//if 
		}//for
    }//while 
}//BFS 

int main(){
	cout << "************算法广度优先搜索遍历连通图**************" << endl << endl;
	Graph G;
	CreateUDN(G);
	cout << endl;
	cout << "无向连通图G创建完成!" << endl << endl;
	
	cout << "请输入遍历连通图的起始点:";
	VerTexType c;
	cin >> c;
	
	int i;
	for(i = 0 ; i < G.vexnum ; ++i){
		if(c == G.vexs[i])
			break;
	}
	cout << endl;
	while(i >= G.vexnum){
		cout << "该点不存在,请重新输入!" << endl;
		cout << "请输入遍历连通图的起始点:";
		cin >> c;
		for(i = 0 ; i < G.vexnum ; ++i){
			if(c == G.vexs[i])
				break;
		}
	}
	cout << "深度优先搜索遍历连通图结果:" << endl;
	BFS(G , i);
	
	cout <<endl;
	return 0;
}//main


5、图的应用

①.最小生成树

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

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

生成树的顶点集合与图的顶点集合相等,顶点数为n;不存在回路,边数为n-1

数据结构(C语言版)-- 图笔记

在网的多个生成树中,寻找一个各边权值之和最小的生成树,即最小生成树

构造最小生成树的准则:

  • 必须只使用该网中的边来构造最小生成树;
  • 必须使用且仅使用n-1条边来联结网络中的n个顶点
  • 不能使用产生回路的边

普里姆算法(加点法)

假设N=(V,E)是连通网,TE是N上最小生成树中边的集合。

  1. U= {u0}(u0∈V),TE={}。
  2. 在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0, v0)并入集合TE,同时v0并人U。
  3. 重复②,直至U=V为止。

此时TE中必有n-1条边,则T=(V,TE)为N的最小生成树。

普里姆算法构造最小生成树的过程:
数据结构(C语言版)-- 图笔记
图采用邻接矩阵存储,二维数组closedeg,记录从U到V-U具有最小代价的边。
对每个顶点vi, v-U在辅助数组存在一个相应的分量closedge[i-1],它包括两个域:

typedef struct
{
    VertexType adjvex; // 最小边的顶点
    VRType lowcost;    // 最小边的权值
} closedge[MAX_VERTEX_NUM];

adjvex:依附于这条最小代价边的另一个顶点。

  • 等于0表示顶点i已在U中;
  • 大于0表示顶点i还在V-U中。

所以,每次循环须在lowcost >0(在集合V-U中)的那些顶点中选择lowcost最小的顶点加入到集合U中,同时将相关顶点的closedge作相应的调整。

//普里姆算法
#include <iostream>
using namespace std;

typedef char VerTexType;
typedef int ArcType;
#define MVNum 100
#define MaxInt 32767                    	//表示极大值,即∞

//辅助数组的定义,用来记录从顶点集U到V-U的权值最小的边
struct{
	VerTexType adjvex;						//最小边在U中的那个顶点
	ArcType lowcost;						//最小边上的权值
}closedge[MVNum];

//- - - - -图的邻接表存储表示- - - - - 						
typedef char VerTexType;              		//假设顶点的数据类型为字符型 
typedef int ArcType;                  		//假设边的权值类型为整型 
typedef struct{ 
	VerTexType vexs[MVNum];            		//顶点表 
	ArcType arcs[MVNum][MVNum];      		//邻接矩阵 
	int vexnum,arcnum;                		//图的当前点数和边数 
}AMGraph;

int LocateVex(AMGraph G , VerTexType v){
	//确定点v在G中的位置
	for(int i = 0; i < G.vexnum; ++i)
		if(G.vexs[i] == v)
			return i;
		return -1;
}//LocateVex

void CreateUDN(AMGraph &G){ 
    //采用邻接矩阵表示法,创建无向网G 
	int i , j , k;
	cout <<"请输入总顶点数,总边数,以空格隔开:";
    cin >> G.vexnum >> G.arcnum;							//输入总顶点数,总边数
	cout << endl;

	cout << "输入点的名称,如a" << endl;

    for(i = 0; i < G.vexnum; ++i){   
		cout << "请输入第" << (i+1) << "个点的名称:";
		cin >> G.vexs[i];                        			//依次输入点的信息 
	}
	cout << endl;
    for(i = 0; i < G.vexnum; ++i)                			//初始化邻接矩阵,边的权值均置为极大值MaxInt 
		for(j = 0; j < G.vexnum; ++j)   
			G.arcs[i][j] = MaxInt;
	cout << "输入边依附的顶点及权值,如a b 5" << endl;
	for(k = 0; k < G.arcnum;++k){							//构造邻接矩阵 
		VerTexType v1 , v2;
		ArcType w;
		cout << "请输入第" << (k + 1) << "条边依附的顶点及权值:";
		cin >> v1 >> v2 >> w;								//输入一条边依附的顶点及权值
		i = LocateVex(G, v1);  j = LocateVex(G, v2);		//确定v1和v2在G中的位置,即顶点数组的下标 
		G.arcs[i][j] = w;									//边<v1, v2>的权值置为w 
		G.arcs[j][i] = G.arcs[i][j];						//置<v1, v2>的对称边<v2, v1>的权值为w 
	}//for
}//CreateUDN 

int Min(AMGraph G){
	//返回权值最小的点
	int i;
	int index = -1;
	int min = MaxInt;
	for(i = 0 ; i < G.vexnum ; ++i){
		if(min > closedge[i].lowcost && closedge[i].lowcost != 0){
			min = closedge[i].lowcost;
			index = i;
		}
	}//for
	return index;
}//Min

void MiniSpanTree_Prim(AMGraph G, VerTexType u){ 
    //无向网G以邻接矩阵形式存储,从顶点u出发构造G的最小生成树T,输出T的各条边  
	int k , j , i;
	VerTexType u0 , v0;
    k =LocateVex(G, u);           										//k为顶点u的下标 
    for(j = 0; j < G.vexnum; ++j){     									//对V-U的每一个顶点vi,初始化closedge[i] 
		if(j != k){  
			closedge[j].adjvex = u;
			closedge[j].lowcost = G.arcs[k][j];							//{adjvex, lowcost}
		}//if
	}//for
	closedge[k].lowcost = 0;        									//初始,U = {u}
	for(i = 1; i < G.vexnum; ++i){     									//选择其余n-1个顶点,生成n-1条边(n= G.vexnum) 
		k = Min(G);  
		//求出T的下一个结点:第k个顶点,closedge[k]中存有当前最小边 
		u0 = closedge[k].adjvex;     									//u0为最小边的一个顶点,u0∈U 
		v0 = G.vexs[k];            										//v0为最小边的另一个顶点,v0∈V-U 
		cout << "边  " <<u0 << "--->" << v0 << endl;           			//输出当前的最小边(u0, v0) 
		closedge[k].lowcost = 0;   		//第k个顶点并入U集 
		for(j = 0; j < G.vexnum; ++j) 
			if(G.arcs[k][j] < closedge[j].lowcost){						//新顶点并入U后重新选择最小边 
				closedge[j].adjvex = G.vexs[k];
				closedge[j].lowcost = G.arcs[k][j];
			}//if 
	}//for 
}//MiniSpanTree_Prim 

int main(){
	cout << "************普里姆算法**************" << endl << endl;
	AMGraph G;
	CreateUDN(G);
	cout << endl;
	cout << "无向图G创建完成!" << endl;
	cout <<endl;

	cout << "******利用普里姆算法构造最小生成树结果:******" << endl;
	MiniSpanTree_Prim(G , 'a');
	cout <<endl;
	return 0;
}//main

例:

数据结构(C语言版)-- 图笔记

数据结构(C语言版)-- 图笔记

克鲁斯卡尔算法(加边法)

假设连通网N=(V,E),将Ⅳ中的边按权值从小到大的顺序排列。

  1. 初始状态为只有n个顶点而无边的非连通图T=(V, {),图中每个顶点自成一个连通分量。
  2. 在E中选择权值最小的边,若该边依附的顶点落在T中不同的连通分量上(即不形成回路),则将此边加人到T中,否则舍去此边而选择下一条权值最小的边。
  3. 重复②,直至T中所有顶点都在同一连通分量上为止。

克鲁斯卡尔算法构造最小生成树的过程:

数据结构(C语言版)-- 图笔记

算法的实现要引人以下辅助的数据结构。

  1. 结构体数组Edge:存储边的信息,包括边的两个顶点信息和权值。
//辅助数组Edges的定义
struct
{
    VerTexType Head; //边的始点
    VerTexType Tail; //边的终点
    ArcType lowcost; //边上的权值
} Edge[arcnnum];
  1. Vexset[i]:标识各个顶点所属的连通分量。对每个顶点v,EV,在辅助数组中存在一个相应元素Vexset[i]表示该顶点所在的连通分量。初始时Vexset[i]=i,表示各顶点自成一个连通分量。
//辅助数组Vexset的定义
int Vexset[MVNum];

  1. 将数组Edge中的元素按权值从小到大排序。
  2. 依次查看数组Edge中的边,循环执行以下操作:
  • 依次从排好序的数组 Edge 中选出一条边(U1,U2);
  • 在Vexset中分别查找v1和v2所在的连通分量vs1和 vs2,进行判断:
    - 如果vs1,和vs2不等、表明所选的两个顶点分属不同的连通分量,输出此边,并合并vs1,和 vs2两个连通分量;
    - 如果vs1,和 vs2相等,表明所选的两个顶点属于同一个连通分量,舍去此边而选择下一条权值最小的边。
//克鲁斯卡尔算法

#include <iostream>
using namespace std;

typedef char VerTexType;              		//假设顶点的数据类型为字符型 
typedef int ArcType;   
#define MVNum 100                       	//最大顶点数
#define MaxInt 32767                    	//表示极大值,即∞

//----------------图的邻接矩阵---------------------
typedef struct{ 
	VerTexType vexs[MVNum];            		//顶点表 
	ArcType arcs[MVNum][MVNum];      		//邻接矩阵 
	int vexnum,arcnum;                		//图的当前点数和边数 
}AMGraph;

//辅助数组Edges的定义
struct{
	VerTexType Head;						//边的始点
	VerTexType Tail;						//边的终点
	ArcType lowcost;						//边上的权值
}Edge[(MVNum * (MVNum - 1)) / 2];

int Vexset[MVNum];							//辅助数组Vexset的定义

int LocateVex(AMGraph G , VerTexType v){
	//确定点v在G中的位置
	for(int i = 0; i < G.vexnum; ++i)
		if(G.vexs[i] == v)
			return i;
		return -1;
}//LocateVex

void CreateUDN(AMGraph &G){ 
    //采用邻接矩阵表示法,创建无向网G 
	int i , j , k;
	cout <<"请输入总顶点数,总边数,以空格隔开:";
    cin >> G.vexnum >> G.arcnum;						//输入总顶点数,总边数
	cout << endl;
	
	cout << "输入点的名称,如a" << endl;

    for(i = 0; i < G.vexnum; ++i){   
		cout << "请输入第" << (i+1) << "个点的名称:";
		cin >> G.vexs[i];                        		//依次输入点的信息 
	}
	cout << endl;
	for(i = 0; i < G.vexnum; ++i)                		//初始化邻接矩阵,边的权值均置为极大值MaxInt 
		for(j = 0; j < G.vexnum; ++j) 
			G.arcs[i][j] = MaxInt; 
	cout << "输入边依附的顶点及权值,如a b 6" << endl;
	for(k = 0; k < G.arcnum;++k){						//构造邻接矩阵 
		VerTexType v1 , v2;
		ArcType w;
		cout << "请输入第" << (k + 1) << "条边依附的顶点及权值:";
		cin >> v1 >> v2 >> w;                           //输入一条边依附的顶点及权值
		i = LocateVex(G, v1);  j = LocateVex(G, v2);	//确定v1和v2在G中的位置,即顶点数组的下标 
		G.arcs[i][j] = w;								//边<v1, v2>的权值置为w 
		G.arcs[j][i] = G.arcs[i][j];					//置<v1, v2>的对称边<v2, v1>的权值为w 
		Edge[k].lowcost = w;
		Edge[k].Head = v1;
		Edge[k].Tail = v2;
	}//for
}//CreateUDN 

//----------冒泡排序-------------------
void Sort(AMGraph G){
	int m = G.arcnum - 2;
	int flag = 1;
	while((m > 0) && flag == 1){
		flag = 0;
		for(int j = 0 ; j <= m ; j++){
			if(Edge[j].lowcost > Edge[j+ 1].lowcost){
				flag = 1;

				VerTexType temp_Head = Edge[j].Head;
				Edge[j].Head = Edge[j+ 1].Head;
				Edge[j + 1].Head = temp_Head;
				

				VerTexType temp_Tail = Edge[j].Tail;
				Edge[j].Tail = Edge[j+ 1].Tail;
				Edge[j + 1].Tail = temp_Tail;
				
				ArcType temp_lowcost = Edge[j].lowcost;
				Edge[j].lowcost = Edge[j+ 1].lowcost;
				Edge[j + 1].lowcost = temp_lowcost;
			}//if
		}//for
		--m;
	}//while
}//Sort

void MiniSpanTree_Kruskal(AMGraph G){ 
    //无向网G以邻接矩阵形式存储,构造G的最小生成树T,输出T的各条边     
    int i , j , v1 , v2 , vs1 , vs2;
	Sort(G);                 							//将数组Edge中的元素按权值从小到大排序 
	for(i = 0; i < G.vexnum; ++i)     					//辅助数组,表示各顶点自成一个连通分量 
        Vexset[i] = i;
    for(i = 0; i < G.arcnum; ++i){      
		//依次查看排好序的数组Edge中的边是否在同一连通分量上     
		v1 =LocateVex(G, Edge[i].Head);     			//v1为边的始点Head的下标 
		v2 =LocateVex(G, Edge[i].Tail);     			//v2为边的终点Tail的下标 
		vs1 = Vexset[v1];       						//获取边Edge[i]的始点所在的连通分量vs1 
		vs2 = Vexset[v2];       						//获取边Edge[i]的终点所在的连通分量vs2 
		if(vs1 != vs2){         						//边的两个顶点分属不同的连通分量 
			cout << Edge[i].Head << "-->" << Edge[i].Tail << endl;		//输出此边 
			for(j = 0; j < G.vexnum; ++j)      			//合并vs1和vs2两个分量,即两个集合统一编号 
				if(Vexset[j] == vs2) Vexset[j] = vs1;	//集合编号为vs2的都改为vs1 
		}//if 
    }//for 
}//MiniSpanTree_Kruskal

void main(){
	cout << "************克鲁斯卡尔算法**************" << endl << endl;
	AMGraph G;
	CreateUDN(G);
	
	cout <<endl;
	cout << "*****无向网G创建完成!*****" << endl;

	cout <<endl;
	MiniSpanTree_Kruskal(G);
}///main

数据结构(C语言版)-- 图笔记

②.最短路径

对于图来说,从一个顶点到另一个顶点可能存在多条路径,每条路径的所包含的边数可能不同。把所包含的边数最少的那条称为最短路径

最短路径:对于网(带权的图)来说,从一个顶点到另一个顶点所经过的边的权值之和称为带权路径长度,把带权路径长度最短的那条称为最短路径

从某个源点到其余各顶点的最短路径------迪杰斯特拉(Dijkstra)算法

  1. 初始化:先找出从源点v0到各终点v的直达路径(v0,vk),即通过一条弧到达的路径。
  2. 选择:从这些路径中找出一条长度最短的路径(v0,u) 。
  3. 更新:然后对其余各条路径进行适当调整。

若在图中存在弧(u,vk) ,(v0,u) +(u,vk)< (v0,vk) ,则以路径(v0,u,vk)代替(v0,vk)。
在调整后的各条路径中,再找长度最短的路径。

主要存储结构︰邻接矩阵G [n][n] (或者邻接表)

辅助存储结构︰

数组S[n]:记录相应顶点是否已被确定最短距离
true:确定 false:未确定

数组D[n]:记录源点到相应顶点路径长度
初值:如果v0到vi有弧,则D[i]为弧上权值;否则为∞

数组Path[n]:记录相应顶点的前驱顶点
初值:如果v0到vi有弧,则Path[i]为v0,否则为-1

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记

  1. 初始化:
    将源点v0加到S中,即S[v0] = true;
    将v0到各个终点的最短路径长度初始化为权值,即D[i]= G.arcs[v0][vi],(vi∈V -S);
    如果v0和顶点vi之间有弧,则将vi的前驱置为v0,即Path[i] = v0,否则Path[i] = -1。

  2. 循环n-1次,执行以下操作:
    选择下一条最短路径的终点vk,使得:D[k]=Min{D[i]viEV-S}
    将vk加到S中,即S[vk] = true。
    更新从v0出发到集合V-S上任一顶点的最短路径的长度,同时更改vi的前驱为vk。
    若S[i]=false 且D[k]+G.arcs[k][i]<D[i],则更新D[i]-D[k]+ G.arcs[k][i; Path [i]=k;。

//最短路径--迪杰斯特拉算法
#include <iostream>
using namespace std;

#define MaxInt 32767                    					//表示极大值,即∞
#define MVNum 100                       					//最大顶点数
typedef char VerTexType;              						//假设顶点的数据类型为字符型 
typedef int ArcType;                  						//假设边的权值类型为整型

int *D=new int[MVNum];	                    				//用于记录最短路的长度
bool *S=new bool[MVNum];          							//标记顶点是否进入S集合
int *Path=new int[MVNum];									//用于记录最短路顶点的前驱

//------------图的邻接矩阵-----------------
typedef struct{ 
	VerTexType vexs[MVNum];            						//顶点表 
	ArcType arcs[MVNum][MVNum];      						//邻接矩阵 
	int vexnum,arcnum;                						//图的当前点数和边数 
}AMGraph;

int LocateVex(AMGraph G , VerTexType v){
	//确定点v在G中的位置
	for(int i = 0; i < G.vexnum; ++i)
		if(G.vexs[i] == v)
			return i;
   return -1;
}//LocateVex

void CreateUDN(AMGraph &G){ 
    //采用邻接矩阵表示法,创建无向网G 
	int i , j , k;
	cout <<"请输入总顶点数,总边数,以空格隔开:";
    cin >> G.vexnum >> G.arcnum;							//输入总顶点数,总边数
	cout << endl;

	cout << "输入点的名称:,如a" << endl;

    for(i = 0; i < G.vexnum; ++i){   
		cout << "请输入第" << (i+1) << "个点的名称:";
		cin >> G.vexs[i];                        			//依次输入点的信息 
	}
	cout << endl;
    for(i = 0; i < G.vexnum; ++i)                			//初始化邻接矩阵,边的权值均置为极大值MaxInt 
		for(j = 0; j < G.vexnum; ++j)   
			G.arcs[i][j] = MaxInt; 
	cout << "输入边依附的顶点及权值,如a b 7" << endl;
	for(k = 0; k < G.arcnum;++k){							//构造邻接矩阵 
		VerTexType v1 , v2;
		ArcType w;
		cout << "请输入第" << (k + 1) << "条边依附的顶点及权值:";
		cin >> v1 >> v2 >> w;								//输入一条边依附的顶点及权值
		i = LocateVex(G, v1);  j = LocateVex(G, v2);		//确定v1和v2在G中的位置,即顶点数组的下标 
		G.arcs[i][j] = w;									//边<v1, v2>的权值置为w 
		G.arcs[j][i] = G.arcs[i][j];						//置<v1, v2>的对称边<v2, v1>的权值为w 
	}//for
}//CreateUDN

void ShortestPath_DIJ(AMGraph G, int v0){ 
    //用Dijkstra算法求有向网G的v0顶点到其余顶点的最短路径 
    int v , i , w , min;
	int n = G.vexnum;                    					//n为G中顶点的个数 

	for(v = 0; v < n; ++v){             					//n个顶点依次初始化 
		S[v] = 0;                  						//S初始为空集 
		D[v] = G.arcs[v0][v];           					//将v0到各个终点的最短路径长度初始化为弧上的权值 
		if(D[v] < MaxInt)  Path [v] = v0;  					//如果v0和v之间有弧,则将v的前驱置为v0 
		else Path [v] = -1;               					//如果v0和v之间无弧,则将v的前驱置为-1 
	}//for 

	S[v0]=true;                    							//将v0加入S 
	D[v0]=0;                      							//源点到源点的距离为0 

	/*―初始化结束,开始主循环,每次求得v0到某个顶点v的最短路径,将v加到S集―*/ 
	for(i = 1;i < n; ++i){									//对其余n-1个顶点,依次进行计算 
        min= MaxInt; 
        for(w = 0; w < n; ++w) 
			if(!S[w] && D[w] < min){						//选择一条当前的最短路径,终点为v 
				v = w; 
				min = D[w];
			}//if         	
		S[v]=true;                   						//将v加入S 
		for(w = 0;w < n; ++w)           					//更新从v0出发到集合V?S上所有顶点的最短路径长度 
			if(!S[w] && (D[v] + G.arcs[v][w] < D[w])){ 
				D[w] = D[v] + G.arcs[v][w];   				//更新D[w] 
				Path [w] = v;              					//更改w的前驱为v 
			}//if 
    }//for  
}//ShortestPath_DIJ

void DisplayPath(AMGraph G , int begin ,int temp ){
	//显示最短路
	if(Path[temp] != -1){
		DisplayPath(G , begin ,Path[temp]);
		cout << G.vexs[Path[temp]] << "-->";
	}
}//DisplayPath

int main()
{
	cout << "************迪杰斯特拉算法**************" << endl << endl;
	AMGraph G; 
	int i , j ,num_start , num_destination;
	VerTexType start , destination;
	CreateUDN(G);
	cout <<endl;
	cout << "*****无向网G创建完成!*****" << endl;
	
	for(i = 0 ; i < G.vexnum ; ++i){
		for(j = 0; j < G.vexnum; ++j){
			if(j != G.vexnum - 1){
				if(G.arcs[i][j] != MaxInt)
					cout << G.arcs[i][j] << "\t";
				else
					cout << "∞" << "\t";
			}
			else{
				if(G.arcs[i][j] != MaxInt)
					cout << G.arcs[i][j] <<endl;
				else
					cout << "∞" <<endl;
			}
		}
	}//for
	cout << endl;
	cout << "请依次输入起始点、终点名称:";
	cin >> start >> destination;
	num_start = LocateVex(G , start);
	num_destination = LocateVex(G , destination);
	ShortestPath_DIJ(G , num_start);
	cout << endl <<"最短路径为:";
	DisplayPath(G , num_start , num_destination);
	cout << G.vexs[num_destination]<<endl;
}//main

数据结构(C语言版)-- 图笔记

数据结构(C语言版)-- 图笔记

每一对顶点之间的最短路径------弗洛伊德(Floyd)算法

从图的带权邻接矩阵G.arcs出发,
假设求顶点Vi到Vj的最短路径。如果从Vi到Vj有弧,则从Vi到Vj存在—条长度为G.arcs[i]i]的路径,但该路径是否一定是最短路径,还需要进行n次试探。

  1. 第一次,判别( Vi, V0 )和( V0, Vj ) ,即判别(Vi, V0 , Vj)是否存在,若存在,则比较(Vi,
    Vj)和(Vi, V0 , Vj)的长度,取长度较短的为从Vi到Vj的中间顶点序号不大于0的最短路径。
  2. 第二次,再加一个顶点V1,如果(Vi,… , V1)和(V1,…,
    Vj)分别是当前找到的中间顶点序号不大于0的最短路径,那么(Vi,… ,V1,…,
    Vj)就有可能是从Vi到Vj的中间顶点序号不大于1的最短路径。将它和已经得到的从Vi到Vj之间顶点序号不大于0的最短路径相比较,取较短者为从Vi到Vj的中间顶点序号不大于1的最短路径。
  3. 第三次,再加一个顶点V2,继续进行试探。

求最短路径步骤:

  1. 初始时设置一个n阶方阵,令其对角线元素为0,若存在弧<Vi,Vj >,则对应元素为权值;否则为无穷大。
  2. 逐步试着在原直接路径中增加中间顶点,若加入中间顶点后路径变短,则修改之;否则,维持原值。所有顶点试探完毕,算法结束。

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记

//弗洛伊德算法

#include <iostream>
using namespace std;

#define MaxInt 32767                    	//表示极大值,即∞
#define MVNum 100                       	//最大顶点数

typedef char VerTexType;              		//假设顶点的数据类型为字符型 
typedef int ArcType;                  		//假设边的权值类型为整型 

int Path[MVNum][MVNum];						//最短路径上顶点vj的前一顶点的序号
int D[MVNum][MVNum];						//记录顶点vi和vj之间的最短路径长度

//------------图的邻接矩阵---------------
typedef struct{ 
	VerTexType vexs[MVNum];            		//顶点表 
	ArcType arcs[MVNum][MVNum];      		//邻接矩阵 
	int vexnum,arcnum;                		//图的当前点数和边数 
}AMGraph;

int LocateVex(AMGraph G , VerTexType v){
	//确定点v在G中的位置
	for(int i = 0; i < G.vexnum; ++i)
		if(G.vexs[i] == v)
			return i;
		return -1;
}//LocateVex

void CreateUDN(AMGraph &G){ 
    //采用邻接矩阵表示法,创建有向网G 
	int i , j , k;
	cout <<"请输入总顶点数,总边数,以空格隔开:";
    cin >> G.vexnum >> G.arcnum;							//输入总顶点数,总边数
	cout << endl;

	cout << "输入点的名称,如a" << endl;

    for(i = 0; i < G.vexnum; ++i){   
		cout << "请输入第" << (i+1) << "个点的名称:";
		cin >> G.vexs[i];                        			//依次输入点的信息 
	}
	cout << endl;
    for(i = 0; i < G.vexnum; ++i){                			//初始化邻接矩阵,边的权值均置为极大值MaxInt 
		for(j = 0; j < G.vexnum; ++j){  
			if(j != i)
				G.arcs[i][j] = MaxInt;  
			else
				G.arcs[i][j] = 0;
		}//for
	}//for

	cout << "输入边依附的顶点及权值,如a b 3" << endl;
	for(k = 0; k < G.arcnum;++k){						//构造邻接矩阵 
		VerTexType v1 , v2;
		ArcType w;
		cout << "请输入第" << (k + 1) << "条边依附的顶点及权值:";
		cin >> v1 >> v2 >> w;                           //输入一条边依附的顶点及权值
		i = LocateVex(G, v1);  j = LocateVex(G, v2);	//确定v1和v2在G中的位置,即顶点数组的下标 
		G.arcs[i][j] = w;								//边<v1, v2>的权值置为w 
	}//for
}//CreateUDN 

void ShortestPath_Floyed(AMGraph G){ 
    //用Floyd算法求有向网G中各对顶点i和j之间的最短路径 
	int i , j , k ;
    for (i = 0; i < G.vexnum; ++i)          		//各对结点之间初始已知路径及距离 
        for(j = 0; j < G.vexnum; ++j){ 
            D[i][j] = G.arcs[i][j]; 
            if(D[i][j] < MaxInt && i != j)  Path[i][j]=i;  	//如果i和j之间有弧,则将j的前驱置为i 
            else Path [i][j] = -1;              		//如果i和j之间无弧,则将j的前驱置为-1 
		}//for
		for(k = 0; k < G.vexnum; ++k) 
			for(i = 0; i < G.vexnum; ++i) 
				for(j = 0; j < G.vexnum; ++j)
					if(D[i][k] + D[k][j] < D[i][j]){   		//从i经k到j的一条路径更短 
						D[i][j] = D[i][k]+D[k][j];    		//更新D[i][j] 
						Path[i][j] = Path[k][j];       			//更改j的前驱为k 
					}//if 
}//ShortestPath_Floyed

void DisplayPath(AMGraph G , int begin ,int temp ){
	//显示最短路径
	if(Path[begin][temp] != -1){
		DisplayPath(G , begin ,Path[begin][temp]);
		cout << G.vexs[Path[begin][temp]] << "-->";
	}
}//DisplayPath

void main(){
	cout << "************弗洛伊德算法**************" << endl << endl;
	AMGraph G;
	char start , destination;
	int num_start , num_destination;

	CreateUDN(G);
	
	cout <<endl;
	cout << "有向网G创建完成!" << endl;
	ShortestPath_Floyed(G);

	cout << "请依次输入路径的起点与终点的名称:";
	cin >> start >> destination;
	num_start = LocateVex(G , start);
	num_destination = LocateVex(G , destination);

	DisplayPath(G , num_start , num_destination);
	cout << G.vexs[num_destination] << endl;
	cout << "最短路径的长度为:" << D[num_start][num_destination] << endl;
	cout <<endl;
}//main

③.拓扑排序

对一项工程,我们最关心两个问题:

  1. 工程能否顺利完成;(拓扑排序)
  2. 整个工程完成所必需的最短工期。(关键路径)

有向无环图----无环的有向图,简称DAG图( directed acycling graph)

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

AOV网应该是一个有向无环图即DAG图。

拓扑排序

就是将AOV网中所有顶点排成一个线性序列(称为拓扑序列),该序列满足∶
若在AOV网中由顶点vi到vj有一条路径,则在该线性序列中的顶点vi必定在vj之前

  1. 在有向图中选一个无前驱的顶点且输出它(即入度为0)
  2. 从图中删除该顶点和所有以它为起点的边
  3. 重复(1)(2),直至不存在无前驱的顶点
  4. 若此时输出的顶点数小于有向图的顶点数,则说明有向图中存在环,否则输出的顶点序列为一个拓扑序列

数据结构(C语言版)-- 图笔记

拓扑排序实现

  1. 求出个顶点的入度存入数组indegree[i]中,并将入度为0的顶点入栈。

  2. 只要栈不空,重复以下操作
    - 将栈顶顶点vi出栈并保存在拓扑序列数组topo中;
    - 对顶点vi的每个邻接点vk的入度减1,如果vk的入度变为0,则将vk入栈

  3. 如果输出顶点个数少于AOV网的顶点个数,则网址存在有向环,无法进行拓扑排序,否则拓扑排序成功。

//拓扑排序

#include <iostream>
using namespace std;

#define MVNum 100                       	//最大顶点数
#define OK 1	
#define ERROR 0 

typedef char VerTexType;

//- - - - -图的邻接表存储表示- - - - - 
typedef struct ArcNode{                		//边结点 
    int adjvex;                          	//该边所指向的顶点的位置 
    struct ArcNode *nextarc;          		//指向下一条边的指针 
}ArcNode; 

typedef struct VNode{ 
    VerTexType data;                    	//顶点信息 
    ArcNode *firstarc;                		//指向第一条依附该顶点的边的指针 
}VNode, AdjList[MVNum];               		//AdjList表示邻接表类型 

typedef struct{ 
    AdjList vertices;                 		//邻接表 
	AdjList converse_vertices;				//逆邻接表
    int vexnum, arcnum;              		//图的当前顶点数和边数 
}ALGraph;
//- - - - - - - - - - - - - - - -

//- - - - -顺序栈的定义- - - - -
typedef struct{
	int *base;
	int *top;
	int stacksize;
}spStack;
//- - - - - - - - - - - - - - - -

int indegree[MVNum];						//数组indegree存放个顶点的入度
spStack S;

//------------栈的相关操作----------------------
void InitStack(spStack &S){
	//初始化栈
	S.base = new int[MVNum];
	if(!S.base)
		exit(1);
	S.top = S.base;
	S.stacksize = MVNum;
}//InitStack

void Push(spStack &S , int i){
	//进栈
	if(S.top - S.base == S.stacksize)
		return;
	*S.top++ = i;
}//Push

void Pop(spStack &S , int &i){
	//出栈
	if(S.top == S.base)
		return;
	i = *--S.top;
}//Pop

bool StackEmpty(spStack S){
	//判断栈是否为空
	if(S.top == S.base)
		return true;
	return false;
}//StackEmpty
//-------------------------------------------------

int LocateVex(ALGraph G , VerTexType v){
	//确定点v在G中的位置
	for(int i = 0; i < G.vexnum; ++i)
		if(G.vertices[i].data == v)
			return i;
		return -1;
}//LocateVex

int CreateUDG(ALGraph &G){ 
	//创建有向图G的邻接表、逆邻接表
	int i , k;
	
	cout <<"请输入总顶点数,总边数,以空格隔开:";
	cin >> G.vexnum >> G.arcnum;				//输入总顶点数,总边数 
    cout << endl;
	
	cout << "输入点的名称,如a" << endl;
	
	for(i = 0; i < G.vexnum; ++i){          	//输入各点,构造表头结点表
		cout << "请输入第" << (i+1) << "个点的名称:";
		cin >> G.vertices[i].data;           	//输入顶点值
		G.converse_vertices[i].data = G.vertices[i].data;
		//初始化表头结点的指针域为NULL 
		G.vertices[i].firstarc=NULL;			
		G.converse_vertices[i].firstarc=NULL;
    }//for
	cout << endl;
	cout << "输入边依附的顶点,如a b" << endl;
	for(k = 0; k < G.arcnum;++k){        		//输入各边,构造邻接表
		VerTexType v1 , v2;
		int i , j;
		cout << "请输入第" << (k + 1) << "条边依附的顶点:";
		cin >> v1 >> v2;                		//输入一条边依附的两个顶点
		i = LocateVex(G, v1);  j = LocateVex(G, v2);
		//确定v1和v2在G中位置,即顶点在G.vertices中的序号 

		ArcNode *p1=new ArcNode;               	//生成一个新的边结点*p1 
		p1->adjvex=j;                   		//邻接点序号为j
		p1->nextarc = G.vertices[i].firstarc;  G.vertices[i].firstarc=p1;
		//将新结点*p1插入顶点vi的边表头部

		ArcNode *p2=new ArcNode;               	//生成一个新的边结点*p1 
		p2->adjvex=i;                   		//逆邻接点序号为i
		p2->nextarc = G.converse_vertices[j].firstarc;  G.converse_vertices[j].firstarc=p2;
		//将新结点*p1插入顶点vi的边表头部
    }//for 
    return OK; 
}//CreateUDG

void FindInDegree(ALGraph G){
	//求出各顶点的入度存入数组indegree中 
	int i , count;

	for(i = 0 ; i < G.vexnum ; i++){
		count = 0;
		ArcNode *p = G.converse_vertices[i].firstarc;
		if(p){
			while(p){
				p = p->nextarc;
				count++;
			}
		}
		indegree[i] = count;
	}
}//FindInDegree

int TopologicalSort(ALGraph G , int topo[]){ 
    //有向图G采用邻接表存储结构 
    //若G无回路,则生成G的一个拓扑序列topo[]并返回OK,否则ERROR 
	int i , m;
    FindInDegree(G);              				//求出各顶点的入度存入数组indegree中 
    InitStack(S);                          		//栈S初始化为空 
    for(i = 0; i < G.vexnum; ++i)
		if(!indegree[i]) Push(S, i);     		//入度为0者进栈 
	m = 0;                               		//对输出顶点计数,初始为0 
	while(!StackEmpty(S)){                		//栈S非空 
		Pop(S, i);                          	//将栈顶顶点vi出栈
		topo[m]=i;                         		//将vi保存在拓扑序列数组topo中 
		++m;                             		//对输出顶点计数 
		ArcNode *p = G.vertices[i].firstarc;    //p指向vi的第一个邻接点 
		while(p){
			int k = p->adjvex;					//vk为vi的邻接点   
			--indegree[k];                   	//vi的每个邻接点的入度减1 
			if(indegree[k] ==0)  Push(S, k);	//若入度减为0,则入栈 
			p = p->nextarc;                		//p指向顶点vi下一个邻接结点 
		}//while 
	}//while
	
	if(m < G.vexnum)  return ERROR;    			//该有向图有回路 
	else return OK;
}//TopologicalSort 

int main(){
	cout << "************拓扑排序**************" << endl << endl;
	ALGraph G;
	CreateUDG(G);
	int *topo = new int [G.vexnum];
	
	cout << endl;
	cout << "有向图的邻接表、逆邻接表创建完成!" << endl << endl;

	if(TopologicalSort(G , topo)){
		cout << "该有向图的拓扑有序序列为:";
		for(int j = 0 ; j < G.vexnum; j++){
			if(j != G.vexnum - 1)
				cout << G.vertices[topo[j]].data << " , ";
			else
				cout << G.vertices[topo[j]].data << endl << endl;
		}//for
	}
	else
		cout << "网中存在环,无法进行拓扑排序!" <<endl << endl;
	return OK;
}//main

④.关键路径

AOE网(Activity On Edges)—用边表示活动的网络用一个有向图表示一个工程的各子工程及其相互制约的关系,弧表示活动,权表示活动持续的时间,顶点表示事件(活动的开始或结束时间),称这种有向图为边表示活动的网,简称AOE网;AOE网用来估算工程的完成时间。

名称 解释
源点 入度为0的顶点(只有1个)
汇点 出度为0的顶点(只有1个)
路径长度 路径上各活动持续时间之和
整个工程完成的时间 从有向图的源点到汇点的最长路径
关键路径 路径长度最长的路径
关键活动 关键路径上的活动,边上的权值增加将使有向图上的最长路径的长度增加。
ve(j) 表示事件V的最早发生时间
vl(j) 表示事件V,的最迟发生时间
e(i) 表示活动ai的最早开始时间
l(i) 表示活动ai的最迟开始时间
l(i)-e(i) 表示完成活动ai的时间余量

注意:在一个AOE网中,可以有不止一条的关键路径。

关键路径

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记

//关键路径算法

#include <iostream>
using namespace std;

#define MVNum 100                       	//最大顶点数
#define BDNum MVNum * (MVNum - 1)			//最大边数
#define OK 1	
#define ERROR 0 

typedef char VerTexType;

//- - - - -图的邻接表存储表示- - - - - 
typedef struct ArcNode{                		//边结点 
    int adjvex;                          	//该边所指向的顶点的位置
	int weight;								//权值
    struct ArcNode *nextarc;          		//指向下一条边的指针 
}ArcNode; 

typedef struct VNode{ 
    VerTexType data;                    	//顶点信息
    ArcNode *firstarc;                		//指向第一条依附该顶点的边的指针 
}VNode, AdjList[MVNum];               		//AdjList表示邻接表类型 

typedef struct{ 
    AdjList vertices;                 		//邻接表 
	AdjList converse_vertices;				//逆邻接表
    int vexnum, arcnum;              		//图的当前顶点数和边数 
}ALGraph;
//- - - - - - - - - - - - - - - -

//- - - - -顺序栈的定义- - - - -
typedef struct{
	int *base;
	int *top;
	int stacksize;
}spStack;
//- - - - - - - - - - - - - - - -

int indegree[MVNum];						//数组indegree存放个顶点的入度
int ve[BDNum];								//事件vi的最早发生时间
int vl[BDNum];								//事件vi的最迟发生时间
int topo[MVNum];							//记录拓扑序列的顶点序号
spStack S;

//----------------栈的操作--------------------
void InitStack(spStack &S){
	//栈的初始化
	S.base = new int[MVNum];
	if(!S.base)
		exit(1);
	S.top = S.base;
	S.stacksize = MVNum;
}//InitStack

void Push(spStack &S , int i){
	//入栈
	if(S.top - S.base == S.stacksize)
		return;
	*S.top++ = i;
}//Push

void Pop(spStack &S , int &i){
	//出栈
	if(S.top == S.base)
		return;
	i = *--S.top;
}//Pop

bool StackEmpty(spStack S){
	//判断栈是否为空
	if(S.top == S.base)
		return true;
	return false;
}//StackEmpty
//---------------------------------------

int LocateVex(ALGraph G , VerTexType v){
	//确定点v在G中的位置
	for(int i = 0; i < G.vexnum; ++i)
		if(G.vertices[i].data == v)
			return i;
		return -1;
}//LocateVex

int CreateUDG(ALGraph &G){ 
	//创建有向图G的邻接表、逆邻接表
	int i , k;
	
	cout <<"请输入总顶点数,总边数,以空格隔开:";
	cin >> G.vexnum >> G.arcnum;				//输入总顶点数,总边数 
    cout << endl;

	cout << "输入点的名称,如a" << endl;
	
	for(i = 0; i < G.vexnum; ++i){          		//输入各点,构造表头结点表
		cout << "请输入第" << (i+1) << "个点的名称:";
		cin >> G.vertices[i].data;           		//输入顶点值
		G.converse_vertices[i].data = G.vertices[i].data;
		//初始化表头结点的指针域为NULL 
		G.vertices[i].firstarc=NULL;			
		G.converse_vertices[i].firstarc=NULL;
    }//for
	cout << endl;

	cout << "输入边依附的顶点及其权值,如a b 3" << endl;

	for(k = 0; k < G.arcnum;++k){        			//输入各边,构造邻接表
		VerTexType v1 , v2;
		int i , j , w;
		cout << "请输入第" << (k + 1) << "条边依附的顶点及其权值:";
		cin >> v1 >> v2 >> w;                		//输入一条边依附的两个顶点
		i = LocateVex(G, v1);  j = LocateVex(G, v2);
		//确定v1和v2在G中位置,即顶点在G.vertices中的序号 

		ArcNode *p1=new ArcNode;               		//生成一个新的边结点*p1 
		p1->adjvex=j;                   			//邻接点序号为j
		p1->nextarc = G.vertices[i].firstarc;  G.vertices[i].firstarc=p1;
		p1->weight = w;
		//将新结点*p1插入顶点vi的边表头部

		ArcNode *p2=new ArcNode;               		//生成一个新的边结点*p1 
		p2->adjvex=i;                   			//逆邻接点序号为i
		p2->nextarc = G.converse_vertices[j].firstarc;  G.converse_vertices[j].firstarc=p2;
		p2->weight = w;
		//将新结点*p1插入顶点vi的边表头部
    }//for 
    return OK; 
}//CreateUDG

void FindInDegree(ALGraph G){
	//求出各顶点的入度存入数组indegree中 
	int i , count;

	for(i = 0 ; i < G.vexnum ; i++){
		count = 0;
		ArcNode *p = G.converse_vertices[i].firstarc;
		if(p){
			while(p){
				p = p->nextarc;
				count++;
			}
		}//if
		indegree[i] = count;
	}//for
}//FindInDegree

int TopologicalOrder(ALGraph G , int topo[]){ 
    //有向图G采用邻接表存储结构 
    //若G无回路,则生成G的一个拓扑序列topo[]并返回OK,否则ERROR 
	int i , m;
    FindInDegree(G);              				//求出各顶点的入度存入数组indegree中 
    InitStack(S);                          		//栈S初始化为空 
    for(i = 0; i < G.vexnum; ++i)
		if(!indegree[i]) Push(S, i);     		//入度为0者进栈 
	m = 0;                               		//对输出顶点计数,初始为0 
	while(!StackEmpty(S)){                		//栈S非空 
		Pop(S, i);                          	//将栈顶顶点vi出栈
		topo[m]=i;                         		//将vi保存在拓扑序列数组topo中 
		++m;                             		//对输出顶点计数 
		ArcNode *p = G.vertices[i].firstarc;    //p指向vi的第一个邻接点 
		while(p){
			int k = p->adjvex;					//vk为vi的邻接点   
			--indegree[k];                   	//vi的每个邻接点的入度减1 
			if(indegree[k] ==0)  Push(S, k);	//若入度减为0,则入栈 
			p = p->nextarc;                		//p指向顶点vi下一个邻接结点 
		}//while 
	}//while
	
	if(m < G.vexnum)  return ERROR;    			//该有向图有回路 
	else return OK;
}//TopologicalOrder

int CriticalPath(ALGraph G){ 
    //G为邻接表存储的有向网,输出G的各项关键活动
	int n , i , k , j , e , l;
    if (!TopologicalOrder(G, topo))  return ERROR; 
    //调用拓扑排序算法,使拓扑序列保存在topo中,若调用失败,则存在有向环,返回ERROR 
    n = G.vexnum;                  				//n为顶点个数 
    for(i = 0; i < n; i++)               		//给每个事件的最早发生时间置初值0 
		ve[i] = 0; 


    /*――――――――――按拓扑次序求每个事件的最早发生时间-――――-―――――*/ 
    for(i = 0;i < n; i++){                 
		k = topo[i];                   			//取得拓扑序列中的顶点序号k             
		ArcNode *p = G.vertices[k].firstarc;    //p指向k的第一个邻接顶点  
		while(p != NULL){            			//依次更新k的所有邻接顶点的最早发生时间   
			j = p->adjvex;               		//j为邻接顶点的序号                   
			if(ve[j] < ve[k] + p->weight)    	//更新顶点j的最早发生时间ve[j] 
				ve[j] = ve[k] + p->weight;     
			p = p->nextarc;              		//p指向k的下一个邻接顶点  
		} //while 
    } //for 

    for(i=0;i<n;i++)                 			//给每个事件的最迟发生时间置初值ve[n-1] 
		vl[i]=ve[n-1];
	
    /*――――――――――按逆拓扑次序求每个事件的最迟发生时间-――――-―――――*/ 
    for(i = n - 1;i >= 0; i--){               
		k = topo[i];                   			//取得拓扑序列中的顶点序号k             
		ArcNode *p = G.vertices[k].firstarc;    //p指向k的第一个邻接顶点  
		while(p != NULL){            			//根据k的邻接点,更新k的最迟发生时间   
			j = p->adjvex;              		//j为邻接顶点的序号                   
			if(vl[k] > vl[j] - p->weight)    	//更新顶点k的最迟发生时间vl[k] 
				vl[k] = vl[j] - p->weight;       
			p = p->nextarc;              		//p指向k的下一个邻接顶点  
		}//while 
    }//for 

    /*――――――――――――判断每一活动是否为关键活动-――――――-―――――*/
	cout << endl;
	cout << "关键活动路径为:";
    for(i = 0;i < n; i++){                		//每次循环针对vi为活动开始点的所有活动 
        ArcNode *p = G.vertices[i].firstarc;    //p指向i的第一个邻接顶点  
        while(p != NULL) {    
			j = p->adjvex;             			//j为i的邻接顶点的序号    
			e = ve[i];                 			//计算活动<vi, vj>的最早开始时间 
			l = vl[j] - p->weight;      		//计算活动<vi, vj>的最迟开始时间 
			if(e == l)               			//若为关键活动,则输出<vi, vj> 
				cout << G.vertices[i].data << "-->" << G.vertices[j].data << " ";
			p = p->nextarc;              		//p指向i的下一个邻接顶点  
		} //while 
	} //for  
	return OK;
}//CriticalPath

int main(){
	cout << "************关键路径算法**************" << endl << endl;
	ALGraph G;
	CreateUDG(G);
	int *topo = new int [G.vexnum];
	
	cout << endl;
	cout << "有向图创建完成!" << endl << endl;
	
	if(!CriticalPath(G))
		cout << "网中存在环,无法进行拓扑排序!" <<endl << endl;
	cout << endl;
	return OK;
}//main

例:

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记


6、总结

数据结构(C语言版)-- 图笔记

(1)根据不同的分类规则,图分为多种类型:无向图、有向图、完全图、连通图、强连通图、带权图(网)、稀疏图和稠密图等。邻接点、路径、回路、度、连通分量、生成树等是在图的算法设计中常用到的重要术语。

(2)图的存储方式有两大类:以边集合方式的表示法和以链接方式的表示法。其中,以边集合方式表示的为邻接矩阵,以链接方式表示的包括邻接表、十字链表和邻接多重表。邻接矩阵表示法借助二维数组来表示元素之间的关系,实现起来较为简单;邻接表、十字链表和邻接多重表都属于链式存储结构,实现起来较为复杂。在实际应用中具体采取哪种存储表示,可以根据图的类型和实际算法的基本思想进行选择。其中,邻接矩阵和邻接表是两种常用的存储结构,二者之间的比较如下图所示。

数据结构(C语言版)-- 图笔记

( 3 )图的遍历算法是实现图的其他运算的基础,**图的遍历方法有两种:深度优先搜索遍历和广度优先搜索遍历。**深度优先搜索遍历类似于树的先序遍历,借助于栈结构来实现(递归);广度优先搜索遍历类似于树的层次遍历,借助于队列结构来实现。两种遍历方法的不同之处仅仅在于对顶点访问的顺序不同,所以时间复杂度相同。当用邻接矩阵存储时,时间复杂度为均O(n2),用邻接表存储时,时间复杂度均为O(n+e)。

(4)图的很多算法与实际应用密切相关,比较常用的算法包括构造最小生成树算法、求解最短路径算法、拓扑排序和求解关键路径算法。
构造最小生成树有普里姆算法和克鲁斯卡尔算法,两者都能达到同一目的。但前者算法思想的核心是归并点,时间复杂度是O(n2),适用于稠密网;后者是归并边,时间复杂度是O(elog2e),适用于稀疏网。
最短路径算法:一种是迪杰斯特拉算法,求从某个源点到其余各顶点的最短路径,求解过程是按路径长度递增的次序产生最短路径,时间复杂度是O(n2);另一种是弗洛伊德算法,求每一对顶点之间的最短路径,时间复杂度是O(n3),从实现形式上来说,这种算法比以图中的每个顶点为源点n次调用迪杰斯特拉算法更为简洁。
拓扑排序和关键路径都是有向无环图的应用。拓扑排序是基于用顶点表示活动的有向图,即AOV-网。对于不存在环的有向图,图中所有顶点一定能够排成一个线性序列,即拓扑序列,拓扑序列是不唯一的。用邻接表表示图,拓扑排序的时间复杂度为O(n+e)。
关键路径算法是基于用弧表示活动的有向图,即AOE-网。关键路径上的活动叫做关键活动,这些活动是影响工程进度的关键,它们的提前或拖延将使整个工程提前或拖延。关键路径是不唯一的。关键路径算法的实现是在拓扑排序的基础上,用邻接表表示图,关键路径算法的时间复杂度为O(n+e)。


7、例题与应用

数据结构(C语言版)-- 图笔记
数据结构(C语言版)-- 图笔记

  1. 编程实现如下功能:
    (1)输入无向图的顶点数、边数及各条边的顶点对,建立用邻接矩阵表示的无向图。
    (2)对图进行深度优先搜索和广度优先搜索遍历,并分别输出其遍历序列。

测试样例无向图
数据结构(C语言版)-- 图笔记
运行结果
数据结构(C语言版)-- 图笔记

#include <bits/stdc++.h>
using namespace std;
#define MVNum 100
#define MAXQSIZE 100
typedef char VerTexType;
typedef int ArcType;
//图的邻接矩阵存储表示
typedef struct
{
    VerTexType vexs[MVNum];
    ArcType arcs[MVNum][MVNum];
    int vexnum, arcnum;
} Graph;
//队列的定义及操作
typedef struct
{
    ArcType *base;
    int front;
    int rear;
} sqQueue;
bool visited[MVNum];
bool visited1[MVNum];
int FirstAdjVex(Graph G, int v);
int NextAdjVex(Graph G, int v, int w);
//构建空队列Q
void InitQueue(sqQueue &Q)
{
    Q.base = new ArcType[MAXQSIZE];
    if (!Q.base)
        exit(1);
    Q.front = Q.rear = 0;
}
//插入元素e为Q的新的队尾元素
void EnQueue(sqQueue &Q, ArcType e)
{
    if ((Q.rear + 1) % MAXQSIZE == Q.front)
        return;
    Q.base[Q.rear] = e;
    Q.rear = (Q.rear + 1) % MAXQSIZE;
}
//判断是否为空队
bool QueueEmpty(sqQueue Q)
{
    if (Q.rear == Q.front)
        return true;
    return false;
}
//队头元素出队并置为u
void DeQueue(sqQueue &Q, ArcType &u)
{
    u = Q.base[Q.front];
    Q.front = (Q.front + 1) % MAXQSIZE;
}
//确定点v在G中的位置
int LocateVex(Graph G, VerTexType v)
{
    for (int i = 0; i < G.vexnum; ++i)
        if (G.vexs[i] == v)
            return i;
    return -1;
}
//采用邻接矩阵表示法,创建无向网G
void CreateUDN(Graph &G)
{
    int i, j, k;
    cout << "输入总顶点数,总边数,以空格隔开:(例:3 2)";
    cin >> G.vexnum >> G.arcnum;
    cout << endl;

    cout << "输入点的名称(例:a)" << endl;

    for (i = 0; i < G.vexnum; ++i)
    {
        cout << "请输入第" << (i + 1) << "个点的名称:";
        cin >> G.vexs[i];
    }
    cout << endl;

    for (i = 0; i < G.vexnum; ++i)
        for (j = 0; j < G.vexnum; ++j)
            G.arcs[i][j] = 0;
    cout << "输入边依附的顶点(例a b)" << endl;
    for (k = 0; k < G.arcnum; ++k)
    {
        VerTexType v1, v2;
        cout << "请输入第" << (k + 1) << "条边依附的顶点:";
        cin >> v1 >> v2;
        i = LocateVex(G, v1);
        j = LocateVex(G, v2);
        G.arcs[j][i] = G.arcs[i][j] = 1;
    }
}
// DFS
void DFS(Graph G, int v)
{
    int w;
    cout << G.vexs[v] << "    ";
    visited[v] = true;
    for (w = 0; w < G.vexnum; w++)
        if ((G.arcs[v][w] != 0) && (!visited[w]))
            DFS(G, w);
}
// BFS
void BFS(Graph G, int v)
{
    sqQueue Q;
    ArcType u;
    ArcType w;

    cout << G.vexs[v] << "    ";
    visited[v] = true;
    InitQueue(Q);
    EnQueue(Q, v);
    while (!QueueEmpty(Q))
    {
        DeQueue(Q, u);
        for (w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w))
        {
            if (!visited[w])
            {
                cout << G.vexs[w] << "    ";
                visited[w] = true;
                EnQueue(Q, w);
            }
        }
    }
}
//返回v的第一个邻接点
int FirstAdjVex(Graph G, int v)
{
    int i;
    for (i = 0; i < G.vexnum; ++i)
    {
        if (G.arcs[v][i] == 1 && visited[i] == false)
            return i;
    }
    return -1;
}
//返回v相对于w的下一个邻接点
int NextAdjVex(Graph G, int v, int w)
{
    int i;
    for (i = w; i < G.vexnum; ++i)
    {
        if (G.arcs[v][i] == 1 && visited[i] == false)
            return i;
    }
    return -1;
}
int main()
{
    Graph G;
    CreateUDN(G);
    cout << endl;
    cout << "无向图G创建完成!" << endl
         << endl;
    cout << "输入遍历无向图G的起始点:";
    VerTexType c;
    cin >> c;
    int i;
    for (i = 0; i < G.vexnum; ++i)
    {
        if (c == G.vexs[i])
            break;
    }
    cout << endl;
    while (i >= G.vexnum)
    {
        cout << "该点不存在,请重新输入!" << endl;
        cout << "请输入遍历连通图的起始点:";
        cin >> c;
        for (i = 0; i < G.vexnum; ++i)
        {
            if (c == G.vexs[i])
                break;
        }
    }
    cout << "深度优先搜索遍历无向图G结果:" << endl;
    DFS(G, i);

    for (int j = 1; j <= G.vexnum; j++)
        visited[j] = false;

    cout << endl;

    cout << "广度优先搜索遍历无向图G结果:" << endl;
    BFS(G, i);

    cout << endl;
    return 0;
}
上一篇:c++中简单的继承


下一篇:C++中关于cout相关的输出格式(操作流算子)