2019独角兽企业重金招聘Python工程师标准>>>

一、基本术语

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

顶点(Vertex):图中的数据元素。线性表中我们把数据元素叫元素,树中将数据元素叫结点。

边:顶点之间的逻辑关系用边来表示,边集可以是空的。

无向边(Edge):若顶点V1到V2之间的边没有方向,则称这条边为无向边。

无向图(Undirected graphs):图中任意两个顶点之间的边都是无向边。(A,D)=(D,A)

对于无向图G来说,G1=(V1,{E1}),其中顶点集合V1={A,B,C,D};边集和E1={(A,B),(B,C),(C,D),(D,A),(A,C)}

有向边:若从顶点V1到V2的边有方向,则称这条边为有向边,也称弧(Arc)。用<V1,V2>表示,V1为狐尾(Tail),V2为弧头(Head)。(V1,V2)≠(V2,V1)。

有向图(Directed graphs):图中任意两个顶点之间的边都是有向边。

注意:无向边用“()”,而有向边用“< >”表示。

简单图:图中不存在顶点到其自身的边,且同一条边不重复出现。

无向完全图:无向图中,任意两个顶点之间都存在边。

有向完全图:有向图中,任意两个顶点之间都存在方向互为相反的两条弧。

稀疏图:有很少条边。

稠密图:有很多条边。

权(Weight):与图的边或弧相关的数。

网(Network):带权的图。

子图(Subgraph):假设G=(V,{E})和G‘=(V',{E'}),如果V'包含于V且E'包含于E,则称G'为G的子图。

度(Degree):无向图中,与顶点V相关联的边的数目。有向图中,入度表示指向自己的边的数目,出度表示指向其他边的数目,该顶点的度等于入度与出度的和。

路径的长度:一条路径上边或弧的数量。

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

图1不是连通图,图2是连通图。

连通分量:无向图中的极大连通子图。(子图必须是连通的且含有极大顶点数)。图1有两个连通分量

强连通分量:有向图中的极大强连通子图。

生成树:无向图中连通且n个顶点n-1条边叫生成树。

有向树:有向图中一顶点入度为0其余顶点入度为1。

森林:一个有向图由若干棵有向树构成生成森林。

二、图的存储结构

1.邻接矩阵:用两个数组,一个数组保存顶点集,一个数组保存边集。

图的邻接矩阵存储的结构:

#define maxvex 100
typedef struct
{
char vexs[maxvex];
int arc[maxvex][maxvex];
int vertex,edges;
}MGraph;
无向图的创建代码:

#define maxvexs 100
#define infinity 65535//用65535来表示∞
typedef struct
{
    char vexs[maxvexs];
    int arc[maxvexs][maxvexs];
    int vertexes,edges;
}mgraph;
 
void creatgraph(mgraph *g)
{
    int i,j,k,w;
    printf("输入顶点数和边数:\n");
    scanf("%d,%d",&g->vertexes,&g->edges);
    for(i=0;i<g->vertexes;i++)//读入顶点信息,建立顶点表
        scanf("%c",&g->vexs[i]);
    for(i=0;i<g->vertexes;i++)
        for(j=0;j<g->vertexes;j++)
            g->arc[i][j]=infinity;//初始化邻接矩阵
    for(k=0;k<g->vertexes;k++)//读入edges条边,建立邻接矩阵
    {
        printf("输入边(Vi,vj)上的下标i,下标j,和权w:\n");
        scanf("%d%d%d",&i,&j,&w);
        g->arc[i][j]=w;
        g->arc[j][i]=w;//无向图,矩阵对称
    }
}

2.邻接表:数组与链表相结合的存储方法。

对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。

邻接表结点定义

typedef struct EdgeNode
{
    int adjvex; //邻接点域,存储该顶点对应的下标
    int weight; //用于存储权值,对于非网图可以不需要
    struct EdgeNode *next;  //链域,指向下一个邻接点
}EdgeNode;//边表结点
 
typedef struct VertexNode //顶点表结点
{
    char data; //顶点域,存储顶点信息
    EdgeNode *firstedge; //边表头指针
}VertexNode,AdjList[MAXVEX];
 
typedef struct
{
    AdjList adjList;
    int numVertexes,numEdges;//图中当前顶点数和边数
}GraphAdjList;

三、图的遍历
1.深度优先遍历(DFS):从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。

2.广度优先遍历(BFS):类似于树的层次遍历。

四、最小生成树

最小生成树:构造连通网的最小代价生成树。

1.普里姆(prime):

第一种:先将一个起点加入最小生成树,之后不断寻找与最小生成树相连的边权最小的边能通向的点,并将其加入最小生成树,直至所有顶点都在最小生成树中。

第二种:

1.将一个图的顶点分为两部分,一部分是最小生成树中的结点(A集合),另一部分是未处理的结点(B集合)。

2.首先选择一个结点,将这个结点加入A中,然后,对集合A中的顶点遍历,找出A中顶点关联的边权值最小的那个(设为v),将此顶点从B中删除,加入集合A中。

3.递归重复步骤2,直到B集合中的结点为空,结束此过程。

4.A集合中的结点就是由Prime算法得到的最小生成树的结点,依照步骤2的结点连接这些顶点,得到的就是这个图的最小生成树。

2.克鲁斯卡尔(kluskal):在剩下的所有未选取的边中,找最小边,如果和已选取的边构成回路,则放弃,选取次小边。

五、最短路径

1.迪杰斯特拉算法(Dijkstra):把图中的顶点集合V分成两组,第一组为已求出最短路径的顶点集合S(初始时S中只有源节点,以后每求得一条最短路径,就将它对应的顶点加入到集合S中,直到全部顶点都加入到S中);第二组是未确定最短路径的顶点集合U。

算法步骤:
    (1)初始化时,S只含有源节点;
    (2)从U中选取一个距离v最小的顶点k加入S中(该选定的距离就是v到k的最短路径长度);
    (3)以k为新考虑的中间点,修改U中各顶点的距离;若从源节点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值是顶点k的距离加上k到u的距离;
    (4)重复步骤(2)和(3),直到终点在S中。

2.弗洛伊德算法(Floyd):

1,从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
2,对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。

---------------------

基本概念
图(Graph):图(Graph)是一种比线性表和树更为复杂的数据结构。 
图结构:是研究数据元素之间的多对多的关系。在这种结构中,任意两个元素之间可能存在关系。即结点之间的关系可以是任意的,图中任意元素之间都可能相关。 
图G由两个集合V(顶点Vertex)和E(边Edge)组成,定义为G=(V,E) 
线性结构:是研究数据元素之间的一对一关系。在这种结构中,除第一个和最后一个元素外,任何一个元素都有唯一的一个直接前驱和直接后继。 
树结构:是研究数据元素之间的一对多的关系。在这种结构中,每个元素对下(层)可以有0个或多个元素相联系,对上(层)只有唯一的一个元素相关,数据元素之间有明显的层次关系。

图相关的概念和术语
一个图(G)定义为一个偶对(V,E) ,记为G=(V,E) 。其中: V是顶点(Vertex)的非空有限集合,记为V(G);E是无序集V&V的一个子集,记为E(G) ,其元素是图的弧(Arc)。 
将顶点集合为空的图称为空图。其形式化定义为:

G=(V ,E)
V={v|vdata object}
E={<v,w>| v,wV∧p(v,w)}
1
2
3
1. 无向图和有向图
对于一个图,若每条边都是没有方向的,则称该图为无向图。图示如下: 
 
因此,(Vi,Vj)和(Vj,Vi)表示的是同一条边。注意,无向图是用小括号,而下面介绍的有向图是用尖括号。 
无向图的顶点集和边集分别表示为: 
V(G)={V1,V2,V3,V4,V5} 
E(G)={(V1,V2),(V1,V4),(V2,V3),(V2,V5),(V3,V4),(V3,V5),(V4,V5)} 
对于一个图G,若每条边都是有方向的,则称该图为有向图。图示如下:

有向图的顶点集和边集分别表示为: 
V(G)={V1,V2,V3} 
E(G)={

2,无向完全图和有向完全图
我们将具有n(n-1)/2条边的无向图称为无向完全图。同理,将具有n(n-1)条边的有向图称为有向完全图。

完全无向图
对于无向图,若图中顶点数为n ,用e表示边的数目,则e [0,n(n-1)/2] 。具有n(n-1)/2条边的无向图称为完全无向图。 
完全无向图另外的定义是: 
对于无向图G=(V,E),若vi,vj V ,当vi≠vj时,有(vi ,vj)E,即图中任意两个不同的顶点间都有一条无向边,这样的无向图称为完全无向图。 
#### 完全有向图 
对于有向图,若图中顶点数为n ,用e表示弧的数目,则e[0,n(n-1)] 。具有n(n-1)条边的有向图称为完全有向图。 
完全有向图另外的定义是: 
对于有向图G=(V,E),若vi,vjV ,当vi ≠vj时,图中任意两个不同的顶点间都有一条弧,这样的有向图称为完全有向图。

子图和生成子图
设有图G=(V,E)和G’=(V’,E’),若V’V且E’E ,则称图G’是G的子图;若V’=V且E’E,则称图G’是G的一个生成子图。

连通图(无向图)
连通图是指图G中任意两个顶点Vi和Vj都连通,则称为连通图。比如图(b)就是连通图。下面是一个非连通图的例子。

图的创建和遍历
图的遍历方法
1) 深度优先搜索遍历 
深度优先搜索DFS遍历类似于树的前序遍历。其基本思路是: 
a) 假设初始状态是图中所有顶点都未曾访问过,则可从图G中任意一顶点v为初始出发点,首先访问出发点v,并将其标记为已访问过。 
b) 然后依次从v出发搜索v的每个邻接点w,若w未曾访问过,则以w作为新的出发点出发,继续进行深度优先遍历,直到图中所有和v有路径相通的顶点都被访问到。 
c) 若此时图中仍有顶点未被访问,则另选一个未曾访问的顶点作为起点,重复上述步骤,直到图中所有顶点都被访问到为止。 
图示如下:

注:红色数字代表遍历的先后顺序,所以图(e)无向图的深度优先遍历的顶点访问序列为:V0,V1,V2,V5,V4,V6,V3,V7,V8 
如果采用邻接矩阵存储,则时间复杂度为O(n2);当采用邻接表时时间复杂度为O(n+e)。 
2) 广度优先搜索遍历 
广度优先搜索遍历BFS类似于树的按层次遍历。其基本思路是: 
a) 首先访问出发点Vi 
b) 接着依次访问Vi的所有未被访问过的邻接点Vi1,Vi2,Vi3,…,Vit并均标记为已访问过。 
c) 然后再按照Vi1,Vi2,… ,Vit的次序,访问每一个顶点的所有未曾访问过的顶点并均标记为已访问过,依此类推,直到图中所有和初始出发点Vi有路径相通的顶点都被访问过为止。 
图示如下: 
 
因此,图(f)采用广义优先搜索遍历以V0为出发点的顶点序列为:V0,V1,V3,V4,V2,V6,V8,V5,V7 
如果采用邻接矩阵存储,则时间复杂度为O(n2),若采用邻接表,则时间复杂度为O(n+e)。

图的其他算法实现
图的创建
AdjGraph  *Create_Graph(MGraph * G)
{   printf(“请输入图的种类标志:”) ;
scanf(“%d”, &G->kind) ;
G->vexnum=0 ;       /*  初始化顶点个数  */
return(G) ; 
}
1
2
3
4
5
6
7
图的顶点定位
图的顶点定位操作实际上是确定一个顶点在vexs数组中的位置(下标) ,其过程完全等同于在顺序存储的线性表中查找一个数据元素。

int  LocateVex(MGraph *G , VexType *vp) 
   {  int  k ;
       for (k=0 ; k<G->vexnum ; k++)
            if (G->vexs[k]==*vp)  return(k) ;
       return(-1) ;     /*  图中无此顶点  */
   }
1
2
3
4
5
6
7
向图中增加顶点
向图中增加一个顶点的操作,类似在顺序存储的线性表的末尾增加一个数据元素。

int  AddVertex(MGraph *G , VexType *vp) 
{  int  k , j ;
if  (G->vexnum>=MAX_VEX)
{  printf(“Vertex Overflow !\n”) ; return(-1) ;  }
if  (LocateVex(G , vp)!=-1)
{  printf(“Vertex has existed !\n”) ; return(-1) ; }
k=G->vexnum ; G->vexs[G->vexnum++]=*vp ;
if (G->kind==DG||G->kind==AG) 
for (j=0 ; j<G->vexnum ; j++)
G->adj[j][k].ArcVal=G->adj[k][j].ArcVal=0 ;
      /*  是不带权的有向图或无向图  */
else
for (j=0 ; j<G->vexnum ; j++)  
{   G->adj[j][k].ArcVal=INFINITY ;
    G->adj[k][j].ArcVal=INFINITY ;
         /*  是带权的有向图或无向图  */
}
return(k) ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
向图中增加一条弧
根据给定的弧或边所依附的顶点,修改邻接矩阵中所对应的数组元素。

int  AddArc(MGraph *G , ArcType *arc) 
{   int  k , j ;
k=LocateVex(G , &arc->vex1)  ;
j=LocateVex(G , &arc->vex1)  ;
if (k==-1||j==-1)     
{  printf(“Arc’s Vertex do not existed !\n”) ;
return(-1) ;
if (G->kind==DG||G->kind==WDG) 
{  G->adj[k][j].ArcVal=arc->ArcVal;
G->adj[k][j].ArcInfo=arc->ArcInfo ;
        /*  是有向图或带权的有向图*/
}
else
{  G->adj[k][j].ArcVal=arc->ArcVal ;
G->adj[j][k].ArcVal=arc->ArcVal ;
G->adj[k][j].ArcInfo=arc->ArcInfo ;
G->adj[j][k].ArcInfo=arc->ArcInfo ;
      /*  是无向图或带权的无向图,需对称赋值  */
}
return(1) ;  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
最小生成树
一个连通图的生成树是一个极小的连通子图,它含有图中全部顶点,但只有足以构成一棵树的n-1条边。那么我们把构造连通网的最小代价生成树称为最小生成树。 
找连通网的最小生成树,经典的有两种算法,普里姆算法和克鲁斯卡尔算法。

邻接矩阵存储
@Test  
    public void Dijkstra(){  
        int distance[] = new int[9];  
        int pre[] = new int[9];  
        boolean finished[] = new boolean[9];  
        finished[0] = true;  
        for(int i=0;i<9;i++){  
            distance[i] = g1.adjMatrix[0][i];  
        }  
        int k = 0;  
        for(int i=1;i<9;i++){  
            int min = 65536;  
            for(int j=0;j<9;j++){  
                if(!finished[j]&&distance[j]<min){  
                    min = distance[j];  
                    k = j;  
                }  
            }  
            finished[k] = true;  
            System.out.println(pre[k]+","+k);  
            for(int j=1;j<9;j++){  
                if(!finished[j]&&(min+g1.adjMatrix[k][j])<distance[j]){  
                    distance[j] = min+g1.adjMatrix[k][j];  
                    pre[j] = k;  
                }  
            }  
        }  
    }  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
拓扑排序
(1)栈:用来存放入度为0的节点; 
(2)变种邻接列表:作为图的存储结构;此邻接列表的顶点节点还需要存放入度属性;

private static String topologicalSort(Graph2 g2) {  
        Stack<Integer> s = new Stack<Integer>();  
        int count = 0;  
        for(int i=0;i<g2.nodes.length;i++){  
            if(g2.nodes[i].indegree==0){  
                s.push(i);  
            }  
        }  
        while(!s.isEmpty()){  
            int value = s.pop();  
            System.out.println(value+"、");  
            count++;  
            EdgeNode node = g2.nodes[value].next;  
            while(node!=null){  
                g2.nodes[node.idx].indegree--;  
                if(g2.nodes[node.idx].indegree==0){  
                    s.push(node.idx);  
                }  
                node = node.next;  
            }

}  
        if(count<g2.nodes.length){  
            return "error";  
        }  
        return "ok";  
    }  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
图的边表存储结构
在某些应用中,有时主要考察图中各个边的权值以及所依附的两个顶点,即图的结构主要由边来表示,称为边表存储结构。 
在边表结构中,边采用顺序存储,每个边元素由三部分组成:边所依附的两个顶点和边的权值;图的顶点用另一个顺序结构的顶点表存储。如图:

---------------------

1、名词解释:
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。在图中的数据元素,我们称之为顶点(Vertex),顶点集合有穷非空。在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

图按照边的有无方向分为无向图和有向图。无向图由顶点和边组成,有向图由顶点和弧构成。弧有弧尾和弧头之分,带箭头一端为弧头。

图按照边或弧的多少分稀疏图和稠密图。如果图中的任意两个顶点之间都存在边叫做完全图,有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图。

图中顶点之间有邻接点、依附的概念。无向图顶点的边数叫做度。有向图顶点分为入度和出度。

图上的边或弧带有权则称为网。

图中顶点间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环,当中不重复的叫简单路径。若任意两顶点都是连通的,则图就是连通图,有向则称为强连通图。图中有子图,若子图极大连通则就是连通分量,有向的则称为强连通分量。

无向图中连通且n个顶点n-1条边称为生成树。有向图中一顶点入度为0其余顶点入度为1的叫有向树。一个有向图由若干棵有向树构成生成森林。

2、图的存储结构—-邻接矩阵
图的邻接矩阵的表示方式需要两个数组来表示图的信息,一个一维数组表示每个数据元素的信息,一个二维数组(邻接矩阵)表示图中的边或者弧的信息。

如果图有n个顶点,那么邻接矩阵就是一个n*n的方阵,方阵中每个元素的值的计算公式如下: 

邻接矩阵表示图的具体示例如下图所示:

首先给个无向图的实例:

下面是一个有向图的实例: 

OK,到这里为止,我们给出一个无向图的邻接矩阵和一个有向图的邻接矩阵,我们可以从这两个邻接矩阵得出一些结论:

无向图的邻接矩阵都是沿对角线对称的
要知道无向图中某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;
对于有向图,要知道某个顶点的出度,其实就是这个顶点vi在邻接矩阵中第i行的元素之和,如果要知道某个顶点的入度,那就是第i列的元素之和。
但是,如果我们需要表示的图是一个网的时候,例如假设有个图有n个顶点,同样该网的邻接矩阵也是一个n*n的方阵,只是方阵元素的值的计算方式不同,如下图所示: 

这里的wij表示两个顶点vi和vj边上的权值。无穷大表示一个计算机允许的、大于所有边上权值的值,也就是一个不可能的极限值。下面是具体示例,表示的一个有向网的图和邻接矩阵:

3、图的存储结构—-邻接矩阵的代码实现
#include<iostream>
using namespace std;

enum Graphkind{ DG, DN, UDG, UDN }; //{有向图,无向图,有向网,无向网}
typedef struct  Node
{
    int * vex;  //顶点数组
    int vexnum; //顶点个数
    int edge;   //图的边数
    int ** adjMatrix; //图的邻接矩阵
    enum Graphkind kind;
}MGraph;
void createGraph(MGraph & G,enum Graphkind kind)
{
    cout << "输入顶点的个数" << endl;
    cin >> G.vexnum;
    cout << "输入边的个数" << endl;
    cin >> G.edge;
    //输入种类

    //cout << "输入图的种类:DG:有向图 DN:无向图,UDG:有向网,UDN:无向网" << endl;
    G.kind = kind;
    //为两个数组开辟空间
    G.vex = new int[G.vexnum];
    G.adjMatrix = new int*[G.vexnum];
    cout << G.vexnum << endl;
    int i;
    for (i = 0; i < G.vexnum; i++)
    {
        G.adjMatrix[i] = new int[G.vexnum];
    }
    for (i = 0; i < G.vexnum; i++)
    {
        for (int k = 0; k < G.vexnum; k++)
        {
            if (G.kind == DG || G.kind == DN)
            {
                G.adjMatrix[i][k] = 0;
            }
            else {
                G.adjMatrix[i][k] = INT_MAX;
            }
        }

    }

    /*//输入每个元素的信息,这个信息,现在还不需要使用
    for (i = 0; i < G.vexnum; i++)
    {
        cin >> G.vex[i];
    }*/
    cout << "请输入两个有关系的顶点的序号:例如:1 2 代表1号顶点指向2号顶点" << endl;
    for (i = 0; i < G.edge; i++)
    {
        int a, b;
        cin >> a;
        cin >> b;
        if (G.kind == DN) {
            G.adjMatrix[b - 1][a - 1] = 1;
            G.adjMatrix[a - 1][b - 1] = 1;
        }
        else if (G.kind == DG)
        {
            G.adjMatrix[a - 1][b - 1] = 1;
        }
        else if (G.kind == UDG)
        {
            int weight;
            cout << "输入该边的权重:" << endl;
            cin >> weight;
            G.adjMatrix[a - 1][b - 1] = weight;
        }
        else {
            int weight;
            cout << "输入该边的权重:" << endl;
            cin >> weight;
            G.adjMatrix[b - 1][a - 1] = weight;
            G.adjMatrix[a - 1][b - 1] = weight;
        }   
    }
}

void print(MGraph g)
{
    int i, j;
    for (i = 0; i < g.vexnum; i++)
    {
        for (j = 0; j < g.vexnum; j++)
        {
            if (g.adjMatrix[i][j] == INT_MAX)
                cout << "∞" << " ";
            else
            cout << g.adjMatrix[i][j] << " ";
        }
        cout << endl;
    }
}

void clear(MGraph G)
{
    delete G.vex;
    G.vex = NULL;
    for (int i = 0; i < G.vexnum; i++)
    {
        delete G.adjMatrix[i];
        G.adjMatrix[i] = NULL;
    }
    delete G.adjMatrix;
}
int main()
{

        MGraph G;
        cout << "有向图例子:" << endl;
        createGraph(G, DG);
        print(G);
        clear(G);
        cout << endl;
        cout << "无向图例子:" << endl;
        createGraph(G, DN);
        print(G);
        clear(G);

        cout << endl;
        cout << "有向图网例子:" << endl;
        createGraph(G, UDG);
        print(G);
        clear(G);

        cout << endl;
        cout << "无向图网例子:" << endl;
        createGraph(G, UDN);
        print(G);
        clear(G);

        cout << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
输出结果: 

4、图的存储结构—-邻接矩阵的优缺点
优点: 
直观、容易理解,可以很容易的判断出任意两个顶点是否有边,最大的优点就是很容易计算出各个顶点的度。
缺点: 
当我么表示完全图的时候,邻接矩阵是最好的表示方法,但是对于稀疏矩阵,由于它边少,但是顶点多,这样就会造成空间的浪费。
5、 图的存储结构—邻接表
邻接表是图的一种链式存储结构。主要是应对于邻接矩阵在顶点多边少的时候,浪费空间的问题。它的方法就是声明两个结构。如下图所示:

OK,我们虽然知道了邻接表是这两个结构来表示图的,那么它的怎么表示的了,不急,我们先把它转为c++代码先,然后,再给个示例,你就明白了。

typedef char Vertextype;
//表结点结构
struct ArcNode {
    int adjvex;   //某条边指向的那个顶点的位置(一般是数组的下标)。
    ArcNode * nextarc; //指向下一个表结点
    int weight;   //这个只有网图才需要使用。普通的图可以直接忽略
};

//头结点
struct Vnode
{
    Vertextype data;  //这个是记录每个顶点的信息(现在一般都不需要怎么使用)
    ArcNode * firstarc; //指向第一条依附在该顶点边的信息(表结点)
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
无向图的示例:

从图中我们可以知道,顶点是通过一个头结点类型的一维数组来保存的,其中我们每个头结点的firstarc都是指向第一条依附在该顶点边的信息,表结点的adjvex表示的该边的另外一个顶点在顶点数组中的下标,weight域对于普通图是无意义的,可以忽略,nextarc指向的是下一条依附在该顶点的边的信息。 
下面再给出一个有向图的例子: 

通过上述的两个例子,我们应该明白邻接表是如何进行表示图的信息的了。

6、图的存储结构—-邻接表的代码实现
#include<iostream>
#include<string>
using namespace std;

typedef string Vertextype;
//表结点结构
struct ArcNode {
    int adjvex;   //某条边指向的那个顶点的位置(一般是数组的下标)。
    ArcNode * nextarc; //指向下一个表结点
    int weight;   //这个只有网图才需要使用。
};

//头结点
struct Vnode
{
    Vertextype data;  //这个是记录每个顶点的信息(现在一般都不需要怎么使用)
    ArcNode * firstarc; //指向第一条依附在该顶点边的信息(表结点)
};

//
struct Graph
{
    int kind;  //图的种类(有向图:0,无向图:1,有向网:2,无向网:3)
    int vexnum; //图的顶点数
    int edge;  //图的边数
    Vnode * node; //图的(顶点)头结点数组
};

void createGraph(Graph & g,int kind)
{
    cout << "请输入顶点的个数:" << endl;
    cin >> g.vexnum;
    cout << "请输入边的个数(无向图/网要乘2):" << endl;
    cin >> g.edge;
    g.kind = kind; //决定图的种类
    g.node = new Vnode[g.vexnum];
    int i;
    cout << "输入每个顶点的信息:" << endl;//记录每个顶点的信息
    for (i = 0; i < g.vexnum; i++)
    {
        cin >> g.node[i].data;
        g.node[i].firstarc=NULL;

    }

    cout << "请输入每条边的起点和终点的编号:" << endl;
    for (i = 0; i < g.edge; i++)
    {
        int a;
        int b;
        cin >> a; //起点
        cin >> b; //终点
        ArcNode * next=new ArcNode;
        next->adjvex = b - 1;
        if(kind==0 || kind==1)
        next->weight = -1;
        else {//如果是网图,还需要权重
            cout << "输入该边的权重:" << endl;
            cin >> next->weight;
        }
        next->nextarc = NULL;

        //将边串联起来
        if (g.node[a - 1].firstarc == NULL) {
            g.node[a - 1].firstarc=next;

        }
        else 
        {
            ArcNode * p;
            p = g.node[a - 1].firstarc;
            while (p->nextarc)//找到该链表的最后一个表结点
            {
                p = p->nextarc;
            }
            p->nextarc = next;
        }
    }
}

void print(Graph  g)
{
    int i;
    cout << "图的邻接表为:" << endl;
    for (i = 0; i < g.vexnum; i++)
    {
        cout << g.node[i].data<<" ";
        ArcNode * now;
        now = g.node[i].firstarc;
        while (now)
        {
            cout << now->adjvex << " ";
            now = now->nextarc;
        }
        cout << endl;
    }
}

int main()
{
    Graph g;
    cout << "有向图的例子" << endl;
    createGraph(g,0);
    print(g);
    cout << endl;

    cout << "无向图的例子" << endl;
    createGraph(g, 1);
    print(g);
    cout << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
输出结果: 
  

7、图的存储结构—-邻接表的优缺点
优点: 
对于,稀疏图,邻接表比邻接矩阵更节约空间。
缺点: 
不容易判断两个顶点是有关系(边),顶点的出度容易,但是求入度需要遍历整个邻接表。
8、有向图的存储结构—-十字链表
十字链表是有向图的一个专有的链表结构,我们之前也说了,邻接表对于我们计算顶点的入度是一个很麻烦的事情,而十字链表正好可以解决这个问题。十字链表和邻接表一样,他会有两个结构来表示图:其中一个结构用于保存顶点信息,另外一个结构是用于保存每条边的信息,如下图所示: 

同样,我们知道头结点就是用于保存每个顶点信息的结构,其中data主要是保存顶点的信息(如顶点的名称),firstin是保存第一个入度的边的信息,firstout保存第一个出度的边的信息。其中,表结点就是记录每条边的信息,其中tailvex是记录这条边弧头的顶点的在顶点表中的下标(不是箭头那个),headvex则是记录弧尾对应的那个顶点在顶点表中的下标(箭头的那个),hlink是指向具有下一个具有相同的headvex的表结点,tlink指向具有相同的tailvex的表结点,weight是表示边的权重(网图才需要使用)。具体的代码表示如下:

typedef string Vertextype;
//表结点结构
struct ArcNode {
    int tailvex;   //弧尾的下标,一般都是和对应的头结点下标相同
    int headvex;   //弧头的下标
    ArcNode * hlink; //指向下一个弧头同为headvex的表结点 ,边是箭头的那边
    ArcNode * tlink;  //指向下一个弧尾为tailvex的表结点,边不是箭头的那边
    int weight;  //只有网才会用这个变量

};

//头结点
struct Vnode
{
    Vertextype data;  //这个是记录每个顶点的信息(现在一般都不需要怎么使用)
    ArcNode *firstin; //指向第一条(入度)在该顶点的表结点
    ArcNode *firstout; //指向第一条(出度)在该顶点的表结点
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
下面,我们给出一个有向图的十字链表的例子: 

其实,这个自己也可以去尝试手画一个十字链表出来,其实都是很简单的

9、有向图的存储结构—-十字链表代码实现
#include<iostream>
#include<string>
using namespace std;

typedef string Vertextype;
//表结点结构
struct ArcNode {
    int tailvex;   //弧尾的下标,一般都是和对应的头结点下标相同
    int headvex;   //弧头的下标
    ArcNode * hlink; //指向下一个弧头同为headvex的表结点 ,边是箭头的那边
    ArcNode * tlink;  //指向下一个弧尾为tailvex的表结点,边不是箭头的那边
    int weight;  //只有网才会用这个变量

};

//头结点
struct Vnode
{
    Vertextype data;  //这个是记录每个顶点的信息(现在一般都不需要怎么使用)
    ArcNode *firstin; //指向第一条(入度)在该顶点的表结点
    ArcNode *firstout; //指向第一条(出度)在该顶点的表结点
};

struct Graph
{
    int kind;  //图的种类(有向图:0,有向网:1)
    int vexnum; //图的顶点数
    int edge;  //图的边数
    Vnode * node; //图的(顶点)头结点数组
};

void createGraph(Graph & g,int kind)
{
    cout << "请输入顶点的个数:" << endl;
    cin >> g.vexnum;
    cout << "请输入边的个数(无向图/网要乘2):" << endl;
    cin >> g.edge;
    g.kind = kind; //决定图的种类
    g.node = new Vnode[g.vexnum];
    int i;
    cout << "输入每个顶点的信息:" << endl;//记录每个顶点的信息
    for (i = 0; i < g.vexnum; i++)
    {
        cin >> g.node[i].data;
        g.node[i].firstin = NULL;
        g.node[i].firstout = NULL;
    }

    cout << "请输入每条边的起点和终点的编号:" << endl;
    for (i = 0; i < g.edge; i++)
    {
        int a, b;
        cin >> a;
        cin >> b;

        ArcNode * next = new ArcNode;
        next->tailvex = a - 1; //首先是弧头的下标
        next-> headvex = b - 1; //弧尾的下标
        //只有网图需要权重信息
        if(kind==0)
        next->weight = -1;
        else
        {
            cout << "输入该边的权重:" << endl;
            cin >> next->weight;
        }
        next->tlink = NULL;
        next->hlink = NULL;
        //该位置的顶点的出度还为空时,直接让你fisrstout指针指向新的表结点
        //记录的出度信息
        if (g.node[a - 1].firstout == NULL)
        {
            g.node[a - 1].firstout = next;
        }
        else
        {
            ArcNode * now;
            now = g.node[a - 1].firstout;
            while (now->tlink)
            {
                now = now->tlink;
            }
            now->tlink = next;
        }
        //记录某个顶点的入度信息
        if (g.node[b - 1].firstin == NULL)
        {
            g.node[b - 1].firstin = next;
        }
        else {
            ArcNode * now;
            now = g.node[b - 1].firstin;
            while (now->hlink)//找到最后一个表结点
            {
                now = now->hlink;
            }
            now->hlink = next;//更新最后一个表结点
        }
    }
}

void print(Graph g)
{
    int i;
    cout << "各个顶点的出度信息" << endl;
    for (i = 0; i < g.vexnum; i++)
    {
        cout << g.node[i].data << " ";
        ArcNode * now;
        now = g.node[i].firstout;
        while (now)
        {
            cout << now->headvex << " ";
            now = now->tlink;
        }
        cout << "^" << endl;
    }

    cout << "各个顶点的入度信息" << endl;

    for (i = 0; i < g.vexnum; i++)
    {
        cout << g.node[i].data << " ";
        ArcNode * now;
        now = g.node[i].firstin;
        while (now)
        {
            cout << now->tailvex << " ";
            now = now->hlink;
        }
        cout << "^" << endl;

    }
}

int main()
{
    Graph g;
    cout << "有向图的例子" << endl;
    createGraph(g, 0);
    print(g);
    cout << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

10、无向图的存储结构—-邻接多重表
邻接多重表是无向图的另一种链式存储结构。我们之前也说了使用邻接矩阵来存储图比价浪费空间,但是如果我们使用邻接表来存储图时,对于无向图又有一些不便的地方,例如我们需要对一条已经访问过的边进行删除或者标记等操作时,我们除了需要找到表示同一条边的两个结点。这会给我们的程序执行效率大打折扣,所以这个时候,邻接多重表就派上用场啦。

首先,邻接多重表同样是对邻接表的一个改进得到来的结构,它同样需要一个头结点保存每个顶点的信息和一个表结点,保存每条边的信息,他们的结构如下: 

其中,头结点的结构和邻接表一样,而表结点中就改变比较大了,其中mark为标志域,例如标志是否已经访问过,ivex和jvex代表边的两个顶点在顶点表中的下标,ilink指向下一个依附在顶点ivex的边,jlink指向下一个依附在顶点jvex的边,weight在网图的时候使用,代表该边的权重。

下面是一个无向图的邻接多重表的实例(自己也可以尝试去画画,具体的原理都是很简单的): 

11、无向图的存储结构—-邻接多重表代码实现
#include<iostream>
#include<string>
using namespace std;

//表结点
struct ArcNode
{
    int mark; //标志位
    int ivex; //输入边信息的那个起点
    ArcNode * ilink; //依附在顶点ivex的下一条边的信息
    int jvex;   //输入边信息的那个终点
    ArcNode * jlink; //依附在顶点jvex的下一条边的信息
    int weight;
};

//头结点
struct VexNode {
    string data;   //顶点的信息,如顶点名称
    ArcNode * firstedge; //第一条依附顶点的边
};

struct Graph {
    int vexnum;   //顶点的个数
    int edge;    //边的个数
    VexNode *node; //保存顶点信息
};

void createGraph(Graph & g)
{
    cout << "请输入顶点的个数:" << endl;
    cin >> g.vexnum;
    cout << "请输入边的个数(无向图/网要乘2):" << endl;
    cin >> g.edge;
    g.node = new VexNode[g.vexnum];

    int i;
    cout << "输入每个顶点的信息:" << endl;//记录每个顶点的信息
    for (i = 0; i < g.vexnum; i++)
    {
        cin >> g.node[i].data;
        g.node[i].firstedge = NULL;
    }

    cout << "请输入每条边的起点和终点的编号:" << endl;
    for (i = 0; i < g.edge; i++)
    {
        int a, b;
        cin >> a;
        cin >> b;

        ArcNode * next = new ArcNode;
        next->mark = 0;
        next->ivex = a - 1; //首先是弧头的下标
        next->jvex = b - 1; //弧尾的下标
        next->weight = -1;
        next->ilink = NULL;
        next->jlink = NULL;

        //更新顶点表a-1的信息
        if (g.node[a - 1].firstedge == NULL)
        {
            g.node[a - 1].firstedge = next;
        }
        else {
            ArcNode * now;
            now = g.node[a - 1].firstedge;
            while (1) {
                if (now->ivex == (a - 1) && now->ilink == NULL)
                {
                    now->ilink = next;
                    break;
                }
                else if (now->ivex == (a - 1) && now->ilink != NULL) {
                    now = now->ilink;
                }
                else if (now->jvex == (a - 1) && now->jlink == NULL)
                {
                    now->jlink = next;
                    break;
                }
                else if (now->jvex == (a - 1) && now->jlink != NULL) {
                    now = now->jlink;
                }
            }
        }
        //更新顶点表b-1
        if (g.node[b - 1].firstedge == NULL)
        {
            g.node[b - 1].firstedge = next;
        }
        else {
            ArcNode * now;
            now = g.node[b - 1].firstedge;
            while (1) {
                if (now->ivex == (b - 1) && now->ilink == NULL)
                {
                    now->ilink = next;
                    break;
                }
                else if (now->ivex == (b - 1) && now->ilink != NULL) {
                    now = now->ilink;
                }
                else if (now->jvex == (b - 1) && now->jlink == NULL)
                {
                    now->jlink = next;
                    break;
                }
                else if (now->jvex == (b - 1) && now->jlink != NULL) {
                    now = now->jlink;
                }
            }
        }

    }
}

void print(Graph g)
{
    int i;
    for (i = 0; i < g.vexnum; i++)
    {
        cout << g.node[i].data << " ";
        ArcNode * now;
        now = g.node[i].firstedge;
        while (now)
        {
            cout << "ivex=" << now->ivex << " jvex=" << now->jvex << " ";
            if (now->ivex == i)
            {
                now = now->ilink;
            }
            else if (now->jvex == i)
            {
                now = now->jlink;
            }
        }
        cout << endl;
    }
}
int main()
{
    Graph g;
    createGraph(g);
    print(g);
    system("pause");
    return 0;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
输出结果: 

--------------------- 

转载于:https://my.oschina.net/hblt147/blog/2987962

数据结构-图-知识点总结相关推荐

  1. 最值得收藏的 数据结构 全部知识点思维导图整理(王道考研), 附带经典题型整理

    本文的思维导图根据王道的数据结构书本整理而来并标记出重点内容,包括了知识点和部分课后习题 思维导图源文件已经发布在我的资源当中, 点击获取全部导图和配套OneNote笔记, 有需要的可以去 我的主页 ...

  2. a - 数据结构实验之图论一:基于邻接矩阵的广度优先搜索遍历_数据结构--图

    故事凌 今天 基本知识点 图可说是所有数据结构里面知识点最丰富的一个, 自己笨的知识点如下: 阶(oRDER), 度: 出度(out-Degree), 入度(in-Degree) 树(Tree), 森 ...

  3. 数据结构--图(Graph)详解(四)

    数据结构–图(Graph)详解(四) 文章目录 数据结构--图(Graph)详解(四) 一.图中几个NB的算法 1.普里姆算法(Prim算法)求最小生成树 2.克鲁斯卡尔算法(Kruskal算法)求最 ...

  4. 数据结构--图(Graph)详解(三)

    数据结构–图(Graph)详解(三) 文章目录 数据结构--图(Graph)详解(三) 一.深度优先生成树和广度优先生成树 1.铺垫 2.非连通图的生成森林 3.深度优先生成森林 4.广度优先生成森林 ...

  5. 数据结构--图(Graph)详解(二)

    数据结构–图(Graph)详解(二) 文章目录 数据结构--图(Graph)详解(二) 一.图的存储结构 1.图的顺序存储法 2.图的邻接表存储法 3.图的十字链表存储法 4.图的邻接多重表存储法 二 ...

  6. 数据结构--图(Graph)详解(一)

    数据结构–图(Graph)详解(一) 文章目录 数据结构--图(Graph)详解(一) 一.图的基本概念 1.图的分类 2.弧头和弧尾 3.入度和出度 4.(V1,V2) 和 < V1,V2 & ...

  7. 四色着色问题 c语言编程,数据结构-图着色问题

    7-38 图着色问题 (25 分) 图着色问题是一个著名的NP完全问题.给定无向图G=(V,E),问可否用K种颜色为V中的每一个顶点分配一种颜色,使得不会有两个相邻顶点具有同一种颜色? 但本题并不是要 ...

  8. Dubbo思维导图知识点整理

    Dubbo思维导图知识点整理 下载地址:https://download.csdn.net/download/liuhenghui5201/12846897

  9. 导数与微分的知识点思维导图_高中生物思维导图知识点总结

    今天小编给大家整理一份高中生物思维导图知识点总结 ,打印出来给孩子学习吧,需要的请点下面赞同,并评论:我要高中生物思维导图知识点总结, 点头像私信获取,希望给你的孩子有所帮助. 刚开始学生物的时候,第 ...

最新文章

  1. 禁止ScrollView在子控件的布局改变时自动滚动的的方法
  2. matlab振动频谱分析是不是要,VB和Matlab混编实现振动信号的频谱分析
  3. LeetCode 456 132 Pattern
  4. 蓝桥杯2016初赛-生日蜡烛-枚举
  5. 在Spring Rest模板中跳过SSL证书验证
  6. 《Java并发编程的艺术》之阻塞队列
  7. 【Python CheckiO 题解】Three Words
  8. 15名优秀女科学家拟获重量级荣誉
  9. Cocos2d-x 3.2:定时器的使用和原理探究(2)
  10. 函数指针与回调函数、句柄
  11. yii2不用composer使用redis
  12. onvif协议client与server对接
  13. ecshop在nginx下实现负载均衡
  14. [已解决]罗技鼠标驱动打不开问题
  15. 鼎立td测试软件窗口参数介绍,鼎立网优参数指标解释
  16. ROS重大功能,无线WISP和桥接
  17. 音乐学院计算机音乐实验中心,中央音乐学院鼎石实验学校
  18. java web 使用 Freemarker 导出word,zip包导出多个word
  19. 阿里云ECS服务器配置全攻略
  20. 面试题16:不含重复字符的最长子字符串(Java版)

热门文章

  1. (C语言实现)页面置换——先进先出算法(FIFO)
  2. 【GaussDB精品课第3期】GaussDB(for openGauss)配套工具介绍
  3. 微信小程序学习day02-WXSS 模板样式
  4. 数据中台应用原型下载-深圳市政府数据开放平台低保真原型设计-Axure9
  5. 汪子嵩:论有、存在与是(如是——真如)
  6. Keil仿真基于虚拟串口VSPD的串口调试(Virtual Serial Port Driver)
  7. 使用广播信道的数据链路层相关知识点
  8. 人工智能(mysql)—— mysql事务、存储引擎及调优
  9. 「高效程序员的修炼」代码版本管理工具 Git 用起来 01 Git 基础
  10. 骗子预警,大家注意,飞鸟资源网 www.fn121.c*o*m