简要
图的结构类似于树,都是一个又一个结点(顶点)连接而成,如下图:
因此树其实也是一种特殊的图结构,树的每个节点只能有一个入度,而图可以有多个,图的知识点在这里解不讲解了,例如弧,入度,出度等可以查阅书籍或者网上查阅。这里主要讲解图的存储结构。
图的存储结构
图有多种存储结构,例如邻接矩阵,邻接表或者十字链表等,那么它是如何用结构体来实现上面图这种抽象结构的?
之前文章所说,数据结构一般都有两种表示方法,一种是数组,一种是链式,由于图的结构比较复杂,任意两个点之间都有可能存在联系,因此无法以数据元素放到数组这种来表示它的关系。但是换个思路,当遇到简单的图(有向和无向)或者网(有向和无向,带有路径,权就称为网)表示的时候,你可以用两个数组来存储图的数据信息和顶点之间的关系。那么就引出了邻接矩阵。
邻接矩阵
上面说用两个数组来表示图的关系,具体什么意思,首先拿最简单的有向图来举例:
以上关系简单理解,V1可以到V2,V2可以到V3等等这里就不细说。这里所说的用矩阵来表示该图关系,其实是用一个二维数组来表示该矩阵。用下面图来解释什么意思:
二维数组都有序号,有几个顶点就有几行几列,比如V1可以到V2,那么在数组上的第一行第二列的值就应该为 1 , V1无法到达V3
,那么数组中第一行的第三列的值就为0,那么自然就懂了, 图中顶点的序号对应数组中的位置 (当然数组中也是从0开始算起的,这里只是帮助理解),有路径,可以到达,那么对应数组的值就为1,如果没有路径那么对应的值就为0,
那么这里就用了一个二维数组来表示了图中各顶点之间的关系。自然可以发现,第一行为1 的个数就是顶点序号为1 的出度数,列就是顶点总的入度数。
上面这个是有向图的二维数组定义,那么无向图的二维数组自然就懂了,无向图的定义是两个顶点可以互相到达,那么二维数组就自然成了对称矩阵了,可以自己画画,这里就不说了。
然后再定义一个数组来存放各个顶点的数据关系,那么就完成了图结构的创建,也可以抽象的想成,定义一个储存图顶点的数组,再定义一个二维数组来存储顶点的关系。
上图就已经完全解释了邻接矩阵如何实现了存储,就是定义一个图的结构体,然后其中定义俩数组一个放顶点(包括顶点的信息),一个放顶点的位置关系。
那么定义的结构体为:
//图结构体的定义
#define MAX_SIZE 20 //定义的最大存储顶点数目
typedef struct
{
VertexType vex[MAX_SIZE]; //定义顶点数组,存放顶点的信息,如果信息复杂了那么自然还要定义顶点的结构体
AdjMatrix arcs[MAX_SIZE][MAX_SIZE]; //定义的邻接矩阵(顶点位置关系),如果单纯的就表示一些距离值,那么直接定义一个int 类型的二维数组即可,如果顶点间还有其他信息那么就将其定义成一个结构体
int vexnum,arcnum; //定义图的顶点数和弧数,弧数就是顶点关系的个数
}Graph;
//初始化图
void init(Graph &G)
{
int i,j;
printf("请输入顶点数和弧数");
scanf("%d",&i);
scanf("%d",&j);
G.vexnum=i; //对图的信息进行初始化
G.arcnum=j;
for(int x=0;x<G.vexnum;x++) //这里是根据定义的节点数来对矩阵进行初始化
{
for(int y=0;y<G.vexnum;y++)
{
G.arcs[x][y]=0; //对图结构中的位置关系进行初始化,先都化为0,表示节点直接都不相连
}
}
for(int x=0;x<G.vexnum;x++)
{
printf("输入顶点的信息"); //对节点信息进行输入
scanf("%s",&G.vex[x]); //输入到定义的顶点信息数组中
}
for(int x=0;x<G.vexnum;x++)
{
printf("请输入要连接的节点");
int a,b;
scanf("%d",&a); //按照顶点在位置数组中的序号来写,数组都是从 0 开始的,也可以写一个根据信息来定在数组中位置的函数。
scanf("%d",&b);
G.arcs[a][b]=1; //获取了需要连接顶点的位置后,将其对应数组中的位置等于 1,表示相通
//G.arcs[b][a]=G.arcs[a][b] 若为无向图,自然顶点是互相连通的,所以在数组中也是对称的
}
}
//输出函数 (就是将图的邻接矩阵输出,就能得到图中顶点的关系)
void printGraph(Graph &G)
{
for(int i=0;i<G.vexnum;i++)
{
for(int j=0;j<G.vexnum;j++)
{
printf("%d ",G.arcs[i][j]); //这里是图,所以设置的是1和0代表是否有路径
}
printf("\n");
}
}
这里假设有个例子
要来实现上面的图,顶点信息就存它们顶点的名称。
//结构体定义
typedef struct //简单点的图结构
{
char vex[MAX_SIZE]; //顶点信息,这里假设存放字符串,表示他们的顶点
int arcs[MAX_SIZE][MAX_SIZE]; //位置关系数组
int vexnum,arcnum;
}Graph;
init(); //一下两个函数定义就不重复写了
printGraph();
int main()
{
Graph G;
init(G);
printGraph(G);
}
那么就输出上面的例图:
先输入图结构的信息,几个顶点几条弧( 几条边 )然后输入三个顶点的信息,执行三次输入操作,然后顶点信息输入完成后,输入弧的信息,你要连接的点,按照顶点在数组中的位置(顶点在顶点数组的位置要和在邻接矩阵对应好),然后输出邻接矩阵,如图,那么可以看出第一行第二个第三个为 1 说明V0可以通到V1和V2 。可以慢慢理解
上面是有向图,那么有向网(带权值的图,带路径的)自然就懂了。那么初始化定义就可以在原来的基础上加上:
void init(Graph &G) //初始化函数和上面的一样,就是在后面加上权值赋值的操作
{
int i,j;
printf("请输入图的节点数和弧数");
scanf("%d",&i);
scanf("%d",&j);
G.vexnum=i;
G.arcnum=j;
for(int x=0;x<G.vexnum;x++)
{
for(int y=0;y<G.vexnum;y++)
{
G.arcs[x][y]=99; //先将邻接矩阵每个值定义为99,因为无法到达,那么它的权值就是无穷大,这里用99代替无穷大
}
}
for(int x=0;x<i;x++)
{
printf("请输入顶点的数据信息");
scanf("%s",&G.vex[x]);
}
for(int x=0;x<j;x++)
{
printf("请输入要连接的点");
int a,b,c;
scanf("%d",&a);
scanf("%d",&b);
printf("请输入它的权值");
scanf("%d",&c); //主要在这里修改即可,将原来的 1改为输入权值即可
G.arcs[a][b]=c;
}
}
那么实现下面这个例子:
上面标有了权值那么如何实现:
int main()
{
Graph G;
init(G);
printGraph(G);
}
输出结果可以从邻接矩阵看出顶点的位置和权值信息。根据矩阵中的序号来推断各顶点之间的信息。(99代表没有路径,无穷大)
邻接矩阵比较简单。
邻接表
邻接表表示法有点类似前面数结构中的孩子表示法,可以看之前的树结构文章。
总而言之,邻接表的功能是找出与其连接的点以及权值,但是你要找它的入度之类却比较麻烦,需要遍历所有顶点,不如邻接矩阵的。
邻接表是如何定义的?
将每个顶点用结构体定义,但是还是要放在那个图结构体的数组中,但是每个顶点结构体中有一个链式表来存放弧的信息,(其连接的下一个点和它的权值)。
上面是框架图,下面为解释图:
每个顶点中有一个链式表来存放它指向的顶点的位置(在数组中的位置)或者也可以存弧的权值,这里的链式表里信息只存放它指向顶点的位置,并不存储指向顶点的数据信息,相当于一个索引。只需要遍历顶点的线性表就可以获得它所指向的有哪些顶点了。这里也可以将该链式表理解为一个存储弧信息的表。
首先定义图的结构体框架:(这个例子是带有权值的,应该叫做网,但是为了大家方便理解,就先叫做图吧)
typedef struct ArcNode //定义图中的表结构,链式表
{
int adjvex; //要连接点在数组中的位置
int arc; //定义顶点与该连接点之间的权值
struct ArcNode *next; //定义下一个节点,链式表之间的链,实现链式表之间的连接
}ArcNode;
typedef struct VNode //定义图顶点结构
{
ArcNode *first; //每个顶点中的表头的头指针,可根据上面的图像理解
char vex[10]; //图信息(数据)的存储,这里也可以存储顶点的多个数据,都由自己定,这里就假设存储的是顶点名称
}VNode; //定义图顶点结构
typedef struct
{
VNode NodeList[10]; //定义顶点数组,存放图的顶点,和上面的道理一样
int vexnum,arcnum; //定义图的属性,几个顶点几条弧
}Graph; //定义图总框架,c语言调用的时候要遵从先后定义顺序,所以该图结构定义在最后面
对链式表不太了解的可以看下面文章
线性表------最通俗易懂的文章
然后我们将该中图结构进行一下实例化:
void initGraph(Graph &G)
{
int x,y;
printf("输入图的节点个数");
scanf("%d",&x);
G.vexnum=x; //定义图的顶点数目
for(int i=0;i<G.vexnum;i++) //对顶点的信息进行初始化,输入,因为这里定义的是顶点数组,所以用循环来定义顶点的信息
{
scanf("%s",&G.NodeList[i].vex); //输入顶点的数据信息
G.NodeList[i].first=(ArcNode*)malloc(sizeof(ArcNode)); //对每个顶点的链表头指针进行初始化,不懂可以看线性表文章
G.NodeList[i].first->next=NULL;
}
for(int i=0;i<G.vexnum;i++) //按照顶点在数组中的位置进行循环对顶点的相关弧来对点的弧进行初始化信息
{
printf("这是%s节点,输入它的弧的个数为:",G.NodeList[i].vex);
int n;
scanf("%d",&n); //输入当前顶点的弧数
ArcNode *p; //定义一个头指针来绑定每个顶点中的链式表,号方便进行对其的操作
p=G.NodeList[i].first;
for(int j=0;j<n;j++) //对你定义的弧数进行初始化
{
ArcNode *q;
q=(ArcNode*)malloc(sizeof(ArcNode)); //来为链式表的每一个节点来开辟空间(创建一个临时节点来实现链式表的连接)
printf("要连接的节点:");
scanf("%d",&q->adjvex); //要连接顶点在数组中的位置
printf("弧的长度为:");
scanf("%d",&q->arc); //要连接顶点与当前顶点的权值
q->next=NULL; //实现链式表的连接,这里是从尾部进行连接的,上面文章中是从表头后进行连接的,连接原理可以看上面文章
p->next=q;
p=q; //将p指针绑定成下一个节点,方便进行下一次的连接
}
}
}
再定义一个输出函数,当输入顶点的位置后,就可以输出它所连接的点了,这里是有向图,可以输出它所出度的点。
void printfG(Graph &G,int n)
{
ArcNode *p=G.NodeList[n].first->next; //绑定需要输出的顶点的next指针
printf("这是%s顶点",G.NodeList[n].vex); //
while(p!=NULL) //对链式表进行循环遍历,若为空,说明以及到了链式表的结尾
{
printf("它连接的顶点有%d,",p->adjvex); //对其连接点信息的输出
printf("它俩之间的权值为%d\n",p->arc);
p=p->next; //进行绑定链式表的下一个节点
}
}
具体实现
那么就实现一下上面的图,比较简单,方便理解。可见V0的出度弧有两个,那么该顶点的链式表就有两个节点,同理V1和V2。
int main()
{
Graph G; //定义图的总框架结构
initGraph(G); //对该结构进行初始化
printfG(G,0); //输出相应的顶点,这里就假设输出V0顶点(这里的0是V0在数组中的位置)
}
简介明了,输入节点数后,对这几个节点进行初始化:顶点数据,所连接的有那些顶点和之间的权值等等,当然唯一缺点就是不能看顶点的入度,需要遍历所有的顶点。否则你可以设置一个逆邻表,就是链式表所存的信息都是入度的信息,上面例子中链式表所存的信息都是出度的信息。
上面的邻接表只是方便来看顶点所连接的有哪些点和之间的权值,但是想遍历图中顶点的所有信息和关系,比较麻烦,所以出现了十字链表。
十字链表
由于受篇幅长度影响,这里就大概说一下原理,大家可以研究。上面两种图结构中的弧只是一个抽象的意义,你并没有定义弧这个对象吧,所以说的顶点的连接是用一种关系来代替弧的存在,那么十字链表是将弧也进行结构体封装定义,每有一条弧就定义一个结构体,里面存弧所连俩顶点在数组中的位置,然后两个指针域,一个指向相同入度的点,一个指向相同出度的点,(指针所指向的还是弧的结构体)那么你随便查询一个顶点的出入信息,那么就可以使用它的弧节点的指针域,直到为NULL为止。
可以看下面文章:
十字链表法
图的遍历
图的两种常用遍历方法:深度优先搜索和广度优先搜索
深度遍历
深度遍历算法是使用了递归的思想,随便找到一个顶点,然后找到它连接的第一个顶点,一直找连接的第一个顶点,直到找到没有连接的顶点,然后返回到上一级递归找他的另一个子顶点,然后再开始深度搜索,就是一直往下突,直到没有连接的顶点为止(遍历过的节点不能再遍历一次,也相当于是不能连接的点),然后再返回去,重复递归操作就可以遍历完图中所有顶点。下面给出深度遍历图的流程:
红色数字表示遍历顶点的顺序。
可以看上图慢慢理解,十分简单。这里可以根据树结构的遍历来理解,当你遍历到最后面的节点无处可走的时候,返回到它的上一个节点,看看有没有第二个子节点,如果有就开始遍历它的第二个子节点,如果没有第二个子节点了了,再返回上一个节点,重复此步骤。只不过在图结构中,只要有连接的可遍历点就一口气捅到最后面,然后回溯(一个一个的回溯,仔细检查是否有第二个子顶点),有点贪心法的味道。
实现
那么它是如何实现这种遍历方法?上面说了主要使用递归的方法来实现,首先递归函数的使用和原理得理解,就是定义一个函数,在这个函数里面使用该函数,具体使用这里就不讲解了。
咱们就拿上图做例子
这里创建过程就不写了,首先你遍历的时候,是如何判定顶点是否已经遍历过了,使你不会再进行第二次遍历?就是设置一个布尔(bool)类型的数组,数组的大小就是你顶点的数目,来存放每个点是否遍历过,初始化为false表示未遍历过,当遍历过后就将相应顶点的 bool 设置为true。
举个例子,有三个点需要遍历:
bool visited[3]; //因为三个顶点,所以布尔数组的容量设置为3
for(int i=0;i<3;i++)
{
visited[i]=false; //将每个顶点进行初始化为false,表示每个点都没有遍历过
}
visited[1]=true; //表示第二个顶点遍历过了,所以将其bool值设置为了true
那么需要在程序中设置一个全局变量布尔数组,用来监控图遍历的情况,那么为什么要定义全局变量,而不是直接在图创建的时候,在图结构里面定义?因为该种遍历方法使用了递归的方法,会导致布尔数组结果回溯。导致遍历过的顶点再遍历一次,程序错误。
bool visited[MAX_SIZE]; //全局变量定义需要在函数外
然后定义两个查找子顶点函数
int findFirst(Graph G,int w) //定义一个找第一个子顶点的函数,w表示要找哪个顶点的子顶点
{
for(int n=0;n<G.vexnum;n++)
{
if(G.arcs[w][n]==1&&visited[n]==false) return n; //根据矩阵的特点找第一个顶点,第几个顶点就是在矩阵的第几行,从第一列开始循环,当为1的时候说明相应的顶点相连,第一个1说明就是其第一个顶点
//并且返回第一个顶点是哪个顶点(在数组中的位置)
}
return 0; //若该顶点没有任何相连的点后返回0
}
int findNext(Graph G,int w,int t) //找下一个子顶点函数,w表示要找哪个顶点的子顶点,t表示查找的开始点
{
for(t=t+1;t<G.vexnum;t++) //因为这是找next顶点,所以要t=t+1,原因可以看下面的遍历来理解为什么
{
if(G.arcs[w][t]==1&&visited[t]==false) return t; //和上面的一样,当为矩阵中的值为1 的时候说明两点相连,并且返回是哪个顶点
}
return 0; //当没有相邻的顶点后函数就会返回 0
}
Tip:根据矩阵的性质,给你一个点,可以从矩阵找它的第一个子顶点和下一个子顶点,自己慢慢理解,但是对于无向图大家都知道,是对称矩阵,那么在深度遍历或者广度遍历无向图的时候,找子顶点会有重复现象,已经遍历过了的顶点,再用两个找子顶点函数就会重复,重复遍历,那么就要在找子顶点函数的判断中加上 visited[n]==false 条件,在找到子顶点的同时并且判断该子顶点是否已经遍历过了,如果没有再返回子顶点。
定义深度遍历函数:
void DeepTraverse(Graph G) //深度遍历函数
{
for(int v=0;v<G.vexnum;v++) visited[v]=false; //初始化布尔数组,将每个顶点都设置为false,表示都没有遍历过
for(int v=0;v<G.vexnum;v++) //这步是开始进行遍历,使用下面DFS函数,这里为什么还要对所有顶点进行一次循环?
//深度遍历,只要是相连的顶点,从其中一个点遍历,就可以全部一连串都遍历出来,但是图中可能有不连通的两部分,导致其中不相连,那么有些点就遍历不上了,所以为了防止这种情况,将所有顶点的visited数组遍历一下
{
if(visited[v]==false) DFS(G,v); //根据布尔数组,若为false则开始遍历,调用下面的递归函数,v就是上面开始遍历的点
}
void DFS(Graph G,int v) //遍历递归函数
{
visited[v]=true; //遍历的时候,将其对应布尔数组设置为true,表示已经遍历过了
printf("%s",G.vex[v]); //将遍历的点数据处理,这里就简单的输出作为数据处理
for(int w=findFirst(G,v);w>0;w=findNext(G,v,w)) //开始递归操作,进行一个循环先找当前遍历点的第一个子顶点作为 w 的初始值
//若有第一个子顶点,那么findFirst函数返回的就不是0,开始进行DFS函数递归,若没有子顶点,那么返回0,直接退出该循环,表示该点后面的子顶点遍历完了。
{
if(visited[w]==false) DFS(G,w); //若当前点未遍历过,那么进入DFS函数进行递归
}
}
上面就是深度遍历的过程,主要是由递归函数来实现的,所以比较麻烦,可以进行debug操作来一步一步看其过程,下面举个简单的例子来解释一下代码流程
如何创建图结构上篇文章已经讲了,这里就不多说了,下面是创建的结果:
那么代码执行流程 :
橙色文字表示DFS递归级别,就是DFS1是在DFS 0中调用的,DFS 2实在DFS 1 中调用的,这里想要理解其使用,必须先理解递归函数怎么用和它的原理。
那么会得出其结果为:
因为咱们代码为边遍历边输出它的信息,所以就会得出其遍历顺序为: V0 V1 V3 V2,符合深度遍历的特点。
那么再看之前的例子:
输出的顺序在片头已经给出,那么用程序试一下:
那么输出结果符合,深度遍历实现。
总结
深度遍历你先理解其含义和运行流程,就是随便找到一个顶点一个劲的往下突,突到头后,开始回溯,但是还要将所有顶点再进行一次审查,防止不连通的图,导致顶点遗漏,因为只要开始深度遍历,那么只要其是相连的顶点,那么都可以一次性遍历完,那么这个实现的步骤就是使用递归函数来实现,有点类似8皇后问题。
广度遍历
广度遍历顾名思义,遍历方式是一层一层的,就是只要与当前遍历点距离为1的点先开始遍历,,因此也可以称为广度优先遍历,和深度优先遍历不同的是,深度遍历是一个劲的往下突,而广度像是一种横扫的方式。其实用一张图来演示一下流程就懂了。
红色数字表示遍历点的顺序,有点像扩散,就是寻找当前点距离为1的点,但是它是一层一层的,然后再找与上一次所找的顶点距离为1的点,但它并不是同时遍历的,还是有顺序的遍历。
上面的数组其实是一个辅助理解作用,但是它可以方便理解代码是如何实现的 。
实现
广度优先遍历代码实现需要用队列做基础,从上面图看出,可以假设这里有一个数组(当然不是具体的数组,只是一个抽象的集合),存放需要找距离为1的顶点。当你开始的时候,数组中先存放V0,起始点。然后从数组中取出V0(这时候数组为空),寻找与V0距离为1的点,就是紧挨的点,找到了V1和V2,再放到数组中,然后再按存放的顺序,拿出V1,找与它距离为1的顶点,找到V3,放到数组中,按照顺序,是不是该找V2了,然后找与V2距离为1的点,找到V4和V5,那么这回数组中就为 { V3,V4,V5 },同理,再开始找V3,显然没有顶点了,然后按顺序找V4,V5.这就是大概流程,就是先取出一个,找到相关顶点再放到数组中,按顺序找下一个,再取,再放。其实这个思想是代码实现的思想,那么上面说的好像是一层同时遍历是算法的思想。
其实总结一下就是你找到一个点,找与它相连的几个点,放到数组里,然后依次找这几个点相关的点,再次放到数组里,然后在去找上一次找出的点的相关点放进去,好像是一层一层的。
那么这里就用了队列做辅助工具,如果不理解队列结构创建和使用的看下面文章
数据结构-----------队列(最通俗易懂的文章)
那么我们用一张图来表示队列存储的流程:
那么要用队列数据结构做工具,肯定有队列数据结构的创建和使用:
typedef struct //队列数据结构创建,其实就是定义一个结构体,在结构体数组中存储数据
{
int *base; //定义一个数组指针
int head; //队列俩索引,不懂看上面文章(和栈中的指针作用一样)
int tail;
}Queue;
int init(Queue &Q) //初始化队列函数
{
Q.base=(int*)malloc(MAX*sizeof(int)); //为数组指针开辟空间,因为队列其实就是在一个结构体里的数组中存储读取数据
if(!Q.base) return 0; //基本操作,当分配空间后,如果分配失败,那么返回0
Q.head=Q.tail=0; //初始化,让两个索引指向相同的区域
}
//定义入队列的函数
int EnQueue(Queue &Q,int e) //e表示存储的数据,这里假设队列中的数组是int类型,存储int类型的数据
{
if(Q.tail==MAX-1) return 0; //每当插入数据的时候,首先看所插入对象是否满了,当队尾已经到达最大值,返回0
Q.base[Q.tail]=e; //从队尾所指向的区域存储数据,因为你是往数组里面存储
Q.tail++; //存储后,tail索引上移
}
//出队列函}
int GetQueue(Queue &Q)
{
if(Q.head==Q.tail) return 0; //当获取数据时候,第一步肯定是看该数据结构是否为空,如上图所示,当head和tail所指向相同区域后说明该数据结构为空
int e;
e=Q.base[Q.head]; //从head所指向的区域获得值
Q.head++; //获取值后,然后head索引上移
return e; //返回值
}
那么队列数据结构创建好后,并且也理解了广度遍历算法那么代码是如何实现的:
void BFSTraverse(Graph &G)
{
Queue Q; //创建队列,因为广度遍历要用
initQueue(Q); //初始化队列
for(int v=0;v<G.vexnum;v++) visited[v]=false; //这个和深度遍历一样,你要遍历图,首先对图中所有顶点用bool数组进行初始化,表示都还未遍历,当然该数组要用全局变量定义原因上面有
for(int v=0;v<G.vexnum;v++) //和深度遍历一样作用,可能图中有些点之间不连通,可能会遗忘遍历
//所以要对所有顶点最后进行一次审查,但是如果联通,只要从一个顶点进去了就一连串可以遍历出来
{
if(visited[v]==false) //开始遍历
{
visited[v]=true; //表示该点已经遍历
printf("%s",G.vex[v]); //对该点数据处理,这里就用输出语句代替复杂的处理语句
EnQueue(Q,v); //将该点入队列,每个点是先遍历再入队列
while(Q.head!=Q.tail) //开始广度遍历精髓,当队列不为空时候表示还没有遍历完,可以看上图理解,当队列为空时候,说明已经遍历完
{
int u=GetQueue(Q); //弹出队列中的第一个点,就是要找子顶点的点,看上图,队列中存储的是顶点在数组中的位置
for(int w=findFirst(G,u);w>0;w=findNext(G,u,w)) //开始找其子顶点,和深度一样(调用的俩函数和深度一样,不重复写定义了)
{
visited[w]=true; //遍历子顶点
printf("%s",G.vex[w]); //对子顶点数据处理
EnQueue(Q,w); //将子顶点入队列,看上图演示
}
}
}
}
}
那么对一开始的图进行实例:
下面就为输出的结果,符合广度遍历,但是可能大家觉得有点偶然性,那么再举个例子:
这个是从网上随便找的例子,根据广度遍历,起始点为V0,根据广度遍历点的顺序大家可以先想一下,很简单,首先遍历的点肯定为V1 V2 V5,然后就是 V3 V4
那么就符合了广度遍历。
最近发现了一个国外挺牛的网站,将所有数据结构都实现了可视化。你可以调节演示速度,有各种算法演示,比如什么查找之类,红黑树,什么都有。可能就是全英文,看不懂的可以用浏览器的翻译软件。地址如下: