数据结构--Chapter6(图)

本文介绍了图的基本概念,包括无向图、有向图、完全图、稠密图和稀疏图等。还讨论了图的邻接矩阵和邻接表这两种存储结构,邻接矩阵适合判断两点间是否有边,但空间效率低,而邻接表空间利用率高,适用于边数量少的情况。

6 图

6.1 图概述

6.1.1  图的基本概念

    图是由顶点(Vertex)集V和边(Edge)集E组成,记为G=(V,E)。V是有穷非空集合,称为顶点集,v∈V称为顶点。E是有穷集合,称为边集,eE称为边。e=(u,v)或e=<u,v>;u,vV,其中,(u,v)表示顶点u与顶点v的一条无向边,简称为边,即(u,v)没有方向,这时(u,v)(v,u)是等同的;而<u,v>表示从顶点u到顶点v的一条有向边,简称为弧,u为始点或弧尾,v为终点或弧头。需要说明的是,E可以是空集,此时图G只有顶点没有边,称为零图。

1. 无向图

    全部由无向边构成的图称为无向图(Undirected Graph

2. 有向图

    全部由有向边构成的图称为有向图(Directed Graph)。

3.  权和网

    在 一个图中,每条边可以标上具有某种含义的数值,此数值成为该边上的权(Weight),通常一个权是一个非负数。权可以表示从一个顶点到另一个顶点的距离,时间或代价等含义。边上标识权的图成为网。

4. 完全图

    在具有n个顶点的无向图G中,当边数达到最大值n(n-1)/2时,称图G为无向完全图(Undirected Complete Graph)。

    在具有n个顶点的有向图G中,当边数达到最大值n(n-1)时,称图G为有向完全图(Directed Complete Graph)。

5. 稠密图和稀疏图

    在具有n个顶点、e条边的图G中,若含有较少的边(例如e<nlog2(n)),则称图G为稀疏图(Sparse Graph),反之则称为稠密图(Dense Graph)。

6. 子图

    设有两个图G=(V,E)和G‘=(V’,E‘),若V’是V的子集,并且E'shi E的子集,则称G’为G的子图(Subgraph),记为G‘G。若G’为G的子图,并且V‘=V,则称G’为G的生成子图(Spanning Subgraph)。

7. 邻接点

    在一个无向图中,若存在一条边(u,v),则称顶点u与v互为邻接点。边(u,v)是顶点u和v关联的边,顶点  u和v是边(u,v)关联的顶点。

    在一个有向图中,若存在一条弧<u,v>,则称顶点u邻接到v,顶点v邻接自u。弧(u,v)和顶点u、v关联。

8. 顶点的弧

    顶点的度(Degree)是图中与该顶点相关联的数目。顶点v的度记为D(v)。在有向图中,顶点v的度有入度和出度之分,以v为终点的弧的数目称为入度(In Degree);以v为起点的弧的数目称为出度(Out Degree),记为OD(v),顶点的度等于它的入度和出度之和,即D(v)=ID(v)+OD(v)。

    若一个图有n个顶点和e条边,则改图所有顶点的度之和与边数e满足如下关系:

                                                          e=0.5*[D(V0)+D(V1)+D(V2)+...+D(Vn-1)]

9.  路径与回路

    在一个图中,路径(Path)是从顶点u到顶点v所经过的顶点序列,路径长度是指该路径上边的数目。第一个顶点和最后一个顶点相同的路径称为回路或环。序列中顶点不重复出现的路径称为初等路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路称为初等回路。

10.  连通图和连通分量

    在无向图中,若从顶点u到顶点v有路径,则称u和v是连通的。若图中的任意两个顶点均是连通的,则称该图是连通图,否则称为非连通图。无向图中的极大连通子图称为连通分量。

11. 强连通图和强连通分量

    在有向图中,若任意两个顶点均连通,则称该图是强连通图。有向图中的极大强连通子图称为强连通分量,强连通图只有一个强连通分量,即其本身;非强连通图有多个强连通分量。

12. 生成树和生成森林

    生成树是一种特殊的生成子图,它包含图中的全部顶点,但只有构成一棵树的n-1条边。

    对于非连通图,每个连通分量可行成一棵生成树,这些生成树组成了该非连通图的生成森林。

6.1.2 图的抽象数据类型

    图由顶点集和边集组成,因此对图的操作也集中在对顶点和边的操作上,主要有以下的一些操作。1)createGraph():创建一个图

2)getVexNum():返回图中的定点数

3)getArcNum():返回图中的边数

4)getVex(v):给定顶点的位置v,返回其对应的顶点值,其中,0<=v<vexNum(vexNum为顶点数)。

5)locateVex(vex):给定顶点的值vex,返回其在图中的位置,如果图中不包含此项点,则返回-1。

6)firstAdjVex(v):返回v的第一个邻接点,若v没有邻接点,则返回-1,其中,0<=v<vexNum(vexNum为顶点数)。

7)nextAdjVex(v,w):返回v相对于w的下一个邻接点,若w是v的最后一个邻接点,则返回-1,其中,0<=v,w<vexNum。

6.2  图的存储结构

    图的类型主要有4中:无向图、有向图、无向网和有向网。可以用枚举表示如下:

public enum GraphKind {
	UDG, // 无向图(UnDirected Graph)
	DG, // 有向图(Directed Graph)
	UDN, // 无向网(UnDirected Network)
	DN;// 有向网(Directed Network)
}


    图的存储结构除了存储图中各个顶点的信息外,还要存储与顶点相关联的边的信息,图的常见存储结构有邻接矩阵、邻接表、临界多重表、十字链表等,每种存储结构都能表示上面所讲的4种类型的图,这儿主要介绍邻接矩阵和邻接表。

6.2.1 邻接矩阵

    图的邻接矩阵(Adjacency Matrix)是用来表示顶点之间相邻关系的矩阵。

    图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。

    设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:


【例】下图中无向图G 5 和有向图G 6 的邻接矩阵分别为A l 和A 2 。


    无向图的邻接矩阵是对称阵,因此一般可以采用压缩存储;有向图的邻接矩阵一般不对称。用邻接矩阵存储图,所需要的存储空间只与顶点数有关。

    对于网G,则邻接矩阵可定义为:


其中:W ij 表示边上的权值;

∞表示一个计算机允许的、大于所有边上权值的数。

【例】下面带权图的两种邻接矩阵分别为A 3 和A 4 。


    用邻接矩阵表示图,很容易判断任意两个顶点之间是否有边,同样很容易求出各个顶点的度。对于无向图,邻接矩阵的第i行或第i列的非零元素的个数正好是第i个顶点Vi的度;对于有向图,邻接矩阵的第i行的非零元素的个数正好是第i个顶点Vi的出度,第i列的非零元素的个数正好是第i个顶点Vi的入度。

2. 图的邻接矩阵类的描述

    对于一个具有n个顶点的图G,可以将图G的邻接矩阵存储在一个二维数组中,图的邻接矩阵类MGraph的描述如下:

package ljjz;

import chapter6.GraphKind;

public class MGraph {
	public final static int INFINITY = Integer.MAX_VALUE;
	private GraphKind kind;// 图的种类标志
	private int vexNum, arcNum;// 图的当前顶点数和边数
	private Object[] vexs;// 顶点
	private int[][] arcs;// 邻接矩阵

	public MGraph() {
		this(null, 0, 0, null, null);
	}

	public MGraph(GraphKind kind, int vexNum, int arcNum, Object[] vexs,
			int[][] arcs) {
		this.kind = kind;
		this.vexNum = vexNum;
		this.arcNum = arcNum;
		this.vexs = vexs;
		this.arcs = arcs;
	}

	// 创建图
	public void createGraph() {

	}

	// 创建无向图
	private void createUDG() {
		
	}

	// 创建有向图
	private void createDG() {

	}

	// 创建无向网
	private void createUDN() {

	}

	// 创建有向网
	private void createDN() {

	}

	// 返回顶点数
	public int getVexNum() {
		return vexNum;
	}

	// 返回边数
	public int getArcNum() {
		return arcNum;
	}

	// 给定顶点的值vex,返回其在图中的位置,如果图中不包含此顶点,则返回-1
	public int locateVex(Object vex) {
		return 0;
	}

	// 返回v表示结点的值,0<=v<vexNum
	public Object getVex(int v) throws Exception {
		if (v < 0 || v >= vexNum) {
			throw new Exception("第" + v + "个顶点不存在!");
		}
		return vexs[v];
	}

	// 返回v的第一个邻接点,若v没有邻接点则返回-1,0<=v<vexNum
	public int firstAdjVex(int v) throws Exception {
		return 0;
	}

	// 返回v相对于w的下一个邻接点,若w是v的最后一个邻接点,则返回-1,其中,0<=v,w<vexNum
	public int nextAdjVex(int v, int w) {
		return 0;
	}

	public GraphKind getKind() {
		return kind;
	}

	public int[][] getArcs() {
		return arcs;
	}

	public Object[] getVexs() {
		return vexs;
	}

}

3. 图的邻接矩阵类基本操作的实现

1)图的创建

	public void createGraph() {
		Scanner sc = new Scanner(System.in);
		GraphKind kind = GraphKind.valueOf(sc.next());
		switch (kind) {
		case UDG:
			createUDG();// 构造无向图
			return;
		case DG:
			createDG();// 构造有向图
			return;
		case UDN:
			createUDN();// 构造无向网
			return;
		case DN:
			createDN();// 构造有向网
			return;
		}
	}


	// 创建无向网
	private void createUDN() {
		Scanner sc = new Scanner(System.in);
		System.out.println("请分别输入图的顶点数、图的边数");
		vexNum = sc.nextInt();
		arcNum = sc.nextInt();
		vexs = new Object[vexNum];
		System.out.println("请分别输入图的各个顶点");
		for (int v = 0; v < vexNum; v++) {
			vexs[v] = sc.next();
		}
		arcs = new int[vexNum][vexNum];
		for (int v = 0; v < vexNum; v++) {
			for (int u = 0; u < vexNum; u++) {
				arcs[v][u] = INFINITY;
			}
		}
		System.out.println("请输入各个变的两个顶点及其权值");
		for (int k = 0; k < arcNum; k++) {
			int v = locateVex(sc.next());
			int u = locateVex(sc.next());
			arcs[v][u] = arcs[u][v] = sc.nextInt();
		}

	}

	// 创建有向网
	private void createDN() {
		Scanner sc = new Scanner(System.in);
		System.out.println("请分别输入图的顶点数、图的边数");
		vexNum = sc.nextInt();
		arcNum = sc.nextInt();
		vexs = new Object[vexNum];
		System.out.println("请分别输入图的各个顶点");
		for (int v = 0; v < vexNum; v++) {
			vexs[v] = sc.next();
		}
		arcs = new int[vexNum][vexNum];
		for (int v = 0; v < vexNum; v++) {
			for (int u = 0; u < vexNum; u++) {
				arcs[v][u] = INFINITY;
			}
		}
		System.out.println("请输入各个变的两个顶点及其权值");
		for (int k = 0; k < arcNum; k++) {
			int v = locateVex(sc.next());
			int u = locateVex(sc.next());
			arcs[v][u] = sc.nextInt();
		}
	}


2)顶点定位

     顶点定位的结拜呢要求:根据顶点信息vex,取得其在顶点数组中的位置,若图中无此顶点,则返回-1.

	public int locateVex(Object vex) {
		for(int v = 0;v<vexNum;v++){
			if(vexs[v].equals(vex)){
				return v;
			}
		}
		return -1;
	}


3)查找第一个邻接点

    查找第一个邻接点的基本要求是:已知图中的一个顶点v,返回v的第一个邻接点,若v没有邻接点,则返回-1,其中,0<=v<vexNum。

	public int firstAdjVex(int v) throws Exception {
		if (v < 0 || v >= vexNum) {
			throw new Exception("第" + v + "个顶点不存在!");
		}
		for (int j = 0; j < vexNum; j++) {
			if (arcs[v][j] != 0 && arcs[v][j] < INFINITY) {
				return j;
			}
		}
		return -1;
	}

4)查找下一个邻接点

    查找下一个邻接点的基本要求是:已知图中的一个顶点v以及v的一个邻接点w,返回v相对于w的下一个邻接点,若w是v最后一个邻接点,则返回-1,其中0<=v,w<vexNum。

	public int nextAdjVex(int v, int w) throws Exception {
		if (v < 0 || v >= vexNum) {
			throw new Exception("第" + v + "个顶点不存在!");
		}
		for (int j = w + 1; j < vexNum; j++) {
			if (arcs[v][j] != 0 && arcs[v][j] < INFINITY) {
				return j;
			}
		}
		return -1;
	}

    用邻接矩阵存储图,虽然能很好地确定图中的任意两个顶点之间是否右边,但是不论是求任意顶点的度,还是查找任一顶点的邻接点,都需要访问对应的一行或一列中的所有的数据元素,其时间复杂度为O(n)。而确定图中有多少条边,则必须按行对每个数据元素进行检测,花费的时间代价较大,其时间复杂度为O(n^2)。从空间上看,无论图中的顶点之间是否有边,都要在邻接矩阵中保存空间,其空闲复杂度为O(n^2),空间效率较低,这也是邻接矩阵的局限性。

6.2.2 邻接表

1. 图的邻接表存储结构

    邻接表(Adjacency List)是图的一种链式存储方法,邻接表表示类似于树的孩子链表表示。邻接表是由一个顺序存储的顶点表和n个链式存储的边表组成的。其中,顶点表由顶点结点组成;边表是由边(或弧)及结点组成的一个单链表,表示所有依附于顶点Vi的边(对于有向图就是所有以Vi为始点的弧)。

    顶点结点包括data和firstArc两个域。data表示顶点信息;firstArc表示指向边表中的第一个边(或弧)结点。顶点节点类VNode的描述如下:

    边界点包括adjVex、nextArc和value共3个域,其中,adjVex表示与顶点Vi邻接的顶点在图中的位置;nextArc指向下一个边结点;value存储与边相关的信息,例如权值等。



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值