数据结构之-图的知识及计算机存储方法

一、图的定义

树的表示是分层的,例如父子关系,而其他关系只能间接的表示,例如同级关系。而图却不受这种限制。图是由顶点(或结点)及顶点之间的关系组成的集合。通常,图中的顶点数量或者一个顶点与其他顶点之间的连线的个数不受限制。

图是由顶点集合(Vertex)及顶点间的关系集合组成的一种数据结构:Graph=( V, E )

V = {x | x ∈某个数据对象 } 是顶点的有穷非空集合;

E ={ (x, y) | x, y ∈V } 是顶点之间关系的有穷集合,也叫做边(Edge)集合。

下图的都是图的表示
在这里插入图片描述
可能有人会提出异议,G2和G3怎么会是图,明明就是二叉树和线性表嘛。不错,你们说的是对的,但是根据图的定义我们不难发现,二叉树和线性表也符合图的特征。大家来看,二叉树的所有结点是不是相当于顶点的集合,结点间都存在一定的关系;线性表也是一样,也就是说二叉树和线性表是特殊的图。只不过图比线性表和树更加复杂罢了。

在线性表中,数据元素之间仅有线性关系,每个数据元素只有一个直接前驱和一个直接后继;在树形结构中,数据元素之间有着明显的层次关系,并且每一层上的数据元素可能和下一层中多个元素相关,但只能和上一层中的一个元素相关;而在图形结构中,结点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关。

图是一种扩展的树结构,每个结点可以指向任意的其它结点。链表是特殊的树结构,树是特殊的图结构。图这种数据结构常用于网络规划和路径路径规划等领域。GPS相关产品中大量应用了图结构和图算法。

二、相关术语

  1. 无向边:若顶点 x 和 y 之间的边没有方向,则称该边为无向边(x, y),(x, y) 与
    (y,x) 意义相同,表示 x 和 y 之间有连接。

  2. 无向图:若图中任意两个顶点之间的边均是无向边,则称该图为无向图(如上图G1,G2)。

  3. 有向边:若顶点 x 和 y 之间的边有方向,则称该边为有向边<x, y>,<x, y> 与
    <y, x> 意义不同,表示从 x 连接到 y,x 称为尾,y 称为头。

  4. 有向图:若图中任意两个顶点之间的边均是有向边,则称该图为有向图(如上图G3,G4)。

  5. 在图中的两个重要关系是邻接和关联。

  6. 邻接:是两个顶点之间的一种关系。如果图包含(u,v),则称顶点v与顶点u邻接。在无向图中,这也暗示了顶点u也与顶点v邻接。换句话说,在无向图中邻接关系是对称的。

  7. 关联:是指顶点和边之间的关系。在有向图中,边(u,v)从顶点u开始关联到v,或者相反,从顶点v开始关联到u。在无向图中,边(u,v)与顶点u和v相关联。

  8. 完全图:每个顶点都与其他顶点相邻接的图。

  9. 度(Degree)的定义:顶点 v 的度是和 v 相关联的边的数目,记为TD(v)。

  10. 入度:以 v 为头的边的数目,记为ID(v)

  11. 出度:以 v 为尾的边的数目,记为OD(v)

    很显然:TD(v) = ID(v) + OD(v)

    E = [TD(v1) + TD(v2) + … + TD(vn)] / 2

    E = ID(v1) + ID(v2) + … + ID(vn)

    E = OD(v1) + OD(v2) + … + OD(vn)

  12. 权(Weight)的定义:与图的边相关的数字叫做权,权常用来表示图中顶点间的距离或者耗费。带权的图通常称为网。
    在这里插入图片描述

  13. 路径:依次遍历顶点序列之间的边所形成的轨迹。没有重复顶点的路径称为简单路径。路径的长度是路径上的边或弧的数目。

  14. 环是指路径包含相同的顶点两次或两次以上。也就是说,在有向图的一条路径中,如果从某顶点出发,最后能够返回该顶点,则该路径是环。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路称为简单环或简单回路。

  15. 连通性是图中另一个重要的概念。对于无向图而言,如果它的每个顶点都能通过某条路径到达其他顶点,那么我们称它为联通的。如果该条件在有向图中同样成立,则称该图是强连通。尽管无向图可能不是连通的,但它扔然可能包含连通的部分,这部分分支为连通分支。如果有向图中只有部分是强连通的,则该部分称为强连通分支。

  16. 某些特定的顶点对于保护图或连通分支的连通性有特殊的重要意义。如果移除某个顶点将使得图或某分支失去连通性,则称该顶点为关结点。

三、图的一些常用操作:

创建图,销毁图,清空图,加入边,删除边,获取权,获取结点的度,获取图的结点数,获取图的边数
在这里插入图片描述

四、图的二种常用数据存储结构

  1. 邻接矩阵表数据存储结构

图的邻接矩阵存储方式是用两个数组表示。

一个一维数组储存图的顶点(V)信息,一个二维数组储存图的边或是弧(E)的信息

假如图G有n个顶点,则邻接矩阵是一个nXn的方阵,定义为:
在这里插入图片描述
如下无向图:
在这里插入图片描述
如下有向图:
在这里插入图片描述
java数据存储结构定义方式:

Class MGraph{

// 顶点个数

int numNodes;

//一维数组储存图的顶点(V)信息

String[] vexs = new String[numNodes];

//邻接矩阵,二维数组储存图的边或是弧(E)的信息

int[][] edgs = new int[numNodes][numNodes];

}

这种定义方式是边很多的情况下使用,如果边很少,用这种方式就很浪费空间,就应该采用邻接连接表数据存储结构

  1. 邻接连接表数据存储结构

对于边界较少的图来说,这种存储方式会对空间造成极大的浪费,尤其对于稀疏有向图来说。所以可以考虑使用链表的方式来按需分配。

数组链表的相结合的存储方式被称为邻接表

1)对于所有顶点的值使用数组进行存储

2)对于每个顶点的边使用链表进行存储

java实现邻接表的数据结构如下:

1)//创建一个顶点类型,用来表示顶点信息

class Vexs{

String data;//顶点的值

Chain chain;//本顶点的链表

}

2)//创建一个链表类型

class Chain{

int id;//相邻接顶点的数组下标

Chain next;//下一个相邻接顶点的数组下标

}

3)//定义一个图节点数组

class ALGraph{

int numNodes;//顶点个数

Vexs vexs[] = new Vexs[numNodes];//顶点数组

六、图的遍历算法:深度遍历算法和广度遍历算法

  1. 基于邻接矩阵表的图遍历算法

  2. 深度优先遍历算法

上面邻接矩阵表的数据存储结构已经把图抽象成一个类,因此我们可以将图的遍历定义成类的方法。对于连通图,调用遍历算法后即可访问所有结点,但对于非连通图,调用遍历算法后仍有一些结点没有被访问,需要从图中另选一个未被访问的顶点再次调用遍历算法。因此需要附设一个访问标志数组visited[n],来记录被访问的结点。增加了访问标志的数据存储结构如下:

(1)定义图的数据结构

class AMGraph{

// 顶点个数

int numNodes;

//一维数组储存图的顶点(V)信息

String[] vexs = new String[numNodes];

//邻接矩阵,二维数组储存图的边或是弧(E)的信息

int[][] edgs = new int[numNodes][numNodes];

//false表示该位置的顶点未访问,true表示已访问

boolean[] visited = null;

}

(2)图的初始化:。。。

(3)图的深度遍历

for(int i = 0; i < this.visited.length; i++) {

//对未访问的顶点调用深度优先遍历算法

if(!this.visited[i]) {

dFS_AM(i); //深度优先遍历算法

}

}

//深度优先遍历算法

public void dFS_AM(int site) {//输入深度优先遍历的开始顶点

System.out.println(this.vexs[site]); //输出该顶点

this.visited[site] = true; //置访问标志为true

for(int i = 0; i < this.vexs.length; i++) {

//依次查找未访问邻接点,并以该邻接点为始点调用深度优先遍历算法

if(this.arcs[site][i] != 0 && !this.visited[i]) {

this.dFS_AM(i);

}

}

}

  1. 广度优先遍历算法

public void bFS_AM(int site) { //输入开始顶点

System.out.println(this.vexs[site]); //输出该顶点

this.visited[site] = true; //置访问标志为true

LinkedList<Integer> linkedList = new LinkedList<Integer>();
//借助队列来实现广度优先遍历

linkedList.offer(site); //将访问过的顶点入队

while(!linkedList.isEmpty()) {

int vexSite = linkedList.poll();//队头顶点出队

for(int i = 0; i < this.vexs.length; i++) {

if(this.arcs[vexSite][i] != 0 && !this.visited[i]) {
//依次查找未访问的邻接点进行访问后入队

System.out.println(this.vexs[i]);

this.visited[i] = true;

linkedList.offer(i);

}

}

}

}
.vexs.length; i++) {

if(this.arcs[vexSite][i] != 0 && !this.visited[i]) {
//依次查找未访问的邻接点进行访问后入队

System.out.println(this.vexs[i]);

this.visited[i] = true;

linkedList.offer(i);

}

}

}

}