数据结构——图

图(Graph)

(参考资料:《大话数据结构》《算法导论》)

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


图的相关术语

1.无向图与有向图

无向图:图中任意两个顶点u和v之间的边没有方向(无向边),每一条无向边用无序偶对(u,v)或(v,u)表示。

无向完全图:任意两个顶点之间都存在边,边个数:n*(n-1)/2(n个顶点)。

有向图:图中任意两个顶点u和v之间的边具有方向(有向边,弧),每一条从顶点u到顶点v的有向边用有序偶对 < u,v > 表示,u称为弧尾,v称为弧头。

有向完全图:任意两个顶点之间都存在方向相反的两条弧,边个数:n*(n-1)(n个顶点)。

  • 无向边用”(u,v)”表示,有向边用“ < u,v > “表示。

2.稀疏图和稠密图

稀疏图:边的条数|E|远小于|v|^2的图。

反之,称为稠密图。

3.权重图(网)

图中的每一条边都带有一个相关的权重的图。

权重图可以由邻接表和邻接矩阵表示(用权重值代替1),若含0权重,则可以用无穷数表示无边情况。

4.顶点的度

无向图:顶点u的度是和u相关联的边的数目。

有向图:顶点u的入度是以顶点u为弧头的边的数目;顶点u的出度是以顶点u为弧尾的边的数目。

5.路径

从顶点u到顶点v的路径是一个顶点序列。

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

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

回路或环:第一个顶点到最后一个顶点相同的路径。

简单回路或简单环:除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路。


图的表示

1.邻接表

邻接链表表示有一个包含|V|条链表的数组Adj所构成,每个顶点有一条链表。对于每个顶点u,邻接链表Adj[u]包含所有与顶点u之间有边相连的顶点v。

存储空间:O(V+E)。

无向图:所有邻接链表的长度之和为2|E|;有向图:所有邻接链表的长度之和为|E|。

优点

1.表示稀疏图,表示紧凑,节约空间,表示简单。

2.鲁棒性高,可以对其进行简单修改来支持许多其他的图变种。

缺点

1.无法快速判断两个顶点u和v之间是否是图中的一条边,需要在邻接链表Adj[u]里搜索结点v。


#define MAXVEX 10              // maximum vertex numbers in the graph

typedef int VertexType;        // vertex data type
typedef int EdgeType;          // edge weight type
// edge node to record the adjacent vertex node for a vertex node
typedef struct EdgeNode {
    int adjvex;                // the adjacent vertex node position in array
    EdgeType weight;           // edge weight
    struct EdgeNode* next;     // next edge node
}EdgeNode;
// vertex node to record the data for a vertex node
typedef struct VertexNode {
    VertexType data;           // vertex data
    EdgeNode *firstedge;       // first edge node pointer
}VertexNode;
// graph
typedef struct {
    VertexNode adj[MAXVEX];
    int numVertexes;
    int numEdges;
}Graph;
Graph* CreateGraph()
{
    Graph* G = new Graph;
    cout << "input vertex num and edge num:" << endl;
    cin >> G->numVertexes >> G->numEdges;
    cout << "input vertex data one by one:" << endl;
    for (int index = 0; index < G->numVertexes; index++) {
        cin >> G->adj[index].data;
        G->adj[index].firstedge = NULL;
    }
    cout << "input edge each by two vertex index(begin from 0):" << endl;
    for (int index = 0; index < G->numEdges; index++) {
        int i, j;
        cin >> i >> j;
        EdgeNode* e = new EdgeNode;
        e->adjvex = j;
        e->next = G->adj[i].firstedge;
        G->adj[i].firstedge = e;

#define NODRECTIONGRAPH

#ifdef NODRECTIONGRAPH

        e= new EdgeNode;
        e->adjvex = i;
        e->next = G->adj[j].firstedge;
        G->adj[j].firstedge = e;

#endif

    }
    return G;
}
void BFS(Graph* G) {
    memset(visit, 0, sizeof(visit[0]) * MAXVEX);
    queue<EdgeNode*> q;
    EdgeNode* temp;
    for (int index = 0; index < G->numVertexes; index++) {
        if (!visit[index]) {
            visit[index] = true;
            q.push(G->adj[index].firstedge);
            cout << " from vertex data: " <<
                G->adj[index].data << endl;
            while (!q.empty()) {
                temp = q.front();
                if (temp && !visit[temp->adjvex]) {
                    cout << "get weight: " << temp->weight <<
                        " to " << temp->adjvex << endl;
                    visit[temp->adjvex] = true;
                    q.push(temp->next);
                }
                q.pop();
            }
        }
    }
}
void DFS_VISIT(Graph* G, int index)
{
    visit[index] = true;
    cout << "vertex data: " << G->adj[index].data << endl;
    EdgeNode* temp = G->adj[index].firstedge;
    while (temp) {
        if (!visit[temp->adjvex])
            DFS_VISIT(G, temp->adjvex);
        temp = temp->next;
    }
}
void DFS(Graph* G)
{
    memset(visit, 0, sizeof(visit[0]) * MAXVEX);
    for (int index_i = 0; index_i < G->numVertexes; index_i++) {
        if (!visit[index_i])
            DFS_VISIT(G, index_i);
    }
}

2.邻接矩阵

由一个|V|x|V|的矩阵A = (aij)表示。aij = 1,若含有(i,j)边;aij = 0,其他。

存储空间:O(V^2)。

无向图:邻接矩阵为对称矩阵,可以减少图存储空间一半需求。

优点

1.表示稠密图。

2.可以快速判断两个顶点u和v之间是否是图中的一条边。

缺点

1.消耗非常大的存储空间。


#define MAXVEX 10              // maximum vertex numbers in the graph

typedef int VertexType;        // vertex data type
typedef int EdgeType;          // edge weight type

#define INFINITY 65535         // set as infinity value

// graph
typedef struct {
    VertexType vertex[MAXVEX];    // vertex list
    EdgeType arc[MAXVEX][MAXVEX]; // adjacent matrix
    int numVertexes;
    int numEdges;
}Graph;
Graph* CreateGraph()
{
    Graph* G = new Graph;
    cout << "input vertex num and edge num:" << endl;
    cin >> G->numVertexes >> G->numEdges;
    cout << "input vertex data one by one:" << endl;
    for (int index = 0; index < G->numVertexes; index++) {
        cin >> G->vertex[index];
    }
    // initial adjacent matrix
    for (int index_i = 0; index_i < G->numVertexes; index_i++)
        for (int index_j = 0; index_j < G->numVertexes; index_j++)
            G->arc[index_i][index_j] = INFINITY;
    cout << "input edge each by two vertex index and weight:" << endl;
    for (int index = 0; index < G->numEdges; index++) {
        int i, j, w;
        cin >> i >> j >> w;
        G->arc[i][j] = w;

#define NODRECTIONGRAPH

#ifdef NODRECTIONGRAPH

        G->arc[j][i] = w;

#endif

    }
    return G;
}
void BFS(Graph* G) {
    memset(visit, 0, sizeof(visit[0]) * MAXVEX);
    queue<int> q;
    for (int index = 0; index < G->numVertexes; index++) {
        if (!visit[index]) {
            visit[index] = true;
            q.push(index);
            while (!q.empty()) {
                int index_i = q.front();
                cout << " from vertex " << index_i << " data: " <<
                    G->vertex[index_i] << endl;
                for (int index_j = 0; index_j < G->numVertexes; index_j++) {
                    if (G->arc[index_i][index_j] != INFINITY &&
                            !visit[index_j]) {
                        visit[index_j] = true;
                        cout << "get weight: " << G->arc[index_i][index_j] <<
                            " to " << index_j << endl;
                        q.push(index_j);
                    }
                }
                q.pop();
            }
        }
    }
}
void DFS_VISIT(Graph* G, int index)
{
    visit[index] = true;
    cout << "vertex data: " << G->vertex[index] << endl;
    for (int index_j = 0; index_j < G->numVertexes; index_j++) {
        if (G->arc[index][index_j] != INFINITY && !visit[index_j])
            DFS_VISIT(G, index_j);
    }
}
void DFS(Graph* G)
{
    memset(visit, 0, sizeof(visit[0]) * MAXVEX);
    for (int index_i = 0; index_i < G->numVertexes; index_i++) {
        if (!visit[index_i])
            DFS_VISIT(G, index_i);
    }
}

图的搜索

1.广度优先搜索 BFS Breadth-First-Search

  • 算法始终将已发现顶点和未发现顶点之间的边界,沿其广度方向向外扩展。也就是说,算法需要在发现所有距离源顶点s为k的所有顶点后,才会发现距离源顶点s为k+1距离的其他顶点。
  • 对每个顶点,设定标志位记录是否被”发现“过。
  • 采用先进先出队列实现(可以联想二叉树的层次遍历,是图广度优先搜索的子集)。
  • 顶点u的属性u.d为最短距离,记录从源顶点s到顶点u之间的距离。
  • 广度优先树:BFS过程中生成的前驱子图(通过记录每个顶点的前驱顶点)。

    时间复杂度:O(V + E)。

广度优先搜索伪代码

BFS(G,s)
  for each vertex u in G.V - {s}
    u.color = WHITE
    u.d = INFINITY
    u.parent = NIL
  s.color = GRAY
  s.d = 0
  s.parent = NIL
  Q = EMPTY
  ENQUEUE(Q,s)
  while Q != EMPTY
    u = DEQUEUE(Q,s)
    for each vertex v in G.adj[u]
      if v.color == WHITE
        v.color = GRAY
        v.d = v.d + 1
        v.parent = u
        ENQUEUE(Q,s)
    u.color = BLACK

2.深度优先搜索 DFS Depth-First-Search

  • 算法总是对最近才发现的顶点的出发边进行探索,直到该顶点的所有出发边都被发现为止。算法重复整个过程,直到所有的顶点都被发现为止。
  • 对每个顶点,设定标志位记录是否被”发现“过。
  • 采用函数递归调用实现(可以联想二叉树的先序、中序、后序遍历,是图深度优先搜索的子集)。
  • 顶点u的属性u.d为时间戳,发现时间,记录从顶点u第一次被发现的时间。
  • 顶点u的属性u.f为时间戳,完成时间,记录完成对顶点u的邻接链表扫描的时间。
  • 深度优先森林:深度优先搜索的前驱子图形成一个由多棵深度优先树构成的深度优先森林(搜索可能从多个源结点重复进行)。
  • DFS对边进行分类
  • 树边(u,v):深度优先森林的边,顶点v因算法对边(u,v)的探索而首先被发现。(u.d < v.d < v.f < u.f)
  • 后向边(u,v):是将结点u连接到深度优先树中一个祖先结点v的边(有向图中的自循环)。(v.d < =u.d < u.f <= v.f)
  • 前向边(u,v):是将结点u连接到深度优先树中一个后代结点v的边。 (u.d < v.d < v.f < u.f)
  • 横向边(u,v):上述三种边以外的边。 (v.d < v.f < u.d < v.f)
  • 无向图:每条边要么是树边,要么是后向边。

时间复杂度:O(V + E)。

广度优先搜索伪代码

DFS(G)
  for each vertex u in G.V
    u.color = WHITE
    u.parent = NIL
  time = 0
  for each vertex u in G.V
     if (u.color == WHITE)
       DFS-VISIT(G,u)
DFS-VISIT(G,u)
  time = time + 1
  u.d = time
  u.color = GRAY
  for each vertex v in G.adj[u]
    if v.color == WHITE
      v.parent = u
      DFS-VISIT(G,v)
  u.color = BLACK
  time = time + 1
  u.f = time

应用:有向无环图的拓扑排序

  • 利用深度优先搜索算法。
  • 有向无环图的深度优先搜索不产生后向边。

应用:将有向图分解为强连通分量

  • 利用深度优先搜索算法。
时间: 2024-10-09 22:40:21

数据结构——图的相关文章

?数据结构-图之强连通

数据结构-图之强连通 在一个无向图G中,若从顶点v_i到顶点v_j有路径相连(当然从v_j到v_i也一定有路径),则称v_i和v_j是连通的.如果G是有向图,那么连接v_i和v_j的路径中所有的边都必须同向.如果图中任意两点都是连通的,那么图被称作连通图.图的连通性是图的基本性质. 连通分量:无向图G的一个极大连通子图称为G的一个连通分量(或连通分支).连通图只有一个连通分量,即其自身:非连通的无向图有多个连通分量. 初级通路:通路中所有的顶点互不相同.初级通路必为简单通路,但反之不真. 强连通

C#与数据结构--图的遍历

C#与数据结构--图的遍历 8.2 图的存储结构 图 的存储结构除了要存储图中各个顶点的本身的信息外,同时还要存储顶点与顶点之间的所有关系(边的信息),因此,图的结构比较复杂,很难以数据元素在存储区 中的物理位置来表示元素之间的关系,但也正是由于其任意的特性,故物理表示方法很多.常用的图的存储结构有邻接矩阵.邻接表.十字链表和邻接多重表. 8.2.1  邻接矩阵表示法 对于一个具有n个顶点的图,可以使用n*n的矩阵(二维数组)来表示它们间的邻接关系.图8.10和图8.11中,矩阵A(i,j)=1

数据结构--图 的JAVA实现(上)

1,摘要: 本系列文章主要学习如何使用JAVA语言以邻接表的方式实现了数据结构---图(Graph),这是第一篇文章,学习如何用JAVA来表示图的顶点.从数据的表示方法来说,有二种表示图的方式:一种是邻接矩阵,其实是一个二维数组:一种是邻接表,其实是一个顶点表,每个顶点又拥有一个边列表.下图是图的邻接表表示. 从图中可以看出,图的实现需要能够表示顶点表,能够表示边表.邻接表指是的哪部分呢?每个顶点都有一个邻接表,一个指定顶点的邻接表中,起始顶点表示边的起点,其他顶点表示边的终点.这样,就可以用邻

数据结构-图

1.图的定义 图:是一种灵活的数据结构,一般作为一种模型用来定义对象之间的关系或者联系.对象由顶点表示,而对象之间的关系或关联则通过顶点之间的边来表示. 2.图的应用 图算法.统计网络跳数.拓扑排序.图着色.哈密顿圈问题.分团问题.可序列化冲突 3.图的代码实现 /*graph.h*/ #ifndef GRAPH_H #define GRAPH_H #include <stdlib.h> #include "list.h" #include "set.h"

数据结构--图 的JAVA实现(下)

在上一篇文章中记录了如何实现图的邻接表.本文借助上一篇文章实现的邻接表来表示一个有向无环图. 1,概述 图的实现与邻接表的实现最大的不同就是,图的实现需要定义一个数据结构来存储所有的顶点以及能够对图进行什么操作,而邻接表的实现重点关注的图中顶点的实现,即怎么定义JAVA类来表示顶点,以及能够对顶点进行什么操作. 为了存储图中所有的顶点,定义了一个Map<key, value>,实际实现为LinkedHashMap<T, VertexInterface<T>>,key 为

数据结构--图--图的数组存储表示,深度优先搜索遍历和广度优先搜索遍历

图有四种存储结构:数组,邻接表,十字链表,邻接多重表.下面以数组为存储结构来实现图的深度优先搜索遍历和广度优先搜索遍历.其中广度优先搜索遍历中有用到STL中的queue,注意头文件的包含.具体代码如下: //图的数组(邻接矩阵)存储表示和深度优先遍历 const int MAX_VERTEX_NUM=20; //最大顶点数 typedef enum {DG,DN,UDG,UDN} GraphKind ;//(有向图,有向网,无向图,无向网) typedef int VRType; typedef

数据结构--图--最小生成树(Prim算法)

构造连通网的最小生成树,就是使生成树的边的权值之和最小化.常用的有Prim和Kruskal算法.先看Prim算法:假设N={V,{E}}是连通网,TE是N上最小生成树中边的集合.算法从U={u0}(uo属于V),TE={}开始,重复执行下述操作:在所有u属于U,v属于V-U的边(u,v)属于E中找到代价最小的一条边(u0,v0)并入集合TE,同时v0并入U,直至U=V为止.此时TE中必有n-1条边,T={V,{TE}}为N的最小生成树.为实现此算法,需另设一个辅助数组closedge,以记录从U

java 数据结构 图中使用的一些常用算法 图的存储结构 邻接矩阵:图的邻接矩阵存储方式是用两个数组来标示图。一个一位数组存储图顶点的信息,一个二维数组(称为邻接矩阵)存储图中边或者弧的信息。 设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为: 实例如下,左图是一个无向图。右图是邻接矩阵表示:

以下内容主要来自大话数据结构之中,部分内容参考互联网中其他前辈的博客. 图的定义 图是由顶点的有穷非空集合和顶点之间边的集合组成,通过表示为G(V,E),其中,G标示一个图,V是图G中顶点的集合,E是图G中边的集合. 无边图:若顶点Vi到Vj之间的边没有方向,则称这条边为无项边(Edge),用序偶对(Vi,Vj)标示. 对于下图无向图G1来说,G1=(V1, {E1}),其中顶点集合V1={A,B,C,D}:边集合E1={(A,B),(B,C),(C,D),(D,A),(A,C)}: 有向图:若

数据结构-图存储表示之邻接表

邻接表 在图论中,邻接表代表一个图中的所有边或弧. 邻接表存储表示,需要保存一个顺序存储的顶点表和每个顶点上的边的链接表. 邻接表(Adjacency List),即数组与链表相结合的存储方法. 如果是无向图,那么每条边由两个结点组成,分别代表边的两个端点: 如果是有向图,那么每条边是一个结点对,分别代表边的始点和终点. 一般来说,邻接表是无向的. 在计算机科学中,邻接表描述一种紧密相关的数据结构,用于表征图.在邻接表的表示中,对于图中的每个顶点,我们将保存所有其它与之相连的顶点(即"邻接表&q