一、实验要求
设计图(网)的邻接矩阵,编写算法实现下列问题的求解。
1.打印出图(网)的两种遍历序。
实验测试数据基本要求:
第一组数据: udg8.grp
第二组数据: udg115.grp
第三组数据: dg6.grp
2.求给定图中的边(或弧)的数目。 实验测试数据基本要求:
第一组数据: udg8.grp
第二组数据: udg115.grp
3.对给定的图G及出发点v0,设计算法从V0出发深度优先遍历图G,并构造出相应的生成树或生成森林。
实验测试数据基本要求:
第一组数据: udg8.grp
第二组数据: dg6.grp
第三组数据: udg114.grp
4.对给定的图G及出发点v0,设计算法从V0出发广度优先遍历图G,并构造出相应的生成树或生成森林。
第一组数据: udg8.grp
第二组数据: dg6.grp
第三组数据: udg114.grp
5.实现Prim算法,求解下列给定图G的最小生成树。 实验测试数据基本要求:第一组数据: udn6.grp
第二组数据: udn8.grp
6.实现Kruskal算法,求解下列给定图G的最小生成树。 实验测试数据基本要求:
第一组数据: udn6.grp
第二组数据: udn8.grp
7.实现Dijkstra算法,求解下列给定图G指定顶点到其余顶点之间的最短路径。 实验测试数据基本要求
第一组数据: udn8.grp
第二组数据: dn8.grp
8.实现Floyd算法,求解下列给定图G各顶点之间的最短路径。 实验测试数据基本要求:
第一组数据: udn8.grp
第二组数据: dn8.grp
9.设计算法求解下列给定图G的拓扑序列。 实验测试数据基本要求:
第一组数据: dn8.grp
第二组数据: kPath101.grp
10.设计算法求解下列给定AOE网的关键路径。 实验测试数据基本要求:
第一组数据: dn8.grp
第二组数据: kPath101.grp
二、数据结构设计
//图的邻接矩阵结构:
#define MaxVerNum 100 //定义最大顶点个数
typedef char elementType; //定义图中顶点的数据类型
typedef int cellType; //定义邻接矩阵中元素的数据类型
typedef enum{UDG, UDN, DG, DN} GraphKind; //枚举图的类型
typedef struct GraphAdjMatrix
{
elementType Data[MaxVerNum+1]; //顶点数组,存放顶点元素的值,Data[0]单元不用
cellType AdjMatrix[MaxVerNum+1][MaxVerNum+1]; //邻接矩阵,数组下标为0单元不用,从AdjMatrix[1][1]单元开始
int VerNum; //顶点数
int ArcNum; //弧(边)数
GraphKind gKind; //图的类型:0-无向图;1-无向网;2-有向图;3-有向网
} Graph; //图的类型名
三、算法设计
由于图的深度优先遍历(DFS)、广度优先遍历(BFS)、Prim算法及思想、Kruskal算法及思想、Dijkstra算法及思想、Floyd算法及思想、拓扑序列算法及思想、AOE网的关键路径算法思想书中已给出,这里不再赘述。
- 深度优先遍历生成树算法及思想:修改深度优先遍历树和深度优先遍历森林算法。DFSTree()按照DFS()深度遍历相应递归生成孩子-兄弟表示的二叉树。而DFSForest()则分别处理每棵树,进而转化成森林(二叉链表表示)。算法描述:
//深度优先遍历生成树
void DFSTree(Graph &G,int v,csTree&T)
{
int j,w,first=1; //first标记是否为根节点,初始化为1
csNode *p,*q;
q=T;
visited[v]=1;
w=firstAdj(G,v);
while(w!=0) //还存在邻接点
{
if(!visited[w]&&G.AdjMatrix[v][w]>=1 && G.AdjMatrix[v][w]<INF)
{
p=new csNode;
p->firstChild=NULL;
p->nextSibling=NULL;
p->data=G.Data[w];
if(first) //(生成第一个孩子)
{
T->firstChild=p;
first=0;
}
else{ //(生成节点的兄弟节点)
q->nextSibling=p;
}
q=p;
DFSTree(G,w,q); //递归处理每个节点
}
w=nextAdj(G,v,w);
}
}
//深度优先遍历生成森林
csTree DFSForest(Graph &G,csTree &T,int v)
{ //v指定生成树或森林的根节点
int i;
csNode *p;
csTree q;
T=NULL;
for(i=1;i<=G.VerNum;i++) //初始化visited数组
{
visited[i]=0;
}
for(v;v>=1;v--) //编号1到v
{
if(!visited[v] )
{
p=new csNode;
p->firstChild=NULL;
p->nextSibling=NULL;
p->data=G.Data[v];
if(!T) //为根节点
{
T=p;
q=T;
}
else{ //生成数根节点的兄弟节点,也就是其他树的根节点
q->nextSibling=p;
}
q=p;
DFSTree(G,v,p);
}
}
for(v+=1;v<=G.VerNum;v++) //编号v到G.vernum
{
if(!visited[v] )
{
p=new csNode;
p->firstChild=NULL;
p->nextSibling=NULL;
p->data=G.Data[v];
if(!T) //为根节点
{
T=p;
q=T;
}
else{ //生成数根节点的兄弟节点,也就是其他树的根节点
q->nextSibling=p;
}
q=p;
DFSTree(G,v,p); //处理每棵树
}
}
return T;
}
- 广度优先遍历生成树算法及思想:修改广度优先遍历树和广度优先遍历森林算法。BFSTree()按照BFS()广度遍历相应分层生成孩子-兄弟表示的二叉树。而BFSForest()则分别处理每棵树,进而转化成森林(二叉链表表示)。
算法描述:
//广度优先遍历生成树
void BFSTree(Graph &G,csTree&T,int v)
{
int i,w;
bool first=true; //标志变量,判断是否生成孩子节点
csNode *t,*q,*p;
queue<int> Q;
if(!T) //生成树的根节点
{
T=new csNode;
T->data=G.Data[v];
visited[v]=true;
T->firstChild=T->nextSibling=NULL;
}
p=T;
Q.push(v);
//对其他层节点操作
while(!Q.empty())
{
v=Q.front(); //获取队头编号
Q.pop(); //并出队
w=firstAdj(G,v);
first=true; //循环i层时需要把first置为true,以便生成T->firstChild
while(w!=0)
{
if(!visited[w]&& G.AdjMatrix[v][w]>=1 && G.AdjMatrix[v][w]<INF)
{
Q.push(w); //第i层节点入队
visited[w]=true; //设置其访问标志
q=new csNode;
q->data=G.Data[w];
q->firstChild=q->nextSibling=NULL;
if(first) //生成孩子节点
{
p->firstChild=q;
first=false;
}
else{ //生成兄弟节点
p->nextSibling=q;
}
p=q;
}
w=nextAdj(G,v,w);
}
}
}
广度优先生成森林与深度优先生成森林代码处理相同,故不再赘述。
四、代码实现
#ifndef _GRPADJMATRIX_H_
#define _GRPADJMATRIX_H_
#include <iostream>
using namespace std;
//************************************************************//
//* 图的邻接矩阵存储的头文件,文件名:grpAdjMatrix.h *//
//* *//
//************************************************************//
#define INF 65535 //定义无穷大
#define MaxVerNum 100 //定义最大顶点个数
//typedef int elementType; //定义图中顶点的数据类型
typedef char elementType; //定义图中顶点的数据类型
typedef int cellType; //定义邻接矩阵中元素的数据类型
//对无权图,1-相邻(有边),0-不相邻(无边)
//对有权图,为边的权值,特别是无穷大。
//枚举图的类型--无向图(UDG),无向网(UDN),有向图(DG),有向网(DN)
typedef enum{UDG, UDN, DG, DN} GraphKind;
bool visited[MaxVerNum+1]; //全局数组,标记顶点是否已经访问,visited[0]单元不用
//****************************************************//
//* 定义邻接矩阵表示的图结构。5个分量组成: *//
//* data[]数组保存图中顶点数据元素 *//
//* AdjMatrix[][]邻接矩阵 *//
//* VerNum顶点个数 *//
//* ArcNum边(弧)条数 *//
//* gKind枚举图的类型 *//
//* 考虑到名称的统一性,图类型名称定义为Graph *//
//****************************************************//
typedef struct GraphAdjMatrix
{
elementType Data[MaxVerNum+1]; //顶点数组,存放顶点元素的值,Data[0]单元不用
cellType AdjMatrix[MaxVerNum+1][MaxVerNum+1]; //邻接矩阵,数组下标为0单元不用,从AdjMatrix[1][1]单元开始
int VerNum; //顶点数
int ArcNum; //弧(边)数
GraphKind gKind; //图的类型:0-无向图;1-无向网;2-有向图;3-有向网
} Graph; //图的类型名
//******************* 访问图中顶点的函数*********************//
//* 函数功能:打印图中顶点元素,并标记为已经访问 *//
//* 入口参数 Graph G,待访问的图;int verID 目标顶点编号 *//
//* 出口参数:无 *//
//* 返 回 值:空 *//
//* 函 数 名:visit(Graph &G, int verID) *//
//***********************************************************//
void visit(Graph &G, int verID)
{ //顶点编号从1开始,数组0单元不用
cout<<G.Data[verID]<<" ";
visited[verID]=true;
}
//******************* 图中查找目标顶点 *********************//
//* 函数功能:给定顶点元素,在图中查找此顶点元素 *//
//* 入口参数 Graph G,待访问的图;elementType v 目标顶点 *//
//* 出口参数:无 *//
//* 返 回 值:int。如果目标顶点存在,返回顶点编号, *//
//* 顶点编号从1开始;否则返回-1 *//
//* 函 数 名:visit(Graph &G, int verID) *//
//***********************************************************//
int LocateVertex(Graph &G, elementType v)
{
for(int i=1;i<=G.VerNum;i++)
{
if( G.Data[i]==v )
return i;
}
return -1;
}
//求顶点v的第一个邻接点
int firstAdj(Graph &G,int v)
{
int w;
for(w=1;w<=G.VerNum;w++)
{
if((G.AdjMatrix[v][w]>=1)&&(G.AdjMatrix[v][w])<INF)
return w; //返回第一个邻接点编号
}
return 0; //未找到邻接点,返回0
}
//求顶点v的位于邻接点w后的下一个邻接点
int nextAdj(Graph &G,int v,int w)
{
int k;
for(k=w+1;k<=G.VerNum;k++)
{
if((G.AdjMatrix[v][k]>=1) && (G.AdjMatrix[v][k])<INF)
return k; //返回v的位于w之后的下一个邻接点k
}
return 0; //不存在下一个邻接点,返回0
}
//******************** 打印图的相关信息 *********************//
//* 函数功能:打印图的相关信息 *//
//* 入口参数:Graph G,待打印的图 *//
//* 出口参数:无 *//
//* 返 回 值:空 *//
//* 函 数 名:GraphPrint(Graph &G) *//
//***********************************************************//
void printGraph(Graph &G)
{
int i=0,j=0;
//打印图的类型
switch(G.gKind)
{
case UDG:
cout<<"图类型:无向图"<<endl;
break;
case UDN:
cout<<"图类型:无向网"<<endl;
break;
case DG:
cout<<"图类型:有向图"<<endl;
break;
case DN:
cout<<"图类型:有向网"<<endl;
break;
default:
cout<<"图类型错误。"<<endl;
break;
}
//打印图的顶点数
cout<<"顶点数:"<<G.VerNum<<endl;
//打印图的边数
cout<<"边 数:"<<G.ArcNum<<endl;
//打印顶点及其编号
cout<<"编 号:";
for(i=1;i<=G.VerNum;i++)
{
cout<<i<<"\t";
}
cout<<endl;
cout<<"顶 点:";
for(i=1;i<=G.VerNum;i++)
{
cout<<G.Data[i]<<"\t";
}
cout<<endl;
//打印邻接矩阵
cout<<"图的邻接矩阵:"<<endl;
for(i=1;i<=G.VerNum;i++)
{
cout<<"\t";
for(j=1;j<=G.VerNum;j++)
{
if((G.gKind==UDN || G.gKind==DN) && G.AdjMatrix[i][j]==INF)
cout<<"INF"<<"\t"; //网,无穷大时,打印“INF”表示
else
cout<<G.AdjMatrix[i][j]<<"\t";
}
cout<<endl;
}
}
#endif // _GRPADJMATRIX_H_
#ifndef _CREATEGRPADJMATRIX_H_
#define _CREATEGRPADJMATRIX_H_
#include "grpAdjMatrix.h"
#include <cstring>
#include <cstdio>
#include <cstdlib>
using namespace std;
void strLTrim(char* str);
//*************************从数据文件创建图**************************//
//* 函数功能:从文本文件创建邻接矩阵表示的图 *//
//* 入口参数 char fileName[],文件名 *//
//* 出口参数: *//
//* 返 回 值:bool,true创建成功;false创建失败 *//
//* 函 数 名:CreateGrpFromFile(char fileName[]) *//
//*******************************************************************//
bool CreateGrpFromFile(char fileName[], Graph &G)
{
FILE* pFile; //定义顺序表的文件指针
char str[1000]; //存放读出一行文本的字符串
char strTemp[10]; //判断是否注释行
cellType eWeight; //边的信息,常为边的权值
GraphKind GrpType; //图类型枚举变量
pFile=fopen(fileName,"r");
if(!pFile)
{
printf("错误:文件%s打开失败。\n",fileName);
return false;
}
while(fgets(str,1000,pFile)!=NULL)
{
//删除字符串左边空格
strLTrim(str);
if (str[0]=='\n') //空行,继续读取下一行
continue;
strncpy(strTemp,str,2);
if(strstr(strTemp,"//")!=NULL) //跳过注释行
continue;
else //非注释行、非空行,跳出循环
break;
}
//循环结束,str中应该已经是文件标识,判断文件格式
if(strstr(str,"Graph")==NULL)
{
printf("错误:打开的文件格式错误!\n");
fclose(pFile); //关闭文件
return false;
}
//读取图的类型,跳过空行
while(fgets(str,1000,pFile)!=NULL)
{
//删除字符串左边空格
strLTrim(str);
if (str[0]=='\n') //空行,继续读取下一行
continue;
strncpy(strTemp,str,2);
if(strstr(strTemp,"//")!=NULL) //注释行,跳过,继续读取下一行
continue;
else //非空行,也非注释行,即图的类型标识
break;
}
//设置图的类型
if(strstr(str,"UDG"))
GrpType=UDG; //无向图
else if(strstr(str,"UDN"))
GrpType=UDN; //无向网
else if(strstr(str,"DG"))
GrpType=DG; //有向图
else if(strstr(str,"DN"))
GrpType=DN; //有向网
else
{
printf("错误:读取图的类型标记失败!\n");
fclose(pFile); //关闭文件
return false;
}
//读取顶点元素,到str。跳过空行
while(fgets(str,1000,pFile)!=NULL)
{
//删除字符串左边空格
strLTrim(str);
if (str[0]=='\n') //空行,继续读取下一行
continue;
strncpy(strTemp,str,2);
if(strstr(strTemp,"//")!=NULL) //注释行,跳过,继续读取下一行
continue;
else //非空行,也非注释行,即图的顶点元素行
break;
}
//顶点数据放入图的顶点数组
char* token=strtok(str," ");
int nNum=1;
while(token!=NULL)
{
G.Data[nNum]=*token; // atoi(token); //顶点数据转换为整数,若为字符则不需转换
token = strtok( NULL, " ");
nNum++;
}
nNum--; //顶点数
//图的邻接矩阵初始化
int nRow=1; //矩阵行下标,从1开始
int nCol=1; //矩阵列下标,从1开始
if(GrpType==UDG || GrpType==DG)
{
for(nRow=1;nRow<=nNum;nRow++)
for(nCol=1;nCol<=nNum;nCol++)
G.AdjMatrix[nRow][nCol]=0;
}
else
{
for(nRow=1;nRow<=nNum;nRow++)
for(nCol=1;nCol<=nNum;nCol++)
G.AdjMatrix[nRow][nCol]=INF; //INF表示无穷大
}
//循环读取边的数据到邻接矩阵
int edgeNum=0; //边的数量
elementType Nf,Ns; //边或弧的2个相邻顶点
while(fgets(str,1000,pFile)!=NULL)
{
//删除字符串左边空格
strLTrim(str);
if (str[0]=='\n') //空行,继续读取下一行
continue;
strncpy(strTemp,str,2);
if(strstr(strTemp,"//")!=NULL) //注释行,跳过,继续读取下一行
continue;
char* token=strtok(str," "); //以空格为分隔符,分割一行数据,写入邻接矩阵
if(token==NULL) //分割为空串,失败退出
{
printf("错误:读取图的边数据失败!\n");
fclose(pFile); //关闭文件
return false;
}
Nf=*token; //获取边的第一个顶点
token = strtok( NULL, " "); //读取下一个子串,即第二个顶点
if(token==NULL) //分割为空串,失败退出
{
printf("错误:读取图的边数据失败!\n");
fclose(pFile); //关闭文件
return false;
}
Ns=*token; //获取边的第二个顶点
//从第一个顶点获取行号
for(nRow=1;nRow<=nNum;nRow++)
{
if(G.Data[nRow]==Nf) //从顶点列表找到第一个顶点的编号
break;
}
//从第二个顶点获取列号
for(nCol=1;nCol<=nNum;nCol++)
{
if(G.Data[nCol]==Ns) //从顶点列表找到第二个顶点的编号
break;
}
//如果为网,读取权值
if(GrpType==UDN || GrpType==DN)
{
token = strtok( NULL, " "); //读取下一个子串,即边的附加信息,常为边的权重
if(token==NULL) //分割为空串,失败退出
{
printf("错误:读取图的边数据失败!\n");
fclose(pFile); //关闭文件
return false;
}
eWeight=atoi(token); //取得边的附加信息
}
if(GrpType==UDN || GrpType==DN) //如果为网,邻接矩阵中对应的边设置权值,否则置为1
G.AdjMatrix[nRow][nCol]=eWeight;
else
G.AdjMatrix[nRow][nCol]=1; //atoi(token); //字符串转为整数
edgeNum++; //边数加1
}
G.VerNum=nNum; //图的顶点数
if(GrpType==UDG || GrpType==UDN)
G.ArcNum=edgeNum / 2; //无向图或网的边数等于统计的数字除2
else
G.ArcNum=edgeNum;
G.gKind=GrpType; //图的类型
fclose(pFile); //关闭文件
return true;
}
//删除字符串、字符数组左边空格
void strLTrim(char* str)
{
int i,j;
int n=0;
n=strlen(str)+1;
for(i=0;i<n;i++)
{
if(str[i]!=' ') //找到左起第一个非空格位置
break;
}
//以第一个非空格字符为手字符移动字符串
for(j=0;j<n;j++)
{
str[j]=str[i];
i++;
}
}
#endif // _CREATEGRPADJMATRIX_H_
#ifndef _TREE_H_
#define _TREE_H_
#include <iostream>
#include <queue>
using namespace std;
//树(森林)的孩子兄弟链表表示
typedef char elementType;
typedef struct csNode
{
elementType data;
struct csNode *firstChild, *nextSibling;
}csNode,*csTree;
//先序遍历森林
void perOrderTraverse(csNode *T)
{
if(T)
{
cout<<T->data<<" "; //访问根节点
perOrderTraverse(T->firstChild); //递归调用先序遍历左子树
perOrderTraverse(T->nextSibling); //递归调用先序遍历右子树
}
}
//后序遍历森林
void postOrderTraverse(csNode *T)
{
if(T)
{
postOrderTraverse(T->firstChild); //递归调用先序遍历左子树
postOrderTraverse(T->nextSibling); //递归调用先序遍历右子树
cout<<T->data<<" "; //访问根节点
}
}
//层次遍历森林
void levelOrderTraverse(csNode *T)
{
queue<csNode *> q;
csNode * u,*n,*p;
if(T==NULL)
{
return;
}
n=T;
while(n)
{
p=n;
q.push(p);
while(!q.empty())
{
p=q.front();
cout<<p->data<<" ";
u=p->firstChild;
while(u)
{
p=u;
q.push(p);
u=u->nextSibling;
}
q.pop();
}
n=n->nextSibling;
}
}
//销毁森林
void destroy(csNode *&T)
{
if(T)
{
destroy(T->firstChild);
destroy(T->nextSibling);
delete T;
}
}
#endif // _TREE_H_
#ifndef _OPERATEGRPADJMATRIX_H_
#define _OPERATEGRPADJMATRIX_H_
#include <iostream>
#include <queue>
#include <stack>
#include "grpAdjMatrix.h"
#include "tree.h"
using namespace std;
//定义边集(prim)
typedef struct minEdgeType{
int v; //边中已选顶点一端
cellType eWeight; //边的权重
} MinEdgeType;
//定义边集(kruskal)
typedef int eInfoType;
typedef struct edgetype{
int vBeign;
int vEnd;
eInfoType eWeight;
}EdgeType;
//操作菜单
void menu()
{
cout<<"*************************************************"<<endl;
cout<<"0退出程序"<<endl;
cout<<"1数据文件创建图"<<endl;
cout<<"2打印出图(网)的两种遍历序"<<endl;
cout<<"3求给定图中的边(或弧)的数目"<<endl;
cout<<"4对给定的图G及出发点v0,设计算法从V0出发深度优先遍历图G,并构造出相应的生成树或生成森林"<<endl;
cout<<"5对给定的图G及出发点v0,设计算法从V0出发广度优先遍历图G,并构造出相应的生成树或生成森林"<<endl;
cout<<"6实现Prim算法,求图G的最小生成树"<<endl;
cout<<"7实现Kruskal算法,求图G的最小生成树"<<endl;
cout<<"8实现Dijkstra算法,求图G指定顶点到其余顶点之间的最短路径"<<endl;
cout<<"9实现Floyd算法,求图G各顶点之间的最短路径"<<endl;
cout<<"10求解图G的拓扑序列"<<endl;
cout<<"11求解AOE网的关键路径"<<endl;
cout<<"*************************************************"<<endl;
}
//连通图深度优先遍历
void DFS(Graph &G,int v)
{
int w;
visit(G,v); //访问定点,并设置其访问标志
w=firstAdj(G,v); //求出v的第一个临结点,返回临结点编号w
while(w!=0) //还存在临结点
{
if(visited[w]==false && G.AdjMatrix[v][w]>=1 && G.AdjMatrix[v][w]<INF)//从来没有访问过的临结点进行深度遍历
DFS(G,w);
w=nextAdj(G,v,w); //取下一个临结点
}
}
//一般图的深度优先遍历
void DFSTraverse(Graph &G,int v)
{
int i;
for(i=1;i<=G.VerNum;i++)
{
visited[i]=false; //初始化访问标志为false
}
for(v;v>=1;v--) //编号1到v
{
if(visited[v]==false) //循环选择未被访问的顶点
DFS(G,v); //每次循环遍历一个连通分量
}
for(v;v<=G.VerNum;v++) //编号v到G.vernum
{
if(visited[v]==false) //循环选择未被访问的顶点
DFS(G,v); //每次循环遍历一个连通分量
}
}
//连通图的广度优先遍历
void BFS(Graph &G,int v)
{
queue<int> Q; //队列存放元素的编号
int w;
visit(G,v); //访问定点,并设置其访问标志
Q.push(v); //节点编号入队
while(!Q.empty())
{
v=Q.front();
Q.pop();
w=firstAdj(G,v);
while(w!=0)
{
if(!visited[w] && G.AdjMatrix[v][w]>=1 && G.AdjMatrix[v][w]<INF)
{
visit(G,w);
Q.push(w);
}
w=nextAdj(G,v,w);
}
}
}
//一般图的广度优先遍历
void BFSTraverse(Graph &G,int v)
{
int i;
for(i=1;i<=G.VerNum;i++)
{
visited[i]=false; //初始化访问标志为false
}
for(v;v>=1;v--) //编号1到v
{
if(visited[v]==false) //循环选择未被访问的顶点
BFS(G,v); //每次循环遍历一个连通分量
}
for(v;v<=G.VerNum;v++) //编号v到G.vernum
{
if(visited[v]==false) //循环选择未被访问的顶点
BFS(G,v); //每次循环遍历一个连通分量
}
}
//深度优先遍历生成树
void DFSTree(Graph &G,int v,csTree&T)
{
int j,w,first=1; //first标记是否为根节点,初始化为1
csNode *p,*q;
q=T;
visited[v]=1;
w=firstAdj(G,v);
while(w!=0) //还存在临结点
{
if(!visited[w] && G.AdjMatrix[v][w]>=1 && G.AdjMatrix[v][w]<INF)
{
p=new csNode;
p->firstChild=NULL;
p->nextSibling=NULL;
p->data=G.Data[w];
if(first) //(生成第一个孩子)
{
T->firstChild=p;
first=0;
}
else{ //(生成节点的兄弟节点)
q->nextSibling=p;
}
q=p;
DFSTree(G,w,q); //递归处理每个节点
}
w=nextAdj(G,v,w);
}
}
//深度优先遍历生成森林
csTree DFSForest(Graph &G,csTree &T,int v)
{
int i;
csNode *p;
csTree q;
T=NULL;
for(i=1;i<=G.VerNum;i++) //初始化visited数组
{
visited[i]=0;
}
for(v;v>=1;v--) //编号1到v
{
if(!visited[v] )
{
p=new csNode;
p->firstChild=NULL;
p->nextSibling=NULL;
p->data=G.Data[v];
if(!T) //为根节点
{
T=p;
q=T;
}
else{ //生成数根节点的兄弟节点,也就是其他树的根节点
q->nextSibling=p;
}
q=p;
DFSTree(G,v,p);
}
}
for(v+=1;v<=G.VerNum;v++) //编号v到G.vernum
{
if(!visited[v] )
{
p=new csNode;
p->firstChild=NULL;
p->nextSibling=NULL;
p->data=G.Data[v];
if(!T) //为根节点
{
T=p;
q=T;
}
else{ //生成数根节点的兄弟节点,也就是其他树的根节点
q->nextSibling=p;
}
q=p;
DFSTree(G,v,p);
}
}
return T;
}
//广度优先遍历生成树
void BFSTree(Graph &G,csTree&T,int v)
{
int i,w;
bool first=true; //标志变量,判断是否生成孩子节点
csNode *t,*q,*p;
queue<int> Q;
if(!T) //生成树的根节点
{
T=new csNode;
T->data=G.Data[v];
visited[v]=true;
T->firstChild=T->nextSibling=NULL;
}
p=T;
Q.push(v);
//对其他层节点操作
while(!Q.empty())
{
v=Q.front(); //获取队头编号
Q.pop(); //并出队
w=firstAdj(G,v);
first=true; //循环i层时需要把first置为true,以便生成T->firstChild
while(w!=0)
{
if(!visited[w]&& G.AdjMatrix[v][w]>=1 && G.AdjMatrix[v][w]<INF)
{
Q.push(w); //第i层节点入队
visited[w]=true; //设置其访问标志
q=new csNode;
q->data=G.Data[w];
q->firstChild=q->nextSibling=NULL;
if(first) //生成孩子节点
{
p->firstChild=q;
first=false;
}
else{ //生成兄弟节点
p->nextSibling=q;
}
p=q;
}
w=nextAdj(G,v,w);
}
}
}
//广度优先遍历生成森林
csTree BFSForest(Graph &G,csTree&T,int v)
{
int i;
csNode *p;
csTree q;
T=NULL;
for(i=1;i<=G.VerNum;i++) //初始化visited数组
{
visited[i]=0;
}
for(v;v>=1;v--) //编号1到v
{
if(!visited[v] )
{
visited[v]=true; //标记已访问
p=new csNode;
p->firstChild=NULL;
p->nextSibling=NULL;
p->data=G.Data[v];
if(!T) //为根节点
{
T=p;
q=T;
}
else{ //生成数根节点的兄弟节点,也就是其他树的根节点
q->nextSibling=p;
}
q=p;
BFSTree(G,p,v);
}
}
for(v+=1;v<=G.VerNum;v++) //编号v到G.vernum
{
if(!visited[v] )
{
visited[v]=true; //标记已访问
p=new csNode;
p->firstChild=NULL;
p->nextSibling=NULL;
p->data=G.Data[v];
if(!T) //为根节点
{
T=p;
q=T;
}
else{ //生成数根节点的兄弟节点,也就是其他树的根节点
q->nextSibling=p;
}
q=p;
BFSTree(G,p,v);
}
}
return T;
}
//判断两个顶点是否有边
bool HasEdge(Graph &G,int vBegin ,int vEnd,int &eWeight)
{
int f=false;
if(G.AdjMatrix[vBegin][vEnd]!=INF)
{
eWeight=G.AdjMatrix[vBegin][vEnd];
f=true;
return f;
}
else{
eWeight=G.AdjMatrix[vBegin][vEnd];
return f;
}
}
//初始化候选边集
void initTE(Graph &G,MinEdgeType TE[],int vID)
{
int i;
int eWeight;
for(i=1;i<=G.VerNum;i++)
{
//初始化数组
if(HasEdge(G,vID,i,eWeight)) // 顶点和i有边
{
TE[i].v=vID;
TE[i].eWeight=eWeight;
}
else{
TE[i].eWeight=INF;
}
}
}
//得到候选边集的最小边
int getMinEdge(Graph &G,MinEdgeType TE[])
{
int eMin=INF;
int i,j=0;
for(i=1;i<=G.VerNum;i++)
{
if(visited[i]==false && TE[i].eWeight<eMin)
{
j=i;
eMin=TE[i].eWeight;
}
}
return j;
}
//当顶点vID被选中变为已选顶点后,更新候选边集
void updateTE(Graph &G,MinEdgeType TE[],int vID)
{
int i,j;
int eWeight;
for(i=1;i<=G.VerNum;i++)
{
if(visited[i]==false)
{
//检查vID与i之间是否有临边
//若其权值更小,则更新(vID,i)的权值
if(HasEdge(G,vID,i,eWeight) && eWeight<TE[i].eWeight)
{
TE[i].v=vID;
TE[i].eWeight=eWeight;
}
}
}
}
//输出最小生成树的边
void printEdge(Graph &G,MinEdgeType TE[],int vID)
{
int i;
for(i=1;i<=G.VerNum;i++)
{
if(i!=vID) //注意TE[vID]是无效数据
{
cout<<"("<<G.Data[i]<<","<<G.Data[TE[i].v]<<")"<<" ";
}
}
}
//prim算法
void prim(Graph &G,int vID)
{
MinEdgeType TE[MaxVerNum+1]; //候选边集
//TE[i]=vID为起点
int i;
int curID; //当前选择编号
for(i=1;i<=G.VerNum;i++)
{
visited[i]=false; //初始化访问标志为false
}
initTE(G,TE,vID);
visited[vID]=true;
for(i=1;i<G.VerNum;i++) //循环选择n-1条边
{
curID=getMinEdge(G,TE);
visited[curID]=true;
updateTE(G,TE,curID); //选择curID后,更新候选边集
}
printEdge(G,TE,vID); //输出数组TE[]
}
//获取边
void getEdge(Graph &G,EdgeType edges[])
{
int i,v; //v是顶点编号
int k=0; //作为边数组下标
for(i=1;i<=G.VerNum;i++)
{
v=firstAdj(G,i); //获取顶点编号i的第一个关联顶点的编号
while(v)
{
edges[k].vBeign=i;
edges[k].vEnd=v;
edges[k].eWeight=G.AdjMatrix[i][v]; //获取权重
v=nextAdj(G,i,v);
k++;
}
}
}
//获取边集edges[]的最小边
EdgeType getMinEdge(Graph &G,EdgeType edges[],int edgeUsed[],int &n)
{
//n返回最小边的下标
EdgeType minEdge;
cellType wMin=INF; //保存最小权值
int i;
int M; //控制循环次数
if(G.gKind==UDG || G.gKind==UDN)
{
M=G.ArcNum*2; //无向网有效边数是原边数2倍
}
else{
M=G.ArcNum; //有向网
}
for(i=0;i<M;i++)
{
if(edgeUsed[i]==false && edges[i].eWeight<wMin)
{
//对未使用且权值最小的边,暂定为最小边
wMin=edges[i].eWeight;
minEdge.eWeight=edges[i].eWeight;
minEdge.vBeign=edges[i].vBeign;
minEdge.vEnd=edges[i].vEnd;
n=i;
}
}
return minEdge; //返回最小边
}
//输出树边
void printTreeEdges(Graph &G,EdgeType treeEdges[])
{
int i;
for(i=0;i<G.VerNum-1;i++)
{
cout<<"("<<G.Data[treeEdges[i].vBeign]<<","<<G.Data[treeEdges[i].vEnd]<<")"<<" ";
}
}
//kruskal算法
void Kruskal(Graph &G)
{
int conVerID[MaxVerNum]; //顶点的连通分量编号数组
EdgeType edges[MaxVerNum*MaxVerNum]; //存放所有边集
EdgeType treeEdges[MaxVerNum]; //存放n-1条树边
int edgeUsed[MaxVerNum*MaxVerNum]; //标记edges[]中的边是否用过,用过得1
EdgeType minEdge;
int i,j;
int k=0;
int conID; //边的终止顶点vEnd的2连通分量编号
int n; //返回最小边的下标
getEdge(G,edges); //获取所有边信息
//初始化可用边标记数组
int M; //循环次数
if(G.gKind==UDG || G.gKind==UDN)
{
M=G.ArcNum*2; //无向网有效边数是原边数2倍
}
else{
M=G.ArcNum; //有向网
}
for(i=0;i<M;i++)
{
edgeUsed[i]=false; //标记所有边可用
}
//初始化连通分量编号
for(i=1;i<=G.VerNum;i++)
{
conVerID[i-1]=i; //注意编号与数组差1
}
//取n-1条边构成生成树(核心代码)
for(i=1;i<G.VerNum;i++)
{
minEdge=getMinEdge(G,edges,edgeUsed,n); //获取最小边
//形成回路
while(conVerID[minEdge.vBeign-1]==conVerID[minEdge.vEnd-1])
{
edgeUsed[n]=1; //标记最小边不可用
minEdge=getMinEdge(G,edges,edgeUsed,n);//继续取下一条最小边
}
//至此取的一条最小可用边
treeEdges[i-1]=minEdge, //加入树边
conID=conVerID[minEdge.vEnd-1]; //取得最小边的终点编号
for(j=1;j<=G.VerNum;j++)
{
if(conVerID[j-1]==conID)
{
conVerID[j-1]=conVerID[minEdge.vBeign-1];
}
}
edgeUsed[n]=true; //标记当前选择的边已用过
}
printTreeEdges(G,treeEdges);
}
//Dijkstra算法
void Dijkstra(Graph &G,int path[],int dist[],int vID)
{
int solved[MaxVerNum]; //标记顶点是否已经求出最短路径
int i,j,v;
cellType minDist; //最短距离
//初始化sloved[]和dist[]数组
for(i=1;i<=G.VerNum;i++)
{
solved[i-1]=0; //标记所有节点均未求解
dist[i-1]=G.AdjMatrix[vID][i];
if(dist[i-1]!=INF)
{
path[i-1]=vID; //第i顶点的前驱节点为vID
}
else{
path[i-1]=-1; //无前驱节点
}
}
solved[vID-1]=1; //标记vID已求解
dist[vID-1]=0; //到自身距离为0
path[vID-1]=-1; //vID为起始顶点,无前驱
//依次找到其他n-1个顶点
for(i=1;i<=G.VerNum;i++)
{
minDist=INF;
//在未求解顶点中寻找距离vID最近的顶点
for(j=1;j<=G.VerNum;j++)
{
if(solved[j-1]==0 && dist[j-1]<minDist)
{
v=j;
minDist=dist[j-1];
}
}
if(minDist==INF)
return;
//输出本次选择的顶点距离
cout<<"选择顶点"<<G.Data[v]<<"--距离:"<<minDist<<endl;
solved[v-1]=1; //顶点已找到最短距离,标记已解顶点
//修改距离
for(j=1;j<=G.VerNum;j++)
{
if(solved[j-1]==0 && (minDist+G.AdjMatrix[v][j]<dist[j-1]))
{
//更新j到顶点vID的最短距离
dist[j-1]=minDist+G.AdjMatrix[v][j];
path[j-1]=v; //更新顶点j的直接前驱为顶点v
}
}
}
}
//输出Dijkstra路径
void printDijsktra(Graph &G,int path[],int dist[],int vID)
{
int sPath[MaxVerNum]; //保存各个顶点到vID的路径
int vPre; //前驱节点编号
int top=-1;
int i,j;
for(i=1;i<=G.VerNum;i++)
{
cout<<G.Data[vID]<<"到"<<G.Data[i];
if(dist[i-1]==INF)
{
cout<<"无路径可到达"<<endl;
}
else{
cout<<"最短距离为:"<<dist[i-1]<<endl;
cout<<"路径:";
}
top++;
sPath[top]=i;
vPre=path[i-1];
while(vPre!=-1)
{
top++;
sPath[top]=vPre;
vPre=path[vPre-1];
}
if(dist[i-1]!=INF)
{
for(j=top;j>=0;j--)
{
cout<<G.Data[sPath[j]]<<" ";
}
}
top=-1;
cout<<endl;
}
}
//Floyd算法
void floyd(Graph &G,cellType dist[MaxVerNum][MaxVerNum],int path[MaxVerNum][MaxVerNum])
{
int i,j,m;
for(i=1;i<=G.VerNum;i++)
{
for(j=1;j<=G.VerNum;j++)
{
dist[i-1][j-1]=G.AdjMatrix[i][j];
if(i!=j && G.AdjMatrix[i][j]<INF)
{
path[i-1][j-1]=i;
}
else{
path[i-1][j-1]=-1;
}
}
}
//floyd核心算法,三重循环
for(m=1;m<=G.VerNum;m++)
{
for(i=1;i<=G.VerNum;i++)
{
for(j=1;j<=G.VerNum;j++)
{
if(i!=j && dist[i-1][m-1]+dist[m-1][j-1] < dist[i-1][j-1])
{
dist[i-1][j-1]=dist[i-1][m-1]+dist[m-1][j-1];
path[i-1][j-1]=path[m-1][j-1];
}
}
}
}
}
//输出floyd路径
void printFloyd(Graph &G,cellType dist[MaxVerNum][MaxVerNum],int path[MaxVerNum][MaxVerNum])
{
int i,j,k;
int Path[MaxVerNum];
int vPre; //保存前驱节点编号
int top=-1; //数组有效元素的最大下标
for(i=1;i<=G.VerNum;i++)
{
for(j=1;j<=G.VerNum;j++)
{
if(i!=j)
{
cout<<G.Data[i]<<"到"<<G.Data[j]<<endl;
if(dist[i-1][j-1]==INF)
{
cout<<"无路径可到达"<<endl;
}
else{
cout<<"最短距离为:"<<dist[i-1][j-1]<<endl;
cout<<"路径为:";
}
if(dist[i-1][j-1]!=INF)
{
top++;
Path[top]=j;
vPre=path[i-1][j-1];
while(i!=vPre && vPre!=-1)
{
top++;
Path[top]=vPre;
vPre=path[i-1][vPre-1];
}
top++;
Path[top]=vPre;
for(k=top;k>=0;k--)
{
cout<<G.Data[Path[k]]<<" ";
}
cout<<endl;
}
top=-1;
}
}
top=-1;
cout<<endl;
}
}
//获取所有顶点的入度并保存在inds[]中
void getInDegree(Graph &G,int inds[])
{
int i,j;
int k=0; //记录入度数
for(i=1;i<=G.VerNum;i++)
{
for(j=1;j<=G.VerNum;j++)
{
if(G.AdjMatrix[j][i]>0 && G.AdjMatrix[j][i]<INF)
{
k++;
}
}
inds[i]=k;
k=0; //再次初始化为0
}
}
//拓扑排序
int TopologicalSort(Graph &G,int topoList[],int vet[])
{
//topoList[]数组用于存放拓扑序列
int inds[MaxVerNum]; //定义顶点入度数组
stack<int> S; //定义一个顺序栈,保存入度为0的节点
int i;
int v; //顶点编号
int vCount=0; //记录顶点入度为0的顶点数
for(i=1;i<=G.VerNum;i++) //初始化vet[]数组
{
vet[i]=0;
}
for(i=1;i<=G.VerNum;i++) //入度数组初始化,inds[0]不用
{
inds[i]=0;
}
for(i=1;i<G.VerNum;i++) //拓扑排序数组初始化
{
topoList[i-1]=-1; //编号初始化为-1
}
getInDegree(G,inds); //获取所有顶点的入度并保存在inds[]中
for(i=1;i<=G.VerNum;i++) //入度为0节点入栈
{
if(inds[i]==0)
{
S.push(i);
}
}
while(!S.empty())
{
v=S.top(); //或取栈顶元素
S.pop(); //出栈
topoList[vCount]=v; //当前入度为0的顶点v,加入拓扑序列
vCount++;
for(i=1;i<=G.VerNum;i++)
{
if(G.AdjMatrix[v][i]>=1 && G.AdjMatrix[v][i]<INF)
{
//与v邻接的顶点i入度减1
if(!(--inds[i]))//顶点i的入度已经为0,入栈
{
S.push(i);
}
if(vet[v]+G.AdjMatrix[v][i] > vet[i])
{
vet[i]=vet[v]+G.AdjMatrix[v][i]; //求其v的邻接顶点最早发生时间
}
}
}
}
if(vCount==G.VerNum)
return 1; //返回无回路标记
else
return 0;
}
//输出拓扑序列
void printTopoList(Graph &G,int topoList[],int vet[])
{
int i;
if(TopologicalSort(G,topoList,vet))
{
cout<<"拓扑序列为:";
for(i=0;i<G.VerNum;i++)
{
cout<<G.Data[topoList[i]]<<" ";
}
for(i=0;i<G.VerNum;i++)
{
cout<<endl;
cout<<"编号"<<G.Data[topoList[i]]<<"最早发生时间:"<<vet[topoList[i]];
}
}
else{
cout<<"有有向环,不能生成拓扑序列!"<<endl;
}
}
//获取所有顶点的出度并保存数组outds[]中
void getOutDegree(Graph &G,int outds[])
{
int i,j;
int k=0; //记录出度数
for(i=1;i<=G.VerNum;i++)
{
for(j=1;j<=G.VerNum;j++)
{
if(G.AdjMatrix[i][j]>0 && G.AdjMatrix[i][j]<INF)
{
k++;
}
}
outds[i]=k;
k=0; //再次初始化为0
}
}
//逆拓扑排序
int antiTopologicalSort(Graph &G,int vlt[],int vet[])
{
int outds[MaxVerNum]; //定义顶点出度数组
stack<int> S; //保存出度为0的顶点
int i;
int v; //顶点编号
int vCount=0; //记录顶点入度为0的顶点数
int max=0;
for(i=1;i<=G.VerNum;i++)
{
if(max<vet[i])
{
max=vet[i]; //获取最大时间(汇点时间)
}
}
for(i=1;i<=G.VerNum;i++)
{
vlt[i]=max; //全部赋值(汇点时间)
}
for(i=1;i<=G.VerNum;i++) //出度数组初始化。outds[0]不用
{
outds[i]=0;
}
// for(i=1;i<G.VerNum;i++) //拓扑排序数组初始化
// {
// antiTopoList[i-1]=-1; //编号初始化为-1
// }
getOutDegree(G,outds); //获取所有顶点的出度并保存数组outds[]中
for(i=1;i<=G.VerNum;i++) //出度为0节点入栈
{
if(outds[i]==0)
{
S.push(i);
}
}
while(!S.empty())
{
v=S.top(); //获取栈顶元素
S.pop(); //出栈
// antiTopoList[vCount]=v; //当前出度为0的顶点v,加入拓扑序列
vCount++;
for(i=1;i<=G.VerNum;i++)
{
if(G.AdjMatrix[i][v]>=1 && G.AdjMatrix[i][v]<INF)
{
//邻接v的顶点的出度减1
if(!(--outds[i]))
{
S.push(i); //出度为0的顶点入栈
}
if(vlt[v]-G.AdjMatrix[i][v] < vlt[i])
{
vlt[i]=vlt[v]-G.AdjMatrix[i][v]; //求其v上一个顶点的最迟发生时间
}
}
}
}
if(vCount==G.VerNum)
return 1; //返回无回路标记
else
return 0;
}
//输出逆拓扑排序
void printAntiTopoList(Graph &G,int topoList[],int vlt[],int vet[])
{
int i;
if(antiTopologicalSort(G,vlt,vet))
{
cout<<"拓扑序列为:";
for(i=0;i<G.VerNum;i++)
{
cout<<G.Data[topoList[i]]<<" ";
}
for(i=0;i<G.VerNum;i++)
{
cout<<endl;
cout<<"编号"<<G.Data[topoList[i]]<<"最晚发生时间:"<<vlt[topoList[i]];
}
}
else{
cout<<"有有向环,不能生成逆拓扑序列!"<<endl;
}
}
//输出一条关键路径
void printCriticalPath(Graph &G,int topoList[],int vlt[],int vet[])
{
int i=0,j=0,k=0,v=0;
int vPre=0;
if(!TopologicalSort(G,topoList,vet))
{
cout<<"无拓扑序列!"<<endl;
return ;
}
cout<<"关键路径为:";
i=G.VerNum;
k=topoList[v];
while(k!=vPre) //如上一次访问顶点编号与此次相同则退出
{
if(vet[k]==vlt[k])
{
if(k!=vPre)
{
cout<<G.Data[k]<<" "; //不同则输出
}
vPre=k;
for(j=1;j<=G.VerNum;j++)
{
if(G.AdjMatrix[k][j]>=1 && G.AdjMatrix[k][j] < INF)
{
if(vet[j]==vlt[j])
{
break;
}
}
}
}
k=j; //或取k的邻接点
}
}
#endif // _OPERATEGRPADJMATRIX_H_
#include <iostream>
#include <Cstdlib>
#include "createGrpAdjMatrix.h"
#include "operateGrpAdjMatrix.h"
#include "tree.h"
using namespace std;
int main()
{
int i,x;
Graph G;
csTree T;
int path[MaxVerNum];
int dist[MaxVerNum];
int topoList[MaxVerNum]; //拓扑序列
int antiTopoList[MaxVerNum]; //逆拓扑序列
cellType Dist[MaxVerNum][MaxVerNum];
int Path[MaxVerNum][MaxVerNum];
int vet[MaxVerNum]={0}; //事件的最早发生时间
int vlt[MaxVerNum]={0}; //事件的最晚发生时间
char fileName[100]; //保存文件名
menu();
cout<<"请输入执行序号:";
cin>>i;
while(i)
{
switch(i)
{
case 1:
cout<<"请输入打开的文件名:";
cin>>fileName;
if(CreateGrpFromFile(fileName,G))
{
cout<<"数据处理完毕!"<<endl;
}
break;
case 2:
cout<<"请输入访问图的起始顶点编号:";
cin>>x;
cout<<"深度优先遍历序为:";
DFSTraverse(G,x);
cout<<endl;
cout<<"广度优先遍历序为:";
BFSTraverse(G,x);
cout<<endl;
break;
case 3:
cout<<"边或弧的数目为:"<<G.ArcNum;
cout<<endl;
break;
case 4:
T=NULL;
cout<<"请输入访问图的起始顶点编号:";
cin>>x;
T=DFSForest(G,T,x);
cout<<"树或森林的先序遍历序为:";
perOrderTraverse(T);
cout<<endl;
destroy(T);
break;
case 5:
T=NULL;
cout<<"请输入访问图的起始顶点编号:";
cin>>x;
T=BFSForest(G,T,x);
cout<<"树或森林的先序遍历序为:";
perOrderTraverse(T);
cout<<endl;
destroy(T);
break;
case 6:
cout<<"请输入访问图的起始顶点编号:";
cin>>x;
prim(G,x);
cout<<endl;
break;
case 7:
cout<<"Kruskal最小生成树为:";
Kruskal(G);
cout<<endl;
break;
case 8:
cout<<"请输入访问图的起始顶点编号:";
cin>>x;
Dijkstra(G,path,dist,x);
printDijsktra(G,path,dist,x);
break;
case 9:
floyd(G,Dist,Path);
printFloyd(G,Dist,Path);
break;
case 10:
printTopoList(G,topoList,vet);
cout<<endl;
printAntiTopoList(G,topoList,vlt,vet);
cout<<endl;
break;
case 11:
TopologicalSort(G,topoList,vet);
antiTopologicalSort(G,vlt,vet);
printCriticalPath(G,topoList,vlt,vet);
cout<<endl;
break;
}
system("PAUSE");
system("CLS");
menu();
cout<<"请输入执行序号:";
cin>>i;
}
return 0;
}
五、运行和测试
本实验所需文件和代码可从百度网盘进行下载:https://pan.baidu.com/s/19Rcf5faJNDj8NFC74hlcrA 提取码:y5ap