由于本人没学过离散数学,在这里只能班门弄斧一下了:
一幅图(graph)G = (V,E)是由顶点集V(vertex)和边集E(egge)组成的。每一条边就是一个点对(v,w),其中v,w∈V。如果点对是有序的,那么图就是有向的,否则成为无向图。如果两个顶点(v1,v2)∈E,那么它们就是相关联的,它们互为邻接点。一个点的度数,指的是与这个点相关联的顶点的个数。对于有向图,还分为入度和出度。如果有一点序列(v1,v2,……vn),如果其中相邻的两个(vi,vi+1)∈E,则它们构成路径。如果v1=vn,则称为环或者回路。如果能从点v1沿着路径到v2,那就称这两点是联通的。如果图中的任意两点都是联通的,那么就称为连通图。如果有令一幅图G'=(V',E'),且V'∈V,E'∈E,则称G'是G的子图。特别的如果两幅图的顶点完全相同,只是第二幅图的边集是第一幅的子集,则第二幅图称为第一幅图的生成子图。
图的基本概念先说这么多吧,其实画出来以后这些概念都很明了的。
那么这种复杂的数据结构,即有点,又有边,该如何表示呢?人们想出了有很多种表示法:邻接矩阵,邻接表等等。这里只介绍最简单的邻接矩阵:就是把图中的n个顶点写成一个n*n的矩阵,如果两个顶点是关联的,那么矩阵的值为1,否者为0。
#include <stdio.h>
#include <malloc.h>
//深度优先搜索需要使用栈
#include"stack.h"
//广度优先遍历需要使用队列
#include "queue.h"
#define MAX 8
//邻接矩阵
typedef int** adjacentMatrix;
//图的数据结构
typedef struct graph
{
//邻接矩阵
adjacentMatrix matrix;
//顶点向量
char* vextex;
//顶点向量个数
int vextexNum;
//边的个数
int arcNum;
}Graph;
//图的基本操作
//初始化图
Graph initGraph(int );
//销毁图
void destroyGraph(Graph* );
//增加一条边
bool addArc(Graph* ,char ,char );
//删除一条边
bool deleteArc(Graph* ,char ,char );
//显示图
void showGraph(Graph* );
//图的遍历:
//深度优先搜索的递归实现
void do_DFS(Graph* ,int );
void DFS(Graph* );
//深度优先搜索的栈实现
int* findAdjacentVertex(Graph* ,int ,int *);
void DFS_byStack(Graph*,char);
//广度优先搜索的队列实现
void BFS_byQueue(Graph*,char);
一种数据结构,最重要的操作就是对他遍历。对于图的遍历,有两种常用的方法:深度优先遍历和广度优先遍历。深度优先遍历类似于对二叉树的先序遍历;广度优先遍历类似按层遍历树。
广度优先遍历的基本步骤如下:
1.将初始节点入队
2.当队列不为空时,出队;出队的节点放在vx中。
3.找到与vx相邻且没有被访问过的节点,将它们入队。
4.转2.
注意在每次出队以后,就可以进行相应的操作了,比如打印。
深度优先遍历的基本步骤如下:
1.将起始节点压栈
2.如果栈不为空,弹栈;弹出的节点放在vx中。
3.找到与vx相邻且没被访问过的节点,将它们压栈。
4.转2.
下面给出相应的代码:
#include "graph.h"
//图的基本操作
//初始化图
Graph initGraph(int n)
{
Graph g;
g.vextexNum = n;
g.arcNum = 0;
g.matrix = (int**)malloc(sizeof(int*) * n);
for(int i = 0; i < n;++i)
g.matrix[i] = (int*)malloc(sizeof(int) * n);
for(int i = 0;i < n;++i)
for(int j = 0; j < n;++j)
g.matrix[i][j] = 0;
g.vextex = (char*)malloc(sizeof(char) * n);
char a = 'A';
for(int i = 0; i < n; ++i)
g.vextex[i] = a+i;
return g;
}
//销毁图
void destroyGraph(Graph* g)
{
free(g->vextex);
g->vextex = NULL;
for(int i = 0; i < g->vextexNum;++i)
free(g->matrix[i]);
free(g->matrix);
g->matrix = NULL;
g->arcNum = -1;
g->vextexNum = -1;
}
//增加一条边
bool addArc(Graph* g,char vex1,char vex2)
{
int m = vex1-'A';
int n = vex2-'A';
if(m < 0 || m > g->vextexNum || n < 0 || n > g->vextexNum )
{
printf("2 vertexes must in the graph\n");
return false;
}
else
{
g->matrix[m][n] = 1;
g->matrix[n][m] = 1;
++g->arcNum;
return true;
}
}
//删除一条边
bool deleteArc(Graph* g,char vex1,char vex2)
{
int m = vex1-'A';
int n = vex2-'A';
if(0 == g->matrix[m][n])
{
printf("this arc does not exsit!\n");
return false;
}
else
{
g->matrix[m][n] = 0;
g->matrix[n][m] = 0;
g->arcNum--;
return true;
}
}
//显示图
void showGraph(Graph* g)
{
printf(" ");
for(int i = 0; i < g->vextexNum;++i)
printf("%c ",g->vextex[i]);
for(int i = 0; i < g->vextexNum;++i)
{
printf("\n");
printf("%c ",g->vextex[i]);
for(int j = 0;j < g->vextexNum;++j)
printf("%d ",g->matrix[i][j]);
}
printf("\n");
}
//图的遍历:
//深度优先搜索的递归实现
//需要使用全局变量记录某个顶点是否被访问过
bool visited_DFS[MAX];
void DFS(Graph* g)
{
printf("使用递归深度优先遍历:\n依次访问节点:");
//访问开始前,所有节点均未被访问过
for(int i = 0;i < g->vextexNum;++i)
visited_DFS[i] = false;
//对于每个节点,如果它未被访问,则调用深度优先搜索
for(int i = 0; i < g->vextexNum;++i)
{
if(false == visited_DFS[i])
do_DFS(g,i);
}
printf("\n");
}
void do_DFS(Graph* g,int i)
{
visited_DFS[i] = true;
printf("%c ",g->vextex[i]);
//依次搜索i的邻接点
for(int j = 0; j < g->vextexNum;++j)
{
//如果j是i的邻接点,且未被访问过,则对j调用深度优先搜索
if(visited_DFS[j] == false && 1 == g->matrix[i][j])
do_DFS(g,j);
}
}
void DFS_byStack(Graph* g,char Start)
{
int start = Start-'A';
printf("使用队列进行深度优先遍历:\n依次访问节点:");
//建立数组来标记节点是否被访问过
bool *is_visited = (bool*)malloc(sizeof(bool) * g->vextexNum);
for(int i = 0;i < g->vextexNum;++i)
is_visited[i] = false;
//建立一个栈
Stack s;
s = initStack(g->vextexNum);
push(&s,start);
is_visited[start] = true;
while(!is_empty(&s))
{
int vertex;
pop(&s,&vertex);
int adjacentNumber;
int* adjacentVertex = findAdjacentVertex(g,vertex,&adjacentNumber);
for(int i = 0;i < adjacentNumber;++i)
{
if(false == is_visited[adjacentVertex[i]])
{
push(&s,adjacentVertex[i]);
is_visited[adjacentVertex[i]] = true;
}
}
printf("%c ",g->vextex[vertex]);
}
printf("\n");
destroyStack(&s);
free(is_visited);
}
//寻找与vex相邻的节点,通过n带出结点个数,返回这些节点组成的数组
int* findAdjacentVertex(Graph* g,int vex,int *n)
{
//计数器
int cnt = 0;
int* adj = (int*)malloc(sizeof(int) * g->vextexNum);
for(int i = 0;i < g->vextexNum;++i)
{
if(1 == g->matrix[vex][i])
adj[cnt++] = i;
}
*n = cnt;
return adj;
}
void BFS_byQueue(Graph *g,char Start)
{
int start = Start-'A';
printf("使用栈进行广度优先遍历:\n依次访问节点:");
//建立数组来标记节点是否被访问过
bool *is_visited = (bool*)malloc(sizeof(bool) * g->vextexNum);
for(int i = 0;i < g->vextexNum;++i)
is_visited[i] = false;
Queue q;
q = initQueue(g->vextexNum);
enqueue(&q,&start);
is_visited[start] = true;
while(!is_empty(&q))
{
int vertex;
dequeue(&q,&vertex);
int adjacentNumber;
int* adjacentVertex = findAdjacentVertex(g,vertex,&adjacentNumber);
for(int i = 0; i < adjacentNumber;++i)
{
if(false == is_visited[adjacentVertex[i]] )
{
enqueue(&q,&adjacentVertex[i]);
is_visited[adjacentVertex[i]] = true;
}
}
printf("%c ",g->vextex[vertex]);
}
free(is_visited);
destroyQueue(&q);
}
注意到,图的深度优先遍历既可以使用栈来实现,也可以使用对归来实现。这与我们之前的认识是相一致的。
程序中使用的栈和队列的代码就不往出贴了,之前的博客都介绍过。主要区别在于这次是通过函数的返回值来获得一个栈或者队列,以前是把指向它们的指针传到函数里。但是二者的思想是大同小异的。