数据结构--图论

目录

一、图的基本知识

二、图的存储结构

(1)邻接矩阵

(2)邻接表

三、图的遍历方式

(1)DFS(Deep First Search 深度优先搜索)

(2)BFS(Breadth First Search 宽度优先搜索&广度优先搜索)

(3)略谈DFS与BFS其他用途

四、图的应用

(1)最小生成树(MST-Minimum Spanning Tree)

1.普里姆算法(Prim)

2.克鲁斯卡尔算法(Kruskal)

3.算法分析

(2)最短路径

1.迪杰斯特拉算法(Dijkstra)

2.弗洛伊德算法(Floyd)

3.算法分析:

(3)拓扑排序

一、图的基本知识

  • :是由数据元素的集合及数据元素之间的关系集合组成的一种数据结构,G=(V,E),其中V是顶点集,E是边集
  • 无向图:如果顶点之间没有方向,称为无向图,无向图的边表示一般用圆括号(v,w)
  • 有向图:如果顶点之间有方向,就称为有向图,有向图的边表示一般用尖括号,又称为弧
  • 完全图:在有n个顶点的无向图中,有n(n-1)/2条边,称此图为完全无向图;在有n个顶点的有向图中,有n(n-1)条边,则称此图 为完全有向图(每一个顶点与另外n-1个顶点都有编项链)
  • :边上具有与它相关的系数,即称为权,带权图也被称为网络
  • 邻接点:如果(v,w)是无向图G的一条边,则称V与W互为邻接顶点;如果是有向图G中的一条弧,则称顶点v邻接到顶点到w(v是w的前驱),顶点w邻接自v(w是v的后继)
  • 子图:设图G=(V,E)和图G'=(V',E')。若V'属于V,E'属于E,则称图G'是图G的子图
  • 顶点的度:无向图中,顶点V的度表示依附于V的边数,记作TD(v);有向图中,以顶点v为始点的有向边的条数称为v的出度,记 作OD(v),以顶点v为终点的有向边的条数称为v的入度,记作ID(v),有向图中顶点v的度等于该顶点的入度与出度之和:TD(v)=ID(v)+OD(v)
  • 路径:在图G=(V,E)中,从顶点vi出发,沿一些边或弧经过一些顶点Vp1,Vp2.....,到达顶点vj,则称顶点序列(vi,vp1,vp2.....vj)为从打顶点vi到顶点vj的路径
  • 路径长度:对于不带权的图,路径长度是指此路径上边的数目;带权图,路径长度是指路径上各边的权之和
  • 简单路径和回路:对于一套路径,若路径上各顶点均不相同,则称该路径为简单路径;若顶点上第一个顶点和最后一个顶点相同,则称该路径为回路或环
  • 连通图和连通分量:在无向图中,若从顶点vi到vj有路径,则称顶点vi与vj使连通的,如果无向图中任意两个顶点都是连用的,则称此无向图为连通图。非连通图的极大连通子图(包含所有连通的顶点和这些顶点依附的所有的边)叫做连通分量
  • 强连通图和强连通分量:在有向图中,若对于顶点vi和vj,存在一条从vi到vj和从vj到vi的路径,则称顶点vi和顶点vj时强连通。如果有向图中任意两个顶点都是强连通的,则称此有向图为强连通图。非强连通图的极大强连通子图叫做强连通分量
  • 生成树:一个连通图的生成树是它的极小连通子图(包含图中全部n个顶点及使这些顶点连通的n-1条

二、图的存储结构

(1)邻接矩阵

对于带权图可表示

邻接矩阵存储表示

#define MaxInt 32768                //表示极大值, 即
#define MVNum                       //最大顶点
typedef char VerTexType;            //设顶点的数据类型为字符型
typedef int ArcType;                //设边的权值为整形
typedef struct
{
    VerTexType vexs[MVNum];         //顶点表
    ArcType arcs[MVNum][MVNum]      //邻接矩阵
    int vexnum, arcnum              //顶点数、边数
}AMGraph;
/*
确定顶点vex在图G中的位置
*/

int LocateVex(AMGraph G, VerTexType vex)
{
    for(int i = 0; i < G.vexnum; i ++)
        if(vex == G.vexs[i])return i;
}
/*
以无向带权图为例
*/
void CreateUDN(AMGraph &G)
{
    cout<<"请输入顶点和边的数目:"<>G.vexnum>>G.arcnum;
    //对无向图初始化
    for(int i = 0; i < G.vexnum; i ++)
    {
        for(int j = 0; j < G.vexnum; j ++)
        {
            G.arcs[i][j] = MaxInt;
        }
    }
    cout<<"请输入顶点:"<>G.vexs[i];
    cout<<"请输入边:"<>u>>v>>w;
        int uid = LocateVex(G, u), vid = LocateVex(G, v);
        G.arcs[uid][vid] = G.arcs[vid][uid] = w;
    }
}

邻接矩阵优缺点:

优点:

直接判断两点是否右边相连

方面计算个点的度(入度与出度)

缺点:

不便增加和删除顶点

不便统计边的数目

空间复杂度高O(n^2),浪费空间

综上:邻接矩阵适用于稠密图

(2)邻接表

typedef char VerTexType;    //顶点的数据类型
typedef int ArcType;        //边的权值的数据类型
typedef struct ArcNode      //定义边节点
{
    int adjvex;                 //指向下一个顶点的位置
    int val;                    //边的的权值
    struct ArcNode *nextarc;    //指向下一个边的指针
//    OtherInfo inof;
}ArcNode;
typedef struct VNode            //定义每一个顶点
{
    VerTexType data;            //顶点表示的值
    ArcNode *firstarc;          //指向第一条依附于该顶点的边的指针
}VNode, AdjList[MVNum];         //邻接表

typedef struct                  //定义图
{
    AdjList vertices;           //图的顶点
    int vexnum, arcnum;         //图的顶点数、边数
}ALGraph;
/*
以无向带权图图为例
*/

void CreateUDN(ALGraph &G)
{
    ArcNode *pu, *pv;
    cout<<"请输入顶点和边的数目:"<>G.vexnum>>G.arcnum;
    cout<<"请输入顶点:"<>G.vertices[i].data;
        G.vertices[i].firstarc = NULL;  //将改顶点的表头指针域置为NULL
    }
    cout<<"请输入边:"<>u>>v>>w;
        int uid = LocateVex(G, u), vid = LocateVex(G, v);
        pu = new ArcNode; pv = new ArcNode;
        pu->adjvex = vid; pu->val = w;
        pu->nextarc = G.vertices[uid].firstarc; G.vertices[uid].firstarc = pu;

//        //无向图
        pv->adjvex = uid; pv->val = w;
        pv->nextarc = G.vertices[vid].firstarc; G.vertices[vid].firstarc = pv;
    }
}

邻接表的优缺点:

优点:

便于增加和删除顶点

便于统计边的数目

空间效率高

缺点:

不便于判断顶点与顶点之间是否存在边

不便于计算图各顶点的度

综上:邻接表使用稀疏图

三、图的遍历方式

(1)DFS(Depth First Search 深度优先搜索)

类似于树的先序遍历,用递归实现,其思路于下:

a.从图中某一顶点v出发,访问v;

b.找出刚访问的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新起点,重复此操作,直至刚访问过得顶点中没有未被访问的邻接点为之;

c.返回前一个访问过的且仍有未被访问的邻接的顶点,

d.重复操作b和c, 直至图中所有顶点都被访问过,结束搜索。

代码实现

/*
以存储方式为邻接矩阵的无向图为例

新定义数组 visited[MVNum]
visited[i] = 0 表示序号为i的顶点未被访问
visited[i] = 1 表示序号为i的顶点已被访问
*/

void DFS(AMGraph G, VerTexType vert)
{
    int vid = LocateVex(G, vert);
    visited[vid] = 1; cout<

(2)BFS(Breadth First Search 宽度优先搜索&广度优先搜索)

类似于树的按层次遍历的过程,用队列实现,其过程于下

a.从图的一个顶点v出发, 访问该点;

b.以此访问顶点v的各个未被访问的邻接点;

c.分别从这些邻接点出发依次访问他们的邻接点,重复此操作,直至所有已被访问的顶点的邻接点都被访问到。

代码实现

/*
以存储方式为邻接矩阵的无向图为例


为方便,使用C++STL中已集成好的队列queue
同时使用visited[MVNum]数组, 标记顶点的访问情况

*/

void BFS(AMGraph G, VerTexType v)
{
    //以顶点v为起始点
    for(int i = 0; i < MVNum; i ++)visited[i] = 0;    //将所有顶点初始化为未访问
    queueque;
    que.push(G.vexs[v]);
    while(!que.empty())
    {
        VerTexType vert = que.front(); que.pop();
        int vid = LocateVex(G, vert);
        if(!visited[vid])                            //若顶点vert未被访问
        {
            visited[vid] = 1;
            cout<

(3)略谈DFS与BFS其他用途

二者都可用于求图的连通度

最短路问题一般用BFS实现

其他用途可查略其他资料

四、图的应用

(1)最小生成树(MST-Minimum Spanning Tree)

1.普里姆算法(Prim)

思想:

a.从图中一点v出发,选择与点v相邻的权值最小的边,将其加入MST的顶点集合U中;

b.再从集合U出发,将不再集合U中且与集合U相连的各边中权值最小的边加入集合U中;

c.重复操作b,知道所有顶点都加入集合U中。

代码实现:

/*
邻接矩阵存储的无向带权
*/
#include 
using namespace std;
#define MaxInt 32768
#define MVNum 100
typedef char VerTexType ;
typedef int ArcType;
typedef struct
{
    VerTexType vexs[MVNum];
    ArcType arcs[MVNum][MVNum];
    int vexnum, arcnum;
}AMGraph;
struct closedge
{
    ArcType cost;
    int adjvex;
}ClosEdge[MVNum];

int LocateVex(AMGraph G, VerTexType v)
{
    for(int i = 0; i < G.vexnum; i ++)
    {
        if(G.vexs[i] == v)return i;
    }
}
//建立无向带权图
void CreateUDN(AMGraph &G)
{
    cout<<"输入顶和边的数目:"<>G.vexnum>>G.arcnum;
    for(int i = 0; i < G.vexnum; i ++)cin>>G.vexs[i];
    for(int i = 0; i < G.vexnum; i ++)
    {
        for(int j = 0; j < G.vexnum; j ++)
        {
            G.arcs[i][j] = MaxInt;
        }
    }
    VerTexType u, v;
    ArcType w;
    for(int i = 0; i < G.arcnum; i ++)
    {
        cin>>u>>v>>w;
        int uid = LocateVex(G, u), vid = LocateVex(G, v);
        G.arcs[uid][vid] = G.arcs[vid][uid] = w;
    }
}
void Prim(AMGraph G, VerTexType v)
{
    int visited[MVNum];             //记录顶点是否被访问
    for(int i = 0; i < G.vexnum; i ++) visited[i] = 0;
    int vid = LocateVex(G, v);
    visited[vid] = 1;
    int Min;
    //初始化
    for(int i = 0; i < G.vexnum; i ++)
    {
        if(!visited[i])
        {
            ClosEdge[i].cost = G.arcs[vid][i];
            ClosEdge[i].adjvex = vid;
        }
    }
    int u;
    ArcType res = 0;
    for(int i = 1; i < G.vexnum; i ++)
    {
        Min = MaxInt;
        //找出权值最小的边
        for(int j = 0; j < G.vexnum; j ++)
        {
            if(!visited[j] && ClosEdge[j].cost < Min)
            {
                Min = ClosEdge[j].cost;
                u = j;
            }
        }
        res += ClosEdge[u].cost;
        cout< "<2.克鲁斯卡尔算法(Kruskal) 
  

思想:

a.将左右边按权值从小到大排序;

b.依次选取选取权值小的边,若该边的两个顶点均在MST集合U中,则放弃次边,否则将次边加入集合U中

c.重复操作c,直至所选的边数等于顶点的数目减一结束。

代码实现:

使用并查集,两个操作

a.判断边的两个顶点是否拥有相同祖先(即两个顶点是否同在集合U中)

b.合并边的两个顶点的公共祖先(即将两个顶点加入集合U中)

#include 
using namespace std;

/*
邻接矩阵实现无向带权图的最小生成树
*/
#define MaxInt 32768
#define MVNum 100
typedef char VerTexType;
typedef int ArcType;

int father[MVNum];          //记录顶点的祖宗节点
int NumEdge;                //记录边的数目

typedef struct
{
    VerTexType vexs[MVNum];
    ArcType arcs[MVNum][MVNum];
    int vexnum, arcnum;
}AMGraph;

//克鲁斯卡尔算法

struct ArcEdge                       //定义边
{
    int head, tail;                  //边的头和尾
    ArcType cost;                    //边的权值
}Edge[MVNum * MVNum];

//按边的从小到大排序
bool cmp(const ArcEdge &a, const ArcEdge &b)
{
    return a.cost < b.cost;
}

//添加边
void AddEdge(int u, int v, ArcType w)
{
    Edge[NumEdge].head = u;
    Edge[NumEdge].tail = v;
    Edge[NumEdge].cost = w;
    NumEdge ++;
}

//获取父节点
int GetFather(int a)
{
    return father[a] == a ? a : GetFather(father[a]);
}

//合并两个节点
void Unionn(int x, int y)
{
    father[GetFather(x)] = GetFather(y);
}
int LocateVex(AMGraph G, VerTexType vex)
{
    for(int i = 0; i < G.vexnum; i ++)
        if(vex == G.vexs[i])return i;
}
//创建一个图
void CreateUDN(AMGraph &G)
{
    cout<<"请输入顶点和边的数目:"<>G.vexnum>>G.arcnum;
    //对无向图初始化
    for(int i = 0; i < G.vexnum; i ++)
    {
        for(int j = 0; j < G.vexnum; j ++)
        {
            G.arcs[i][j] = MaxInt;
        }
    }
    cout<<"请输入顶点:"<>G.vexs[i];
    cout<<"请输入边:"<>u>>v>>w;
        int uid = LocateVex(G, u), vid = LocateVex(G, v);
        G.arcs[uid][vid] = G.arcs[vid][uid] = w;
        /*
        此处即为克鲁斯卡尔算法的建边
        因为是无向图,要建两次边
        */
        AddEdge(uid, vid, w);
        AddEdge(vid, uid, w);
    }
}

void Kruskal(AMGraph G)
{
    //初始化个顶点的祖宗节点为本身,即初始各个顶点互不相连
    sort(Edge, Edge + NumEdge, cmp);    //按边排序
    for(int i = 0; i < G.vexnum; i ++)father[i] = i;
    int tot = 0;
    ArcType res = 0;
    for(int i = 0; i < NumEdge; i ++)
    {
        int head = Edge[i].head;
        int tail = Edge[i].tail;
        ArcType cost = Edge[i].cost;
        if(GetFather(head) == GetFather(tail))continue;
        //如何该边的两个顶点的祖宗节点相同,则说明这两个顶点属于一个集合,所以要跳出循环

        //否则该边可以作为生成树的一条边
        cout<"<

3.算法分析

分析:Prim算法的复杂度为O(n^2),适用于稠密图

Kruskal算法复杂度为O(n + e)使用稀疏图

(2)最短路径

1.迪杰斯特拉算法(Dijkstra)

#include 
using namespace std;
#define MaxInt 32768
#define MVNum 100
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)
{
    for(int i = 0; i < G.vexnum; i ++)
    {
        if(G.vexs[i] == v)return i;
    }
}
//建立无向带权图
void CreateUDN(AMGraph &G)
{
    cout<<"输入顶和边的数目:"<>G.vexnum>>G.arcnum;
    for(int i = 0; i < G.vexnum; i ++)cin>>G.vexs[i];
    for(int i = 0; i < G.vexnum; i ++)
    {
        for(int j = 0; j < G.vexnum; j ++)
        {
            G.arcs[i][j] = MaxInt;
        }
    }
    VerTexType u, v;
    ArcType w;
    for(int i = 0; i < G.arcnum; i ++)
    {
        cin>>u>>v>>w;
        int uid = LocateVex(G, u), vid = LocateVex(G, v);
        G.arcs[uid][vid] = G.arcs[vid][uid] = w;
    }
}
void Dijkstra(AMGraph G, VerTexType v)
{
    int visited[MVNum], lowcost[MVNum], pre[MVNum];
    //pre[j]表示到顶点j前驱
    int vid = LocateVex(G, v);
    for(int i = 0; i < G.vexnum; i ++)
    {
        lowcost[i] = MaxInt; pre[i] = -1; visited[i] = 0;
    }
    lowcost[vid] = 0;
    int u;
    while(1)
    {
        u = -1;
        //找较小的路径
        for(int v = 0; v < G.vexnum; v ++)
        {
            if(!visited[v] && (u == -1 || lowcost[v] < lowcost[u]))
            {
                u = v;
            }
        }
        if(u == -1)break;
        visited[u] = 1;
        cout< lowcost[u] + G.arcs[u][v]))
            {
                lowcost[v] = lowcost[u] + G.arcs[u][v];
                pre[v] = u;
            }
        }
    }
    int path[MVNum]; 
    //回溯最短路的路径
    for(int i = 0; i < G.vexnum; i ++)
    {
        if(vid == i)continue;
        int t = i, ans = 0;
        while(pre[t] != -1)
        {
             path[ans++] = pre[t];
             t = pre[t];
        }
        cout<<"to the vert the  "<= 0; j--)cout<

2.弗洛伊德算法(Floyd)

关键方程为 lowcost[i][j] = min(lowcost[i][j], lowcost[i][k] + lowcost[k][j]);(初始化时lowcost[i][j] = G.arcs[i][j])

#include 
using namespace std;
#define MaxInt 32768
#define MVNum 100
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)
{
    for(int i = 0; i < G.vexnum; i ++)
    {
        if(G.vexs[i] == v)return i;
    }
}
//建立无向带权图
void CreateUDN(AMGraph &G)
{
    cout<<"输入顶和边的数目:"<>G.vexnum>>G.arcnum;
    for(int i = 0; i < G.vexnum; i ++)cin>>G.vexs[i];
    for(int i = 0; i < G.vexnum; i ++)
    {
        for(int j = 0; j < G.vexnum; j ++)
        {
            G.arcs[i][j] = MaxInt;
        }
    }
    VerTexType u, v;
    ArcType w;
    for(int i = 0; i < G.arcnum; i ++)
    {
        cin>>u>>v>>w;
        int uid = LocateVex(G, u), vid = LocateVex(G, v);
        G.arcs[uid][vid] = G.arcs[vid][uid] = w;
    }
}
void Floyd(AMGraph G)
{
    ArcType lowcost[MVNum][MVNum];
    for(int i = 0; i < G.vexnum; i ++)
    {
        for(int j = 0; j < G.vexnum; j ++)
        {
            lowcost[i][j] = G.arcs[i][j];
        }
    }

    for(int k = 0; k  < G.vexnum; k ++)
    {
        for(int i = 0; i < G.vexnum; i ++)
        {
            for(int j = 0; j < G.vexnum; j ++)
            {
                lowcost[i][j] = min(lowcost[i][j], lowcost[i][k] + lowcost[k][j]);
            }
        }
    }
    for(int i = 0 ; i < G.vexnum; i ++)
    {
        for(int j = 0; j < G.vexnum; j ++)
        {
            cout<

3.算法分析:

Dijkstra算法的复杂度为O(n^2),Floyd算法的复杂度为O(n^3)

Dijkstra算法只能求一点到其他点的最短路径,Floyd算法能求任意两点的最短路径

(3)拓扑排序

拓扑排序:将AOV-网(Activity On Vertex Network)中所有顶点排成一个线性序列,该线性序列满足:若在AOV-网中由顶点vi到顶点vj有一条路径,则在该线性序列中的顶点vi必在顶点vj之前。(前提:AOV-网不能出现有向环

 

数据结构--图论_第1张图片

其拓扑序列可以为 e  b  g  f  a  d  c  h

代码实现:(栈实现)

/*
使用邻接表存储的有向图
为方便起见,,使用C++STL中集成的栈stack

*/
#include 
using namespace std;

#define MaxInt 32768
#define MVNum 100
typedef char VerTexType;    //节点的数据类型
typedef int ArcType;

int indegree[MVNum];
typedef struct ArcNode      //定义节点
{
    int adjvex;                 //指向下一个节点的位置
    struct ArcNode *nextarc;    //指向下一个节点
}ArcNode;
typedef struct VNode            //定义每一个顶点
{
    VerTexType data;            //顶点表示的值
    ArcNode *firstarc;          //指向第一条依附于该顶点的边的指针
}VNode, AdjList[MVNum];         //邻接表

typedef struct                  //定义图
{
    AdjList vertices;           //图的顶点
    int vexnum, arcnum;         //图的顶点数、边数
}ALGraph;

int LocateVex(ALGraph G, VerTexType v)
{
    for(int i = 0; i < G.vexnum; i ++)
        if(v == G.vertices[i].data)return i;
}
//建立有向图(无环)
void CreateDN(ALGraph &G)
{
    ArcNode *p;
    cout<<"请输入顶点和边的数目:"<>G.vexnum>>G.arcnum;
    cout<<"请输入顶点:"<>G.vertices[i].data;
        G.vertices[i].firstarc = NULL;
    }
    for(int i = 0; i < G.vexnum; i ++)indegree[i] = 0;
    VerTexType u, v;
    cout<<"请输入边:"<>u>>v;
        uid = LocateVex(G, u); vid = LocateVex(G, v);
        p = new ArcNode;
        indegree[vid] ++;
        p->adjvex = vid;
        p->nextarc = G.vertices[uid].firstarc; G.vertices[uid].firstarc = p;
    }
}
stackst;
void TopoSort(ALGraph G)
{
    VerTexType topo[MVNum];
    for(int i = 0; i < G.vexnum; i ++)
    {
        if(!indegree[i])st.push(i);         //入度为零的顶点入栈
    }
    int m = 0;                          //记录顶点的个数
    while(!st.empty())
    {
        int v = st.top(); st.pop();         //将栈顶顶点弹出
        topo[m++] = G.vertices[v].data;
        ArcNode *p;
        p = G.vertices[v].firstarc;
        while(p)
        {
            //将与顶点p邻接点顶点的入度减一
            int k = p->adjvex;
            indegree[k]--;
            if(!indegree[k]) st.push(k);        //入度为零的顶点入栈
            p = p->nextarc;
        }
    }
    if(m < G.vexnum) {cout<<"存在有向环"<

 

 

你可能感兴趣的:(数据结构)