数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。
目录
图的遍历
和树的遍历类似,图的遍历也是从图中某一顶点出发,按照某种方法对图中所有顶点进行访问而且仅访问一次。图的遍历算法是求解图的连通性问题、拓扑排序和关键路径等算法的基础。
然而,图的遍历要比树的遍历复杂得多。因为图的任一顶点都可能和其余顶点相邻接,所以在访问了某个顶点之后,可能沿着某条路径搜索之后,又回到该顶点上。例如图中所示的G2,由于图中存在回路,因此在访问了V1、V2、V3、V4之后,沿着边<V4,V1>又可访问到V1。为了避免同一顶点被访问多次,在遍历图的过程中,必须记下每个已访问过的顶点。为此,设一个辅助数组visited[n],其初始值置为“false”或者0,一旦访问了顶点Vi,便置visited[i]为“true”或者1.
根据搜索路径的方向,通常有两条遍历图的路径:深度优先搜索和广度优先搜索。他们对无向图和有向图都适用。
1.深度优先搜索
1.深度优先搜索遍历的过程
深度优先搜索遍历类似于树的先序遍历,是树的先序遍历的推广。
对于一个连通图,深度优先搜索遍历的过程如下。
(1)从图中某个顶点v出发,访问v。
(2)找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复此步骤,直至刚访问过的顶点没有未被访问的邻接点为止。
(3)返沪前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点。
(4)重复步骤(2)和步骤(3),直至图中所有顶点都被访问过,搜索结束 。
(1)从顶点V1出发,访问V1.
(2)在访问了顶点V1之后,选择第一个未被访问的邻接点V2,访问V2。以V2为新顶点,重复此步骤,访问V4,V8,V5.在访问了V5之后,由于V5的邻接点都已经被访问,此步骤结束。
(3)搜索从V5回到V8,由于同样的理由,搜索继续回到V4、V2、直至V1,此时由于V1的另一个邻接点未被访问,则搜索又从V1到V3,再继续进行下去。由此,得到的顶点访问序列为:
V1->V2->V4->V8->V5->V3->V6->V7
所有顶点加上标有实箭头的边,构成一棵以V1为根的树,称之为深度优先生成树。
2.深度优先搜索遍历的算法实现
接下来我们选用无向图的深度优先搜索遍历来举例说明,代码段选用C++进行讲解,为了更好的迎合教材,我们使用大量的替换符来替换数据类型。以下为头文件及替换符部分。
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
#define MaxInt 32767 //表示极大值,即∞
#define MVNum 100 //最大顶点数
typedef int VerTexType;//假设顶点的数据类型为整型
typedef int ArcType; //假设边的权值类型为整型
然后是创建表结构体部分,创建一个顶点表以及邻接矩阵数组,和点数边数。
typedef struct
{
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的当前点数和边数
int visited[MVNum];
}AMGraph;
因为我们在图的输入部分中需要用到查找位置函数,所以提前对其进行定义声明。
Status LocateVex(AMGraph G,VerTexType v) //查询顶点v在图G中的下标位置
{
for(int i=0;i<G.vexnum;i++){
if(G.vexs[i]==v){
return i;
}
}
}
紧接着是图的输入部分,由于是无向图的搜索遍历,所以要双向插入,也就是需要插入两次。
Status CreateUDG(AMGraph *G){
scanf("%d",&G->vexnum);
scanf("%d",&G->arcnum);
for(int i=0;i<G->vexnum;i++){
scanf("%d",&G->vexs[i]);//输入点的信息
}
int v1,v2;
int m,n;
for(int j=0;j<G->arcnum;j++){
scanf("%d",&v1);//输入一条边依附的顶点
scanf("%d",&v2);
int m=LocateVex(*G,v1);
int n=LocateVex(*G,v2);
G->arcs[m][n]=1;
G->arcs[n][m]=1;
}
for(int i=0;i<G->vexnum;i++){
G->visited[i]=0;
}
return OK;
}
然后是图的输出部分。简单套用一个for循环对二维数组进行输出。
Status PrintAMGraph(AMGraph G){
for(int i=0;i<G.vexnum;i++){
for(int j=0;j<G.vexnum;j++){
printf("%d ",G.arcs[i][j]);
}
printf("\n");
}
return OK;
}
再然后就是核心的深度优先遍历算法部分。首先判断是否为空表,然后对顶点进行标记。
void DFSTraverse(AMGraph *G,int v)
{
int n=G->vexnum;//顶点数目
if(v<0||v>=n) return ;
printf("%d ",G->vexs[v]);//输出顶点v
G->visited[v]=1;//被访问过
for(int j=0;j<n;j++)
if(G->visited[j]==0&&G->arcs[v][j]==1)//没被访问过且存在边(v,j)
DFSTraverse(G,j);
}
最后为主函数部分。
int main()
{
AMGraph G;
//调用利用邻接矩阵创建无向图的函数CreateUDG
CreateUDG(&G);
//调用输出邻接矩阵的函数PrintAMGraph
PrintAMGraph(G);
DFSTraverse(&G,0);
return 0;
}
2.广度优先搜索
1.广度优先搜索遍历的过程
广度优先搜索遍历类似于树的按层次遍历的过程。
广度优先搜索遍历的过程如下。
(1)从图中某个顶点V出发,访问v。
(2)依次访问V的各个未曾访问过的邻接点。
(3)分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤(3),直至图中所有已被访问的顶点的邻接点都被访问到。
例如,对下图进行广度优先搜索遍历的具体过程如下。
(1)从顶点V1出发,访问V1。
(2)依次访问V1的各个未曾访问过的邻接点V2和V3.
(3)依次访问V2的邻接点V4和V5,以及V3的邻接点V6和V7,最后访问V4的邻接点V8.由于这些顶点的邻接点均已经被访问,并且图中所有顶点都被访问,由此完成了图的遍历。得到的顶点访问序列为:
V1->V2->V3 ->V4->V5->V6->V6->7->V8
所有顶点加上标有实箭头的边,构成一棵以V1为根的树,称为广度优先生成树。
2.广度优先搜索遍历的算法实现
接下来我们选用无向图的广度优先搜索遍历来举例说明,代码段选用C++进行讲解,为了更好的迎合教材,我们使用大量的替换符来替换数据类型。以下为头文件及替换符部分。
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
#define MaxInt 32767 //表示极大值,即∞
#define MVNum 100 //最大顶点数
typedef int VerTexType;//假设顶点的数据类型为整型
typedef int ArcType; //假设边的权值类型为整型
#define MAXSIZE 100//循环队列
typedef int QElemType;
然后是创建表结构体部分,创建一个顶点表以及邻接矩阵数组,和点数边数。
typedef struct {
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum, arcnum; //图的当前点数和边数
int visited[MVNum];
} AMGraph;
由于广度优先搜索遍历需要用到栈,所以需要对栈进行声明以及一系列操作,以下为栈的代码部分,其中包括栈的声明,栈的入队函数,销毁函数,出队函数,获取栈顶元素函数,测量深度函数,以及栈的打印输出函数。
typedef struct {
QElemType *base;
int front;
int rear;
} SqQueue;
Status InitQueue_Sq(SqQueue *Q) {
Q->base = (QElemType*)malloc(sizeof(QElemType) * MAXSIZE);
if (!Q->base) {
exit(OVERFLOW);
}
Q->front = Q->rear = 0;
return OK;
}
void DestroyQueue_Sq(SqQueue *Q) {
Q->base = 0;
}
//此处定义入队函数EnQueue_Sq
Status EnQueue_Sq(SqQueue *Q, QElemType e) {
if ((Q->rear + 1) % MAXSIZE == Q->front) {
return ERROR;
}
Q->base[Q->rear] = e;
Q->rear = (Q->rear + 1) % MAXSIZE;
return OK;
}
Status GetHead_Sq(SqQueue *Q) {
if (Q->front == Q->rear) {
return ERROR;
}
return Q->base[Q->front];
}
int DeQueue_Sq(SqQueue *Q) {
if (Q->front == Q->rear) {
return ERROR;//队空
}
int e = Q->base[Q->front];
Q->front = (Q->front + 1) % MAXSIZE;
return e;
}
Status QueueLength_Sq(SqQueue *Q) {
return (Q->rear - Q->front + MAXSIZE ) % MAXSIZE;
}
void StackQueue_Sq(SqQueue *Q) {
SqQueue q;
q = *Q;
while (q.front != q.rear) {
printf("%d ", q.base[q.front]);
q.front++;
}
}
然后就是无向图的基本创建函数。
Status LocateVex(AMGraph G, VerTexType v) { //查询顶点v在图G中的下标位置
for (int i = 0; i < G.vexnum; i++) {
if (G.vexs[i] == v) {
return i;
}
}
}
//此处定义无向图的创建
Status CreateUDG(AMGraph *G) {
scanf("%d", &G->vexnum);
scanf("%d", &G->arcnum);
for (int i = 0; i < G->vexnum; i++) {
scanf("%d", &G->vexs[i]); //输入点的信息
}
int v1, v2;
int m, n;
for (int j = 0; j < G->arcnum; j++) {
scanf("%d", &v1); //输入一条边依附的顶点
scanf("%d", &v2);
int m = LocateVex(*G, v1);
int n = LocateVex(*G, v2);
G->arcs[m][n] = 1;
G->arcs[n][m] = 1;
}
for (int i = 0; i < G->vexnum; i++) {
G->visited[i] = 0;
}
return OK;
}
然后是无向图的输出部分其中还包括了,头元素和相邻元素。
//此处定义无向图的邻接矩阵的输出
Status PrintAMGraph(AMGraph G) {
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
printf("%d ", G.arcs[i][j]);
}
printf("\n");
}
return OK;
}
int FirstNeighbor(AMGraph *G, int v) {
for (int j = 0; j < G->vexnum; j++) {
if ( G->visited[j] == 0 &&G->arcs[v][j] == 1) {
return j;
}
}
return -1;
}
int NextNeighbor(AMGraph *G, int v,int w)
{
for (int j = w; j < G->vexnum; j++) {
if (G->visited[j] == 0 && G->arcs[v][j] == 1) {
return j;
}
}
return -1;
}
然后就是广度优先搜索遍历函数段和输出函数段。
//广度优先遍历
void BFSTraverse(AMGraph *G, int v, SqQueue *Q) { //从顶点v出发,广度优先遍历图G
printf("%d ", G->vexs[v]); //输出顶点v
G->visited[v] = 1; //对v做已访问标记
EnQueue_Sq(Q, v);//入队
while (QueueLength_Sq(Q) != 0) {
int v1;
v1 = DeQueue_Sq(Q); //顶点v出队列
for (int w = FirstNeighbor(G, v1); w >= 0; w = NextNeighbor(G, v1, w)) //检测v所有邻接点
if (G->visited[w]==0) { //w为v的尚未访问的邻接顶点
printf("%d ", G->vexs[w]); //访问顶点w
G->visited[w] = 1; //对w做已访问标记
EnQueue_Sq(Q, w); //顶点w入队列
}
}
}
void DFSTraverse(AMGraph *G, int v) {
int n = G->vexnum; //顶点数目
if (v < 0 || v >= n) return ;
printf("%d ", G->vexs[v]); //输出顶点v
G->visited[v] = 1; //被访问过
for (int j = 0; j < n; j++) {
if (G->visited[j] == 0 && G->arcs[v][j] == 1) { //没被访问过且存在边(v,j)
DFSTraverse(G, j);
}
}
}
最后的主函数部分。
int main() {
AMGraph G;
SqQueue Q;
InitQueue_Sq(&Q);
//调用利用邻接矩阵创建无向图的函数CreateUDG
CreateUDG(&G);
//调用输出邻接矩阵的函数PrintAMGraph
PrintAMGraph(G);
BFSTraverse(&G, 0, &Q);
return 0;
}
3.小总结
本次内容主要了讲解了数据结构中的一些基础知识点,主要内容顺序表的有关知识本篇内容都为数据结构的基本思想,若想更深的理解以及体会,还请大家在日常学习中多多努力,希望大家学有所成。