图数据结构(期限、存储结构、遍历)

1、相关条款

顶点(Vertex)、弧形(Arc)、圆弧头(初始点)、圆弧终点(端点)、边缘(Edge)、向图(Directed graph)、无向图(Undigraph)、完全图(Completed grapg)、导演完全图、稀疏图(Sparse graph)、密集图(Dense graph)、权(weigh)、网(network)、无向网、有向网、子图(Subgraph)、邻接点(Adjacent)、度(Degree)、入度(Indegree)、出度(Outdegree)、路径(path)、回路(环)、简单路径、简单回路(简单环)、连通、

fr=aladdin">连通图(Connected
graph)、连通分量(Connected Component)、强连通图强连通分量(有向图中的极大强连通子图)、生成树、极小连通子图、有向树。

无向图:G=(V, {E})、0≤边≤n(n-1)/2

有向图:G=(V, {A})、0≤弧≤n(n-1)

连通图:在无向图G中,假设图中随意两个顶点vi, vj属于V,vi和vj都是连通的,则图G是连通图。

连通分量:无向图中的极大连通子图

图例:

强连通图:在有向图G中,假设每一对顶点vi, vj属于V且vi不等于vj,从vi到vj与从vj到vi都存在路径,则图G是连通图。

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

生成树:一个连通图的生成树是一个极小连通子图,它含有图中所有顶点,但仅仅有足以构成一棵树的n-1条边。

假设在生成树上加入一条边,必然构成一个环:由于这条边使得它依附的那两个顶点之间有了第二条路径。

一个有n个顶点的生成树有且仅有n-1条边。

假设一个图有n和顶点和小于n-1条的边。则是非连通图。假设多余n-1条边,则一定有环。

但有n-1条边的图不一定是生成树。

2、存储结构

2.1邻接矩阵(数组表示法)

(无向图、有向图、无向网、有向网)

用两个数组来表示图。一个一维数组存储图中数据元素(顶点)的信息。一个二维数组(邻接矩阵)存储图中数据元素之间的关系(边或弧)的信息。

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

下图就是一个无向图:

从上面能够看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。

即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相相应的元全都是相等的。

从这个矩阵中,非常easy知道图中的信息。

(1)要推断随意两顶点是否有边无边就非常easy了。

(2)要计算某个顶点的度,事实上就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和。

(3)求顶点vi的全部邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]=1的vj就是邻接点;

而有向图有入度和出度之分:顶点vi的入度为是第i列各数之和,顶点vi的出度是第i行的各数之和。

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

wij表示(vi,vj)或<vi,vj>上的权值。

无穷大表示一个计算机同意的、大于全部边上权值的值,也就是一个不可能的极限值。

下图是一个有向网图和它的邻接矩阵:

注:这个边数组(邻接矩阵)的对角线应该是无穷大。

能够看出:

(1)第i行权重大于0、小于无穷的个数之和为顶点vi的出度(OD(vi));

(2)第j列权重大于0、小于无穷的个数之和为顶点vj的入度(ID(vj))。

(3)第i行和第i列权重大于0、小于无穷的个数之和即为顶点vi的度(TD(vi) = OD(vi) + ID(vj))。

使用邻接矩阵存储图并创建图的代码示比例如以下:

typedef char VertexType; //顶点类型,由用户自定义
typedef int  EdgeType;   //边上的权值类型,由用户自定义

#define MAX_VEX 20  //最大顶点数,由用户定义
#define	INFINITY 65535 //代表无穷大

typedef struct
{
	VertexType vexs[MAX_VEX]; //顶点数组
	EdgeType   arc[MAX_VEX][MAX_VEX]; //邻接矩阵
	int vexNum, arcNum; //图中当前定点数和弧数
}Graph;
//定位顶点v在顶点数组的下标位置,不存在返回-1
int LocateVex(Graph *g, VertexType v)
{
	int i = 0;
	for (i = 0; i < g->vexNum; i++)
	{
		if (g->vexs[i] == v)
			break;
	}
	if (i > g->vexNum)
	{
		fprintf(stderr,"no such vertex.\n");
		return -1;
	}
	return i;
}

//用邻接矩阵表示法,构造有/无向网g
void CreateUDN(Graph *g)
{
	int i, j;
	EdgeType w;

	printf("输入顶点数和边数:\n");
	scanf("%d,%d", &(g->vexNum), &(g->arcNum));
	//输入顶点
	for(i = 0; i < g->vexNum; i++)
	{
		printf("顶点%d:", i + 1);
		g->vexs[i] = getchar();
		while(g->vexs[i] == '\n')
		{
			g->vexs[i] = getchar();
		}
	}
	getchar();
	//初始化邻接矩阵
	for (i = 0; i < g->arcNum; i++)
	{
		for (j = 0; j < g->arcNum; j++)
		{
			g->arc[i][j] = INFINITY;
		}
	}

	printf("输入边(vi, vj)上的顶点vi vj和权值w\n");
	for (i = 0; i < g->arcNum; i++)
	{
		char v1, v2;
		//v1 = getchar();
		//while (v1 == '\n')
		//{
		//	v1 = getchar();
		//}
		//v2 = getchar();
		//while (v2 == '\n')
		//{
		//	v2 = getchar();
		//}
		//scanf("%d", &w);
		scanf("%c %c %d", &v1, &v2, &w);
		getchar();
		int m = LocateVex(g, v1);
		int n = LocateVex(g, v2);
		if (m == -1 || n == -1)
		{
			fprintf(stderr, "no this vertex!\n");
			return;
		}
		g->arc[m][n] = w;
#if 0
		g->arc[n][m] = g->arc[m][n];	//无向图的邻接矩阵对称
#endif
	}
}

void PrintGrapth(Graph *g)
{
	for (int i = 0; i < g->vexNum; i++)
	{
		for (int j = 0; j < g->vexNum; j++)
		{
			printf("%6d", g->arc[i][j]);
		}
		putchar('\n');
	}
}
int main()
{
	Graph g;
	CreateUDN(&g);
	PrintGrapth(&g);
	return 0;
}

创建上文中的有向网图,程序执行演示样例截图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMzA3MTA3NA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

创建无向网图。执行截图为:

n个顶点和e条边的无向网图的创建。时间复杂度为O(n + n^2 + e·n) = O(n^2 + e·n),当中第一个n是输入n个顶点。对邻接矩阵的初始化耗费了O(n^2)的时间;对每一条边。定位两个顶点的下标花费了O(n),因此是O(e·n)。

2.2邻接表

对于边数相对顶点较少的图,邻接矩阵这样的结构会对存储空间造成极大浪费。邻接表(Adjacency List)是图的一种链式存储结构。是数组与链表相结合来存储图。

邻接表的处理方法是这种:

(1)图中顶点用一个一维数组存储。当然顶点也能够用单链表来存储。只是,数组能够较easy的读取顶点的信息。更加方便。

(2)图中每一个顶点vi的全部邻接点构成一个线性表。因为邻接点的个数不定。所以,用单链表存储,无向图称为顶点vi的边表。有向图则称为顶点vi作为弧尾的出边表。

比如。下图就是一个无向图的邻接表的结构。

从图中能够看出。顶点表的各个结点由data和firstedge两个域表示:data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点。即此顶点的第一个邻接点。

边表结点由adjvex和next两个域组成:adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标。next则存储指向边表中下一个结点的指针。

对于带权值的网图。能够在边表结点定义中再添加一个weight的数据域。存储权值信息就可以。例如以下图所看到的。

使用邻接表储图并创建网图的代码示比例如以下:

typedef char VertexType;	//顶点类型,由用户自定义
typedef int  EdgeType;	//边上的权值类型,由用户自定义

#define MAX_VEX 20	//最大顶点数,由用户定义

typedef struct EdgeNode	//边表结点
{
	int adjvex;	//邻接点域。存储该顶点在顶点表中的下标
	EdgeType weight;	//网图权值
	struct EdgeNode *next;	//链域。指向下一个邻接点
}EdgeNode;

typedef struct VertexNode	//顶点表结点
{
	VertexType data;	//顶点域,存储顶点信息
	EdgeNode *firstedge;	//边表头指针
}VertexNode, AdjList[MAX_VEX];

typedef struct
{
	AdjList adjList;
	int vexNum, arcNum; //图中当前顶点数和弧数
}GraphList;
//定位顶点v在顶表的下标位置,不存在返回-1
int LocateVex(GraphList *g, VertexType v)
{
	int i;
	for (i = 0; i < g->vexNum; i++)
	{
		if (v == g->adjList[i].data )
			break;
	}

	if (i > g->vexNum)
	{
		fprintf(stderr,"no this vertex.\n");
		return -1;
	}
	return i;
}

//用邻接表表示法,构造有/无向网g
void CreateGraph(GraphList *g)
{
	int i, j;
	EdgeType w;
	EdgeNode *e, *f;

	printf("输入顶点数和边数:\n");
	scanf("%d,%d", &(g->vexNum), &(g->arcNum));

	//输入顶点
	for (i = 0; i < g->vexNum; i++)
	{
		printf("顶点%d:", i);
		g->adjList[i].data = getchar();
		g->adjList[i].firstedge = NULL;
		while(g->adjList[i].data == '\n')
		{
			g->adjList[i].data = getchar();
		}
	}
	getchar();

	//建立边表
	printf("输入边(vi, vj)上的顶点vi vj和权值w\n");
	for (i = 0; i < g->arcNum; i++)
	{
		char v1, v2;
		scanf("%c %c %d", &v1, &v2, &w);
		getchar();
		int m = LocateVex(g, v1);
		int n = LocateVex(g, v2);
		if (m == -1 || n == -1)
		{
			fprintf(stderr, "no this vertex!\n");
			return;
		}

		//向内存申请空间,生成边表结点
		e = (EdgeNode*)malloc(sizeof(EdgeNode));
		if(e == NULL)
		{
			fprintf(stderr, "malloc() error.\n");
			return;
		}
		e->adjvex = n;	//邻接序号为n
		e->weight = w;	//权值
		e->next = g->adjList[m].firstedge; //单链表的头插法
		g->adjList[m].firstedge = e;

#if 0
		//由于是无向网图
		f = (EdgeNode*)malloc(sizeof(EdgeNode));
		if(e == NULL)
		{
			fprintf(stderr, "malloc() error.\n");
			return;
		}
		f->adjvex = m;
		f->weight = w;
		f->next = g->adjList[n].firstedge;
		g->adjList[n].firstedge = f;
#endif
	}
}

void PrintGrapth(GraphList *g)
{
	int i;
	for (i = 0; i < g->vexNum; i++)
	{
		printf("顶点%c ", g->adjList[i].data) ;
		EdgeNode *e = g->adjList[i].firstedge;
		while (e != NULL)
		{
			printf("—> <%c, %c> %d  ", g->adjList[i].data, g->adjList[e->adjvex].data, e->weight);
			e = e->next;
		}
		putchar('\n');
	}
}
int main()
{
	GraphList g;
	CreateGraph(&g);
	PrintGrapth(&g);
	return 0;
}

创建上文中的有向网图,程序执行演示样例截图:

若创建无向网图。执行截图为:

对于n个顶点e条边。本算法的时间复杂度是O(n + e·n),由于输入的顶点信息不是顶点的编号。须要通过查找才可以得到顶点在图中的位置。

假设输入的是顶点的编号。那么仅仅须要O(n+e)的时间复杂度。

对于有向图,邻接表能够方便的求某一个顶点的出度,可是不方便求入度。须要遍历整个边表。逆邻接表能够方便求入度。但不方便求出度。

另外关于图的存储结构还有:十字链表、邻接多重表。

3、图的遍历

图的遍历(Traversing Graph):从图中某一顶点出发訪遍图中其余顶点,且使每个顶点仅被訪问一次。

图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。

3.1深度优先搜索

深度优先搜索(Depth First Search),简称DFS,其遍历类似树的前序遍历。

它从图中某个结点v出发,訪问此顶点。然后从v的未被訪问的邻接点出发深度优先遍历图。直至图中全部和v有路径相通的顶点都被訪问到。

若图中尚有顶点未被訪问,则另选图中一个未曾被訪问的顶点作起始点。反复上述过程,直至图中的全部顶点都被訪问到为止。

邻接矩阵存储结构的DFS代码:

bool visited[MAX_VEX];//訪问标志数组

//邻接矩阵的深度优先递归算法
void DFS(Graph *g, int i)
{
	int j;
	visited[i] = true;
	printf("%c ", g->vexs[i]);	//打印顶点。也能够是其它操作
	for (j = 0; j < g->vexNum; j++)
		if (g->arc[i][j] > 0 && g->arc[i][j] != INFINITY && !visited[j])
			DFS(g, j);
}

//邻接矩阵的深度遍历操作
void DFSTraverse(Graph *g)
{
	int i;
	for (i = 0; i < g->vexNum; i++)
		visited[i] = false;		//初始化全部顶点状态都是未訪问过状态
	for (i = 0; i < g->vexNum; i++)
		if (!visited[i])
			DFS(g, i);		   //对未訪问的顶点调用DFS.若是连通图。仅仅会运行一次
}

邻接表存储结构的DFS代码:

//邻接矩阵的深度优先递归算法
void DFS(GraphList *g, int i)
{
	EdgeNode *e;
	visited[i] = true;
	printf("%c ", g->adjList[i].data);	//打印顶点,也能够是其它操作
	for (e = g->adjList[i].firstedge; e != NULL; e = e->next)
		if (!visited[e->adjvex])
			DFS(g, e->adjvex);
}

//邻接矩阵的深度遍历操作
void DFSTraverse(GraphList *g)
{
	int i;
	for (i = 0; i < g->vexNum; i++)
		visited[i] = false;		//初始化全部顶点状态都是未訪问过状态
	for (i = 0; i < g->vexNum; i++)
		if (!visited[i])
			DFS(g, i);		   //对未訪问的顶点调用DFS.若是连通图,仅仅会运行一次
}

对于n个顶点e条边的图来说。邻接矩阵由于是二维数组,要查找某个顶点的邻接点须要訪问矩阵中的全部元素,由于须要O(n^2)的时间。而邻接表做存储结构时。找邻接点所需的时间取决于顶点和边的数量,所以是O(n+e)。显然对于点多边少的稀疏图来说,邻接表结构使得算法在时间效率上大大提高。

3.2广度优先搜索

广度优先搜索(Breadth First Search),简称DFS。其遍历类似树的层次遍历。

如果从图中某顶点v出发。在訪问了v之后依次訪问v的各个未曾訪问过的邻接点,然后分别从这些邻接点出发依次訪问它们的邻接点。并使“先被訪问的顶点的邻接点“先于“后被訪问的顶点的邻接点”被訪问。直至图中全部已被訪问的顶点的邻接点都被訪问到。若此时图中尚有顶点未被訪问,则选中图中一个未曾被訪问的顶点 作起始点,反复上述过程直至图中全部顶点都被訪问到为止。

//邻接矩阵的BFS
void BFSTraverse(Graph *g)
{
	SqQueue q;
	int v;
	for (v = 0; v < g->vexNum; v++)
		visited[v] = false;
	InitQueue(q);		 //辅助队列
	for (v = 0; v < g->vexNum; v++)
		if (!visited[v])	//第v个顶点尚未訪问
		{
			visited[v] = true;
			printf("%c ", g->vexs[v]);	 //打印顶点,也能够是其它操作
			EnQueue(q, v);	//第v个顶点入队列
			while (!QueueEmpty(q))
			{
				int i;
				DeQueue(q, i);
				for (int j = 0; j < g->vexNum; j++)
					//推断其它顶点若与当前顶点存在边且未訪问过
					if (g->arc[i][j] > 0 && g->arc[i][j] != INFINITY && !visited[j])
					{
						visited[j] = true;		 //j为i尚未訪问过的邻接顶点
						printf("%c ", g->vexs[j]);
						EnQueue(q, j);
					}  //if
			}//while
		}//if
}
//邻接表的BFS
void BFSTraverse(GraphList *g)
{
	SqQueue q;
	int i;
	for (i = 0; i < g->vexNum; i++)
		visited[i] = false;
	InitQueue(q);
	for (i = 0; i < g->vexNum; i++)
		if (!visited[i])
		{
			visited[i] = true;
			printf("%c ", g->adjList[i].data);
			EnQueue(q, i);
			while (!QueueEmpty(q))
			{
				int j;
				DeQueue(q, j);
				//找到当前顶点边表链表头指针
				for (EdgeNode *e = g->adjList[j].firstedge; e != NULL; e = e->next)
					if (!visited[e->adjvex])
					{
						visited[e->adjvex] = true;
						printf("%c ", g->adjList[e->adjvex].data);
						EnQueue(q, e->adjvex);
					}  //if
			}//while
		}  //if
}

图的深度优先遍历与广度优先遍历算法在时间复杂度上是一样的。不同之处只在于对顶点的訪问顺序不同。

參考:数据结构(C语言版)

本文所有測试代码:http://download.csdn.net/detail/u013071074/7445893

蓝色梦魔

2014.6.4。16:20

再參考:经典算法研究系列:四、教你彻底了解透明:BFS和DFS优先搜索算法(这个精心编写博客撰文系列)

版权声明:本文博客原创文章,博客,未经同意,不得转载。

时间: 2024-10-12 10:11:21

图数据结构(期限、存储结构、遍历)的相关文章

图总结之存储结构代码详解

一.图的存储结构 1.1 邻接矩阵 图的邻接矩阵存储方式是用两个数组来表示图.一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息. 设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为: 看一个实例,下图左就是一个无向图. 从上面可以看出,无向图的边数组是一个对称矩阵.所谓对称矩阵就是n阶矩阵的元满足aij = aji.即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的. 从这个矩阵中,很容易知道图中的信息. (1)要判断任意两顶点是否有

二叉树 二叉树的性质 存储结构 遍历二叉树 C实现二叉树的创建和遍历 线索二叉树

定义 二叉树(binary tree)是n(n>=0)个结点的有限集合,该集合为空集合称为空二叉树,或者有一个根结点和两棵互不相交的,分别称为树根结点的左孩子树和右孩子树组成. 二叉树的特点 每个结点最多有两棵子树,所以二叉树总没有度大于2的结点 左子树和右子树是有顺序的,次数不能任意颠倒 即使树中某结点只有一棵子树,也要区分是左子树还是右子树 特殊的二叉树 1. 斜树 所有的结点都只有左子树的二叉树称为左斜树; 所有的结点都只有右子树的二叉树称为右斜树; 这两者统称为斜树 2. 满二叉树 在一

SQL Server 索引(一)数据结构和存储结构

本文关注以下方面(本文所有的讨论基于SQL Server数据库): 索引的分类: 索引的结构: 索引的存储 一.索引定义分类 让我们先来回答几个问题: 什么是索引? 索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息. 举个例子,索引就像我们查字典时用的按拼音或笔画或偏旁部首 有哪些索引? 从物理结构上可分为两种:聚集索引和非聚集索引 (此外还有空间索引.筛选索引.XML索引) 索引说明 (http://msdn.microsoft.com/zh-cn/l

数据结构(二十九)图的邻接矩阵存储结构

一.邻接矩阵的Java语言代码实现: 二.邻接矩阵的创建C语言代码实现: #include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXVEX 10

图的常用存储结构

一.邻接矩阵 邻接矩阵是简单的也是比较常用的一种表示图的数据结构,对于一个有N个点的图,需要一个N*N的矩阵,这个矩阵的i行第j列的数值表示点vi到点vj的距离.邻接矩阵需要初始化,map[i][i] = 0;map[i][j] = INF(i != j),对于每组读入的数据vi,vj,w(vi为边的起点,vj为边的终点,w为边的权值),赋值map[vi][vj] = w,另外邻接矩阵的值和边的输入顺序无关. 对于邻接矩阵来说,初始化需要O(n^2)的时间,建图需要O(m),所以总时间复杂度是O

图的邻接矩阵存储结构

如上图,我们可以把v0标记为0,v1标记为1.... 并把联通的2点权值全设置为1,那么可以用邻接矩阵(右图)来表示 概念解析: 第一个邻接顶点: 我们以vo为例,第一个邻接顶点为V1(其实也可以使V3,只不过考虑计算机的存储顺序,我们找邻接顶点,一般是从v0扫描到v3,所以我们先在内存中扫描到v1) 下一个邻接顶点: 我们以v0为例,下一个邻接顶点就是v3(同样,其实也可以使V1,只不过考虑计算机的存储顺序,我们找下个邻接顶点,一般是从v2扫描到v3,之所以从v2扫描起,那是因为,V1已经是第

Nutch中Web图基本类型和存储结构

类Node表示Web图中节点,基本信息包括:入链数.出链数.入链分数和元数据.出链分数通过入链分数除以出链数得到. 类LinkDatum表示Web图中链接,基本信息包括:链接.锚文本.分数.时间戳和链接类型(出链或入链). 类LinkNode表示链接节点,包括链接和Node两部分. 类LoopSet表示链接构成的环,包含环中的链接集合. Web图由抓取的段(主要是parse-data,可选包括crawl-fetch)生成,包括三部分:出链数据库.入链数据库和节点库. 设Web图所在目录为w,则:

《大话数据结构》笔记(7-2)--图:存储结构

第七章  图 图的存储结构 图不能用简单的顺序存储结构来表示. 而多重链表的方式,即以一个数据域和多个指针域组成的结点表示图中的一个顶点,尽管可以实现图结构,但是会有问题,比如若各个顶点的度数相差很大,按度数最大的顶点设计结点结构会造成很多存储单元的浪费,而若按每个顶点自己的度数设计不同的顶点结构,又带来操作的不便. 对于图来说,如何对它实现物理存储是个难题.图有以下五种不同的存储结构. 邻接矩阵 图的邻接矩阵(Adjacency Matrix)存储方式使用过两个数组来表示图.一个一维数组存储图

【数据结构】数据的存储结构

数据有有线性结构.树形结构.图状结构和集合四种逻辑结构,那么它们是如何存储的呢? 数据结构的存储结构有两种,分别是顺序存储和链式存储.顺序存储的特点是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系:链式存储的特点是借助指针表示数据元素质检单逻辑关系. 1.线性结构:结构中的元素之间存在着一对一的线性关系. 如图为一个线性结构,那么它的顺序存储和链式存储如何呢?如下图: 顺序结构 链式结构 线性结构如数组的存法,按一定顺序存放:而链式结构如链表的存法,结点可以任意存放,如上图,所以要用n

14 图的基础知识-几种常用的存储结构

时间有点紧 没时间接着更了..考完研回头再写吧 一.邻接矩阵1.描述:用一维数组存储图顶点的信息用二维数组存储图边的信息2.特点:①无向图的邻接矩阵: 是唯一的对称矩阵,可以压缩存储(仅存储上/下三角): 第i行(列)非零元素为第i个顶点的度②有向图的邻接矩阵: 第i行(列)的非零元素为第i个顶点的出度(入度):③邻接矩阵的局限性:要确定多少边必须按行列检测,花费时间的代价很大.④用于确定两点之间是否有边非常方便⑤设图G邻接矩阵为A,A的n次方元素A^n[i][j]等于由顶点i~j长度为n的路径