实验内容:图的两种遍历方法及对应的生成树。
自己编写源程序,把图的深度优先遍历、广度优先遍历改为输出深度优先生成树、广度优先生成树。
实验说明:
(1)读懂教师给定的程序或课本的程序;
(2)输入图;
(3)把显示遍历序列改为显示深度优先、广度优先生成树。
1.图的定义声明及基本运算算法
#include<iostream>
#include<iomanip>
using namespace std;
#define MAXV 100//图最大结点个数
#define INF 32767
#define MaxSize 100//队列最大结点个数
typedef char InfoType;
//以下定义邻接表类型
typedef struct ANode {
int adjvex;//该边的邻接点编号
struct ANode* nextarc;//指向下一条边的指针
int weight;//该边的相关信息
}ArcNode;//边界点类型
typedef struct Vnode {
InfoType info;//顶点的其他类型
ArcNode* firstarc;//指向第一条边
}VNode;//邻接表的头结点类型
typedef struct {
VNode adjlist[MAXV];//邻接表的节点数组
int n, e;//顶点数n和边数e
}AdjGraph;//完整的邻接表类型
void CreateAdj(AdjGraph*& G, int A[MAXV][MAXV], int n,int e) {//创建图的邻接表
int i, j;
ArcNode* p;
G = new AdjGraph;
for (i = 0; i < n; i++) {
G->adjlist[i].firstarc = NULL;//邻接表头指针置空
}
for (i = 0; i < n; i++) {
for (j = n - 1; j >= 0; j--)
if (A[i][j] != 0 && A[i][j] != INF) {//检查两点之间是否存在边
p = new ArcNode;
p->adjvex = j;
p->weight = A[i][j];
p->nextarc = G->adjlist[i].firstarc;//头插法
G->adjlist[i].firstarc = p;
}
}
G->n = n;
G->e = e;//图G的边数和结点数
}
void DispAdj(AdjGraph* G) {//输出图
int i;
ArcNode* p;
for (i = 0; i < G->n; i++) {
p = G->adjlist[i].firstarc;
cout << setw(3) << i;
while (p != NULL) {
cout << setw(3) << p->adjvex << "[" << p->weight << "]->";
p = p->nextarc;
}
printf("\n");
}
}
2.队列的基本算法
typedef struct {//队列的声明,用于广度有先遍历
int data[MaxSize];
int front, rear;
}SqQueue;
void InitQueue(SqQueue*& q) {//初始化队列
q = new SqQueue;
q->front = q->rear = 0;
}
bool QueueEmpty(SqQueue* q) {//判断队列是否为空
return (q->front == q->rear);
}
void enQueue(SqQueue* q, int e) {//进队
if ((q->rear+1)%MaxSize == q->front)
return;
q->rear=(q->rear+1)%MaxSize;
q->data[q->rear] = e;
}
void deQueue(SqQueue* q, int& e) {//出队
if (q->rear == q->front)
return;
q->front=(q->front+1)%MaxSize;
e = q->data[q->front];
}
3.深度优先遍历生成树
当沿着图中的一条路经访问过某一顶点后,可能还沿着另一条路回到顶点,为了避免同一顶点被重复访问,需要记住每个被访问的顶点。在访问算法中,可设置一个访问标记数组visited,当顶点i被访问过时,将数组中的元素visited[i]置为一,否则置为零。
从图中的某个初始点v出发,首先初始点v,然后选择一个与顶点v相邻且没被访问过的顶点w,以w为初始顶点,在从它出发进行深度优先遍历,直到图中与顶点v邻接的所有顶点都被访问过为止,要用递归的方式解决。因为是用递归方式解决,visited需要被设置为全局数组。
DFS算法的时间复杂度为O(n+e),n和e分别为图的顶点和边的个数。
int visited[MAXV] = { 0 };//全局数组
void DFS(AdjGraph* G, int v) {//深度优先遍历
ArcNode* p;
visited[v] = 1;//为1时,表示已访问过
p = G->adjlist[v].firstarc;//p指向v的第一个邻接点
while (p != NULL) {
if (visited[p->adjvex] == 0) {
cout << "(" << v << "," << p->adjvex << ")";
DFS(G, p->adjvex);
}
p = p->nextarc;
}
}
4.广度优先遍历生成树
首先访问初始点v,接着访问顶点v的所有未被访问过的邻接点,然后按照其次序访问每一个顶点的所有未被访问过的邻接点,直至所有与v有路径相通的顶点都被访问为止。
BFS算法的时间复杂度为O(n+e),n和e分别为图的顶点和边的个数。
void BFS(AdjGraph* G, int v) {//广度优先遍历
int w, i;
ArcNode* p;
SqQueue* qu;//环形队列指针
InitQueue(qu);
int visited[MAXV];
for (i = 0; i < G->n; i++) {
visited[i]=0;
}
visited[v] = 1;//设置已访问标记
enQueue(qu, v);
while (!QueueEmpty(qu)) {//队不空时循环
deQueue(qu, w);//出队顶点w
p = G->adjlist[w].firstarc;
while (p != NULL) {//产找w的所有邻接点
if (visited[p->adjvex] == 0) {
cout << "(" << w << "," << p->adjvex<< ")";
visited[p->adjvex] = 1;//置访问标记
enQueue(qu, p->adjvex);//该顶点进队
}
p = p->nextarc;//下一个邻接点
}
}
cout << endl;
}
5.主函数
int main() {
AdjGraph* G;
int A[MAXV][MAXV] = { { 0,1,0,1,1 },{ 1,0,1,1,0 },{0,1,0,1,1},{1,1,1,0,1},{1,0,1,1,0}};
int n = 5, e = 8;
CreateAdj(G, A, n, e);
cout << "图G的邻接表:\n";
DispAdj(G);
int v = 2;
cout << "深度优先生成树:" << endl;
DFS(G, v);
cout << endl;
cout << "广度优先生成树:" << endl;
BFS(G, v);
}
6.执行结果
实验总结
本次实验通过深度优先生成树和广度优先生成树的实现,可以建立起基于图的树结构,便于对图的结构进行理解和处理。同时也可以让我们更好地理解深度优先遍历和广度优先遍历算法的实现过程和原理。在编写代码的过程中,需要注意记录节点间的连接关系,并根据具体情况选择合适的。但是该代码生成的树并未能用树的存储结构来存储,只是简单的用数组输出了各节点之间的关系,还有优化空间,可以用孩子链存储结构来存储。