图的深度优先遍历C汇总.docx

上传人:b****4 文档编号:24865609 上传时间:2023-06-02 格式:DOCX 页数:36 大小:96.44KB
下载 相关 举报
图的深度优先遍历C汇总.docx_第1页
第1页 / 共36页
图的深度优先遍历C汇总.docx_第2页
第2页 / 共36页
图的深度优先遍历C汇总.docx_第3页
第3页 / 共36页
图的深度优先遍历C汇总.docx_第4页
第4页 / 共36页
图的深度优先遍历C汇总.docx_第5页
第5页 / 共36页
点击查看更多>>
下载资源
资源描述

图的深度优先遍历C汇总.docx

《图的深度优先遍历C汇总.docx》由会员分享,可在线阅读,更多相关《图的深度优先遍历C汇总.docx(36页珍藏版)》请在冰豆网上搜索。

图的深度优先遍历C汇总.docx

图的深度优先遍历C汇总

西北师范大学地环学院地理信息系

数据结构实验讲义

十图的存储与深度优先遍历

张长城

2011-2-8

[图存储以及深度优先遍历算法分析,C语言实现]

实验任务描述

1用C语言邻接矩阵完成图的存储;

2分析深度优先遍历算法;

3用C语言实现图的深度优先遍历;

4深度优先遍历应用:

图的关节点计算。

一、

邻接矩阵存储图的深度优先遍历过程分析

对图1这样的无向图,要写成邻接矩阵,则就是下面的式子

图1

顶点矩阵:

V=

弧长矩阵:

A=

一般要计算这样的问题,画成表格来处理是相当方便的事情,实际中计算机处理问题,也根本不知道所谓矩阵是什么,所以画成表格很容易帮助我们完成后面的编程任务。

在我们前面介绍的内容中,有不少是借助着表格完成计算任务的,如Huffman树。

V1

(1)

V2

(2)

V3(3)

V4(4)

V5(5)

V6(6)

V7(7)

V8(8)

V1

(1)

0

1

1

0

0

0

0

0

V2

(2)

1

0

0

1

1

0

0

0

V3(3)

1

0

0

0

0

1

1

0

V4(4)

0

1

0

0

0

0

0

1

V5(5)

0

1

0

0

0

0

0

1

V6(6)

0

0

1

0

0

0

1

0

V7(7)

0

0

1

0

0

1

0

0

V8(8)

0

0

0

1

1

0

0

0

表1图1的邻接矩阵表

为了记录那些顶点是已经走过的,还要设计一个表来标记已经走过的顶点,在开始,我们假设未走过的是0,走过的是1,于是有:

V1

V2

V3

V4

V5

V6

V7

V8

0

0

0

0

0

0

0

0

表2图1的顶点访问表Visited

深度优先遍历过程如下:

(1)从第1行开始,寻找和V1相连的第1个顶点,首先在Visited表中标记V1被访问到,就是:

V1

V2

V3

V4

V5

V6

V7

V8

1

0

0

0

0

0

0

0

表3图1的顶点访问表Visited深度优先遍历步骤

(1)

在该行,我们找到的第一个连接顶点是V2,找到V2顶点后,记录:

V1->V2,意味着我们已经抵达V2,注意修改邻接矩阵表;

V1

(1)

V2

(2)

V3(3)

V4(4)

V5(5)

V6(6)

V7(7)

V8(8)

V1

(1)

0

1

1

0

0

0

0

0

V2

(2)

1

0

0

1

1

0

0

0

表4图1的邻接矩阵表深度优先遍历步骤

(1)

(2)然后则转向V2顶点所在的行,意味着我们已经抵达V2,再次在Visited表中标记V2顶点已经被访问,就是:

V1

V2

V3

V4

V5

V6

V7

V8

1

1

0

0

0

0

0

0

表5图1的顶点访问表Visited深度优先遍历步骤

(2)

然后,寻找连接V2的、并且是未被访问过的第一个顶点,就是V4:

记录V2->V4;

V1

(1)

V2

(2)

V3(3)

V4(4)

V5(5)

V6(6)

V7(7)

V8(8)

V1

(1)

0

1

1

0

0

0

0

0

V2

(2)

1

0

0

1

1

0

0

0

V4(4)

0

1

0

0

0

0

0

1

表6图1的邻接矩阵表深度优先遍历步骤

(2)

(3)然后则转向V4顶点所在的行,意味着我们已经抵达V4,再次在Visited表中标记V4顶点已经被访问,就是:

V1

V2

V3

V4

V5

V6

V7

V8

1

1

0

1

0

0

0

0

表7图1的顶点访问表Visited深度优先遍历步骤(3)

然后则转向V4顶点所在的行,寻找连接V4的、并且是未被访问过的第一个顶点,就是V8:

记录V4->V8;

V1

(1)

V2

(2)

V3(3)

V4(4)

V5(5)

V6(6)

V7(7)

V8(8)

V1

(1)

0

1

1

0

0

0

0

0

V2

(2)

1

0

0

1

1

0

0

0

V4(4)

0

1

0

0

0

0

0

1

V8(8)

0

0

0

1

1

0

0

0

表8图1的邻接矩阵表深度优先遍历步骤(3)

(4)然后则转向V8顶点所在的行,意味着我们已经抵达V8,再次在Visited表中标记V8顶点已经被访问,就是:

V1

V2

V3

V4

V5

V6

V7

V8

1

1

0

1

0

0

0

1

表9图1的顶点访问表Visited深度优先遍历步骤(4)

然后则转向V8顶点所在的行,寻找连接V8的、并且是未被访问过的第一个顶点,就是V5:

记录V8->V5;

V1

(1)

V2

(2)

V3(3)

V4(4)

V5(5)

V6(6)

V7(7)

V8(8)

V1

(1)

0

1

1

0

0

0

0

0

V2

(2)

1

0

0

1

1

0

0

0

V4(4)

0

1

0

0

0

0

0

1

V8(8)

0

0

0

1

1

0

0

0

V5(5)

0

1

0

0

0

0

0

1

表10图1的邻接矩阵表深度优先遍历步骤(4)

(5)然后则转向V5顶点所在的行,意味着我们已经抵达V5,再次在Visited表中标记V5顶点已经被访问,就是:

V1

V2

V3

V4

V5

V6

V7

V8

1

1

0

1

1

0

0

1

表11图1的顶点访问表Visited深度优先遍历步骤(5)

寻找连接V5的、并且是未被访问过的第一个顶点,此处未找到,注意V2、V8顶点已经在Visited表中标记已访问过。

V1

(1)

V2

(2)

V3(3)

V4(4)

V5(5)

V6(6)

V7(7)

V8(8)

V1

(1)

0

1

1

0

0

0

0

0

V2

(2)

1

0

0

1

1

0

0

0

V4(4)

0

1

0

0

0

0

0

1

V8(8)

0

0

0

1

1

0

0

0

V5(5)

0

1

0

0

0

0

0

1

表12图1的邻接矩阵表深度优先遍历步骤(4)

(5)这个地方一定注意:

V5上找不到未访问过的顶点,说明此路到此就算走死了。

此时看Visited表:

其中还有顶点没有抵达过,于是要按原路返回,所谓原路就是从表12、表10、表9走过的路线返回、然后逐个查找这些顶点上有无未抵达过的顶点。

过程如下:

再次从V5返回到V8,查找V8上有无未抵达过的顶点,结果是无;

再次从V8返回到V4,查找V4上有无未抵达过的顶点,结果是无;

再次从V4返回到V2,查找V2上有无未抵达过的顶点,结果是无;

再次从V2返回到V1,查找V1上有无未抵达的顶点,结果是V3,于是重复第

(1)步,首先标记V3访问到:

标记V1->V3,标记Visited表V3被访问:

V1

V2

V3

V4

V5

V6

V7

V8

1

1

1

1

1

0

0

1

表13图1的顶点访问表Visited深度优先遍历步骤(4)

到V3,就是这样的情况:

V1

(1)

V2

(2)

V3(3)

V4(4)

V5(5)

V6(6)

V7(7)

V8(8)

V1

(1)

0

1

1

0

0

0

0

0

V2

(2)

1

0

0

1

1

0

0

0

V4(4)

0

1

0

0

0

0

0

1

V8(8)

0

0

0

1

1

0

0

0

V5(5)

0

1

0

0

0

0

0

1

V3(3)

1

0

0

0

0

1

1

0

表14图1的邻接矩阵表深度优先遍历步骤(5)

(6)到达V3后,寻找第一个未被访问过的顶点:

V6,首先标记Visited表,说明已经抵达V6,就是:

V1

V2

V3

V4

V5

V6

V7

V8

1

1

1

1

1

1

0

1

表15图1的顶点访问表Visited深度优先遍历步骤(6)

再从V6开始找下一个顶点就是V7:

V1

(1)

V2

(2)

V3(3)

V4(4)

V5(5)

V6(6)

V7(7)

V8(8)

V1

(1)

0

1

1

0

0

0

0

0

V2

(2)

1

0

0

1

1

0

0

0

V4(4)

0

1

0

0

0

0

0

1

V8(8)

0

0

0

1

1

0

0

0

V5(5)

0

1

0

0

0

0

0

1

V3(3)

1

0

0

0

0

1

1

0

V7(7)

0

0

1

0

0

1

0

0

表16图1的邻接矩阵表深度优先遍历步骤(6)

(7)在Visited表中标注V7已经访问到,就是:

V1

V2

V3

V4

V5

V6

V7

V8

1

1

1

1

1

1

1

1

表17图1的顶点访问表Visited深度优先遍历步骤(7)

至此,图1的深度优先遍历完成。

二、结果分析

从上面的过程可以看出:

仅仅就顶点访问到的次序而言,图1的深度优先遍历结果是:

V1->V2->V4->V8->V5->V3->6->V7

但实际执行过程中我们可以发现:

所谓图的遍历、其结果应该是一个树:

图2深度优先遍历生成树

在C语言中,显示这个结果并不容易,所以大多教材中并不会给出这样的结果。

三、C语言编程实现图的深度优先遍历

图1只有8个顶点,可在实际中,一个图的顶点个数是不确定的,在编程中要保存顶点数据、邻接矩阵,首先就要考虑动态数组;

其次,为了方便邻接矩阵的输入和修改,最好是把数据保存在文本文件中。

在我们的教材中、程序使用了键盘输入的方式,而在实际操作中、在图的顶点个数比较多的情况下,手工无差错输入很多数据、几乎是无法办到的事情,为此,我们在文件p176G719.txt中保存了图1的邻接矩阵数据。

这个文件的名称含义是:

教材176页图7.19的顶点名称和邻接矩阵数据。

有了这样的数据文件,在记事本程序中可以很方便的修改和补充数据。

这个文件的内容如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

8

V1

V2

V3

V4

V5

V6

V7

V8

01100000

10011000

10000110

01000001

01000001

00100010

00100100

00011000

表18p176G719.txt文件内容

这个文件第一行是8,说明这个图有8个顶点,随后在第2行到第9行,则是图的顶点名称,第10行到末尾,则是该图的邻接矩阵。

根据不同的图,在记事本程序中完成这样的数据是非常简单的事情,哪怕错了再修改也是很容易做到。

1设计图的存储结构以及数据文件读取

图的存储、无论哪种方法,都是由两部分组成的,一个是顶点名称集合,一个是顶点关系集合,在邻接矩阵方式中,顶点名称是一个字符串数组,而顶点关系则是一个矩阵、这个矩阵在C语言中是一个二维数组。

于是图的结构可以是:

structGraph

{

intA[100][100];//邻接矩阵

charV[100][20];//顶点名称矩阵,100行,每个名称字符串不超过20字节

intnum;//顶点个数

intVisited[100];//访问记录表

};

但这样的定义很死板,它假设程序最大是100个顶点,实际我们的教材中就是这么定义的。

但幸好我们前面已经知道该怎么处理二维数组,于是这里我们可以动态申请内存,以保证在很多顶点的情况也能使用,对二维数组,则上述定义变为:

structGraph

{

int**pA;//邻接矩阵指针

char**pV;//顶点名称指针

intnum;//顶点个数

int*Visited;//访问记录表指针

};

对这样数组的构造,参见第5部分:

数组,好在我们前面有过介绍。

回忆一下,如有数组:

intA[3][3]={{1,2,3},{4,5,6},{7,8,9}};

则A[0]、A[1]、A[2]则代表每一行的地址,一般称为行首地址,比如这三行行首地址分别是[100]、[200]、[300],这三个地址数据分别存储在地址[2000]、[2001]、[2002]的存储空间里,则地址[2000]就是这个数组A的含义,就是所谓行首地址数组的首地址。

反过来,如:

int*p[3];

p[0]=A[0];p[1]=A[1];p[2]=A[2];

上面的式子里,如p[0]地址为[3000]、[3001]、[3002],其中的内容保存的是100、200、300,这样就相当于保存了A数组的行首地址,所以p就是个二维数组行首指针数组,只不过它仅仅是三行的二维数组。

如把p[0]的地址给另外一个指针变量pA,则pA就是:

int**pA;

假如这个变量的地址在[5000],给这个变量赋值:

pA=&p[0];

于是地址[5000]中将存储3000,这里pA和A的含义是一致的。

实际数组A本身就是地址[5000],如有以下语句:

X=A[1][2];

就是从A的地址[5000]、读到内容3000、再从3001读到200、再从200后取第1个数,过程如下图3所示:

图3二维数组的数据读取过程

针对n个顶点,则初始化一个图的函数就是:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

#defineVLENGTH20//定义每个顶点名称不超过20字节

structGraph*GraphInit(intn)

{

inti;

structGraph*g;

if(n<=0)returnNULL;

g=(structGraph*)malloc(sizeof(structGraph));

g->num=n;

g->pA=(int**)malloc(sizeof(int)*n);

g->pV=(char**)malloc(sizeof(char)*n);

for(i=0;i

{

g->pA[i]=(int*)malloc(sizeof(int)*n);

g->pV[i]=(char*)malloc(sizeof(char)*VLENGTH);

}

g->Visited=(int*)malloc(sizeof(int)*n);

for(i=0;i

g->Visited[i]=0;

returng;

}

表19动态申请内存,为n个顶点构造顶点名称数组、邻接矩阵数组,见程序G0.C

注意第9行,是为行首地址数组申请内存;

注意第13行,是为每行数据申请存储空间;

注意第14行,前面定义每个顶点的名称是VLENGTH长度,这里是20字节。

由于C语言的指针可以用下标法读写内容,所以完全可以把pA、pV当做普通的二维数组来处理,此处不再叙述。

2从文件中读数据到邻接矩阵和顶点名称矩阵

这个过程在链表的处理中已经介绍过了,只不过数据格式有差异而已。

针对我们前面介绍的数据格式,读数据文件并构造图的函数如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

structGraph*GraphCreat(charFileName[20])

{

inti,j,n;

FILE*fp,*fopen();

structGraph*G;

fp=fopen(FileName,"r");

if(fp==NULL)returnNULL;

fscanf(fp,"%d",&n);

G=GraphInit(n);

for(i=0;i

fscanf(fp,"%s",G->pV[i]);

for(i=0;i

for(j=0;j

fscanf(fp,"%d",&G->pA[i][j]);

fclose(fp);

returnG;

}

表20从文件中读顶点数据以及邻接矩阵数据见G0.c

第8行首先读文件中顶点个数,然后根据顶点个数、使用GraphInit()申请内存并构造这个图的存储空间,然后在第10行读n个顶点名称、在第12行按二维数组的组织读邻接矩阵。

最后,返回G就是包含有顶点名称、邻接矩阵的图的存储空间。

有了这两个函数后,就可以编写main()来测试它们,就是:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

main()

{

inti,j;

structGraph*G;

G=GraphCreat("p176G719.txt");

//打印顶点名称

for(i=0;inum;i++)

printf("%s",G->pV[i]);

printf("\n");

//打印邻接矩阵

for(i=0;inum;i++)

{

for(j=0;jnum;j++)

printf("%d",G->pA[i][j]);

printf("\n");

}

}

表21从文件中读顶点数据以及邻接矩阵数据并显示见G0.c

这个程序第5行,你可以修改成用scanf()来读到一个文件名称字符串,然后,就可以使用任何格式符合要求的数据文件了。

G0.C中还包含有GraphFirstAdj()、GraphNextAdj()、GraphDestory()三个函数,这些函数的意义你能看懂么?

2深度优先遍历的编程实现

从前面算法分析过程可知:

对一个图的深度优先遍历,实际就是从第n个顶点开始、标记该顶点已被访问,然后查找该顶点上第一个和它相连、并且未被访问到的顶点、比如是第i个顶点,再去第i个顶点,如此繁琐的说这些,实际就是:

1

2

3

4

5

6

7

8

9

10

voidDFS(structGraph*G,intn)

{

inti;

if(G==NULL)return;

if(n<0||n>G->num)return;

G->Visited[n]=1;

printf("%s",G->pV[n]);

for(i=0;inum;i++)

if(G->pA[n][i]!

=0&&G->Visited[i]!

=1)DFS(G,i);

}

表22深度优先遍历图见G1.c

第6行是标记该顶点被访问;

第9行就是:

查找第n个顶点上、未被访问到的顶点,如找到该顶点、且顶点编号是i,则再次DSF(G,i);

有了这个函数后,构造main()开始从第0个顶点遍历图1,就是:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

main()

{

inti,j;

structGraph*G;

G=GraphCreat("p176G719.txt");

for(i=0;inum;i++)

printf("%s",G->pV[i]);

printf("\n");

for(i=0;inum;i++)

{

for(j=0;jnum;j++)

printf("%d",G->pA[i][j]);

printf("\n");

}

DFS(G,0);

printf("\n");

}

表23深度优先遍历图见G1.c

进一步测试该函数,按图1的数据仔细分析下它的执行过程,如有图的连接分量不为1,则会在第一个连接分量遍历完成后终止。

如下图4,在G1.C中是无法全部遍历完成的。

这个图的文件在G4.TXT,修改表23中第5行,从G4.TXT中读数据,则会发现这个程序仅仅遍历了A、B、C、D,而没有到达过E、F、G这三个顶点。

图4两个连同分量的图

为确保多个分量的图都能顺利遍历完成,则该函数退出后还需要判断是有顶点是否确保全部遍历完成、并确保每次遍历开始的时候、其访问数组Visited[]中全部是0,就是:

1

2

3

4

5

6

7

8

9

10

voidDFSTraverse(structGraph*G)

{

inti;

if(G==NULL)return;

for(i=0;inum;i++)

G->Visited[i]=0;

for(i=0;inum;i++)

if(G->Visited[i]==0)DFS(G,i);

}

表24深度优先遍历图见G2.c

表24中函数,很容易修改成计算图的连接分量的函数,这个工作就由同学们自己完成。

如果你遇到困难无法完成,参见G3.C

略加修改main()函数,补充:

DFSTraverse(G);

即可完成图4的深度优先遍历。

到此,C语言的深度优先遍历到此结束。

四、图的遍历及其应用

1图的关节点

图的关节点、在图上或许仅仅是个理论或者方法,但对GIS而言,却绝对是个重要意义的理论、尽管目前还没见到这类应用。

求解图的关节点、是典型的深度优先遍历应用,首先我们从教材中找到G5的图,其邻接矩阵如下:

A

B

C

D

E

F

G

H

I

J

K

L

M

A

0

1

1

0

0

1

0

0

0

0

0

1

0

B

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 动物植物

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1