1
【实验目的和要求】
见“图的存储结构的实现与应用”上机实验任务书(不要用附件)
实验目的
-
掌握图的各种存储结构,特别要熟练掌握邻接矩阵和邻接表存储结构。
-
熟练掌握图的深度优先遍历和广度优先遍历算法。
-
掌握生成最小生成树的Prim算法/克鲁斯卡尔算法。
-
掌握求最短路径的DijKstra算法。
实验要求
1.独立完成;
2.程序调试正确,有执行结果。
3.程序是自己开发的,在运行结果界面上输出显示姓名。
2
【实验题目】
见“图的存储结构的实现与应用”上机实验任务书;
基础题必做
1、基础题
(1)图的邻接矩阵定义及实现:
定义图的邻接矩阵存储结构,并编写图的初始化、建立图、输出图、深度优先遍历、计算并输出图中每个顶点的度等基本操作实现函数。以下两图为例,建立一个验证操作实现的主函数进行测试。
(2)图的邻接表的定义及实现:
定义图的邻接表存储结构,并编写图的初始化、建立图、输出图、广度优先遍历、计算并输出图中每个顶点的度等基本操作实现函数。同时在主函数中调用这些函数进行验证(以下两图为例)。
2、应用题
(1)设计华东交通大学的校园平面图,所含景点不少于8个。以图中顶点表示学校内各景点,存放景点的名称、景点介绍信息等;以边表示路径,存放路径长度信息。为来访客人提供从校园某一个景点到其他各景点的最短路径。在主函数main( )中进行测试验证。
(2)利用Prim算法或者克鲁斯卡尔算法,求解无向图的最小生成树,输出最小生成树。在主函数main( )中进行测试验证。
3
【实验内容】1.需求分析
按照上机实验报告模板格式,写出图的"数组"和"邻接表"存储结构的图形结构的上机实验的需求分析。(不要用上传附件形式)
正确答案:
我的答案:
数组表示法:使用两个数组,其中一个用来存储顶点的数据,另一个用来存储顶点之间的关系(弧)。
表示弧(顶点之间的关系)的矩阵被称为邻接矩阵(二维数组)。
具有n个顶点的图G的邻接矩阵是具有如下性质的n阶矩阵:
A[i][j]=
1 Vi邻接Vj (0≤i,j≤n-1)
0 Vi与Vj不邻接
邻接表是图(包括有向图和无向图)的链式存储结构。
对于图中的每个顶点vi,把所有邻接自vi的各个顶点链成一个单链表,称为边链表。
无向图中顶点vi的边链表中每个结点都对应一个与vi关联的边;
有向图中顶点vi的边链表中每个结点都对应一个以vi为起始顶点的弧,因此称为出边表。
为每个顶点vi的边链表建立一个头指针,存储在一维数组中(即顶点表),以便随机访问任一顶点的边链表。
因此,在邻接表中有两种结点:
链表结点 和 顶点结点
邻接表是一种顺序+ 链式存储结构。用顺序表存放顶点,为每个顶点建立一个单链表,单链表中的结点表示依附于该顶点的边或以该顶点为尾的弧。
4
【实验内容】2.概要设计
按照上机实验报告模板,写出图形结构的抽象数据类型定义ADT,其他模块(如果有)和函数的功能说明,本程序包含的函数列表,函数之间的调用关系。(不要用上传附件形式,调用关系可上传图片)
正确答案:
我的答案:
图的抽象数据类型定义如下:
ADT Graph {
数据对象:
V 是具有相同特性的数据元素的集合,称为顶点集。
数据关系:
R = { VR }
VR = { <v, w> | v, w∈V 且 P(v, w) }
谓词 P(v, w) 定义了 <v, w>之间关系的意义或信息。
基本操作:
初始化操作
CreatGraph ( &G, V, VR )操作结果:按 V和{VR}的定义构造图G。
结构销毁操作
DestroyGraph ( &G )
初始条件:图 G 存在。
操作结果:销毁图 G 。
引用型操作
LocateVex ( G, u )
初始条件:图 G 存在,u和G中的顶点有相同特征。
操作结果:若图G中存在顶点 u,则返回该顶点在图中位置,否则返回其他信息。
GetVex ( G, v )
初始条件:图 G 存在,v 是 G 中某个顶点。
操作结果:返回顶点 v 的值。
FirstAdjVex ( G, v )
初始条件:图 G 存在,v 是 G 中某个顶点。
操作结果:返回 v 的第一个邻接顶点 。若 v 在 G 中没有邻接点,则返回“空”。
NextAdjVex ( G, v, w )
初始条件:图 G 存在,v 是 G 中某个顶点,w 是 v 的邻接顶点。
操作结果:返回 v 的(相对于 w )下一个邻接顶点。若 w 是 v 的最后一个邻接点,则返回“空”。
DFSTraverse ( G, v)
初始条件:图 G 存在,v 是 G 中某个顶点。
操作结果:从顶点 v 起深度优先遍历图 G,并对每个顶点仅访问一次。
BFSTraverse ( G, v)
初始条件:图 G 存在,v 是 G 中某个顶点,Visit 是顶点的访问函数。
操作结果:从顶点 v 起广度优先遍历图 G,并对每个顶点仅访问一次。
加工型操作
PutVex ( &G, v, value )
初始条件:图 G 存在,v 是 G 中某个顶点。
操作结果:对 v 赋值 value。
InsertVex ( &G, v )
初始条件:图 G 存在,v和图中顶点有相同特性。
操作结果:在图 G 中增添新顶点 v。
DeleteVex ( &G, v )
初始条件:图 G 存在,v 是 G 中某个顶点。
操作结果:删除 G 中顶点 v 及其相关的弧。
InsertArc ( &G, v, w )
初始条件:图 G 存在,v, w 是 G 中2个顶点。
操作结果:在 G 中增添弧<v, w>,若 G 是无向的,则还增添对称弧<w, v>。
DeleteArc ( &G, v, w )
初始条件:图 G 存在,v, w 是 G 中2个顶点。
操作结果:在 G 中删除弧<v, w>,若 G 是无向的,则还删除对称弧<w, v>。
} ADT Graph
5
【实验内容】续2.概要设计(如果不需要续加,可以空着)
正确答案:
我的答案:
6
【实验内容】3.详细设计
按照上机实验报告模板,写出图形结构上机实验的详细设计部分:
1)实现概要设计中定义的所有的数据类型;
2)对每个操作对应的各个函数给出详细的伪码。
3)对主程序和其他模块(如果有)也写出详细的伪码;
4)分析各个函数的时间复杂度和空间复杂度。(递归的除外)
void DFSTraverse (Graph G ) { // 图深度优先遍历
for (v = 0; v < G.vexnum; ++v)
visited[v] = false; // 访问标志数组初始化
for (v = 0; v < G.vexnum; ++v) //保证所有连通分量
if(!visited[v])DFS(G, v); } //end
void DFS (Graph G, int v) {
// 从顶点 v 出发,深度优先搜索遍历 ——连通分量
visited[v]= true; cout<<v;
// 尚未访问的邻接顶点递归调用
for (w = FirstAdjVex(G, v); w>=0;
w = NextAdjVex(G, v, w))
if(!visited[w]) DFS(G, w); //从顶点W开始,递归
} // DFS
void BFSTraverse (Graph G) {
// 广度优先非递归遍历。使用辅助队列Q和标志数组visited。
for (v = 0; v < G.vexnum; ++v)
visited[v] = false; //访问标志数组初始化
InitQueue(Q); // 置空的辅助队列Q
for (v = 0; v < G.vexnum; ++v ) //保证遍历所有连通分量
if ( !visited[v]) { // v尚未访问过
visited[v] = true; //标记为访问过
cout<<v; // 访问起点v
EnQueue(Q, v); //v入队列
while (!QueueEmpty(Q)) { //队列非空的情况下
DeQueue(Q, u); // 队头元素出队并置为u
for (w=FirstAdjVex(G, u); w>=0;
w=NextAdjVex(G, u, w))
// 访问所有未被访问过的邻接点
if ( !visited[w]) {
// u的尚未访问的邻接顶点w入队列Q
visited[w] = true; //w标记为访问过
cout<<w;
EnQueue(Q, w);
} //while循环内的if结束
} // while结束
}//for循环内的if结束
} // BFSTraverse
void MiniSpanTree_P (MGraph G, VertexType u) {
// 用 Prim 算法从顶点 u 出发构造网 G 的最小生成树
// 网 G 用邻接矩阵表示
k = LocateVex( G, u );min = 100; // 假设100无穷大
for ( j = 0; j < G.vexnum; ++j ) // 辅助数组初始化
if ( j != k ) closedge[j] = { u, G.arcs[k][j] }; //矩阵值
closedge[k].lowcost = 0; // 初始,TV = { u },用0标记
for ( i = 1; i < G.vexnum; ++i ) { //循环n-1次,加顶点
for ( n = 0; n < G.vexnum; ++i ) //选权值最小的
if ( closedge[n].lowcast < min && closedge[n].lowcast )
{k = n; min = closedge[n].lowcast;} //k标记顶点
printf ( “(%c, %c)”, closedge[k].adjvex, G.vexs[k] );
// 输出最小生成树的一条边
closedge[k].lowcost = 0; // k顶点并入TV集,用0标记
for ( j = 0; j < G.vexnum; ++j ) // 修改顶点的当前 lowcast
if (G.arcs[k][j] < closedge[j].lowcost)
closedge[j] = { G.vexs[k], G.arcs[k][j] };
} // for
} // MiniSpanTree_P
时间复杂度: O(n2)
int dist[maxv]; //dist存储当前找到的最短路径长度
int path[maxv]; //当前找到的最短路径最后一个中转点
bool s[maxv];
//标记当前是否已求出最短路径,false:没,true:已求出
void dijkstra(mgraph g,int v) //v是起点对应数组下标
{//迪杰斯特拉算法从顶点v到其余各顶点的最短路径
int mindis, i, j, u;
for (i=0;i<g.n;i++) //初始化
{ dist[i]=g.edges[v][i]; //当前最短路径长度初始化
s[i]=false; //s[]标记是否求出最短路径
if (g.edges[v][i]<inf) path[i]= v;
//初始化当前找到的最短路径最后一个中转顶点
elsepath[i]= -1; }
s[v]=true; //源点编号v标记已求出最短路径
path[v]=-1; //源点v没有前驱顶点
dist[v]=0; //最短路径长度
for (i=1;i<g.n;i++) //求n-1个顶点的最短路径
//循环直到所有顶点的最短路径都求出或没有最短路径
{ mindis=inf; //存当前找到的最短路径长度
u=-1; //存当前找到的路径最短的新顶点下标
for (j=0;j<g.n;j++)
//选取不在s中,且具有最小距离的顶点u
if ((s[j]==false) && (dist[j]<mindis))
{ u=j;
mindis=dist[j];
} //if结束
if(mindis<inf) //如果找到了新的最短路径
{ s[u]=true; //新选出顶点u标记为找到了最短路径
for (j=0;j<g.n;j++) //修改未找出最短路径顶点信息
if (s[j]==false)
if (g.edges[u][j]<inf && dist[u]+g.edges[u][j]<dist[j])
{ dist[j]=dist[u]+g.edges[u][j];
//修改当前最短路径长度
Status TopologicalSort (ALGraph G) {
// 输出有向图 G 的拓扑顶点序列,用邻接表表示图
for ( i = 0; i < G.vexnum; i++ ) indegree[i] = 0;//入度
for ( i = 0; i < G.vexnum; i++ )
for ( p = G.xlist[i].firstarc; p; p = p->nextarc ){
k = p->adjvex;
indegree[k]++; // 计算每个顶点的入度
}
InitStack(S);
for ( i = 0; i < G.vexnum; i++ ) // 全部入度为 0 的顶点进栈
if (!indegree[i]) Push(S, i);
count = 0;// 输出顶点计数
path[j]=u; //修改当前最短路径最后一个中转点
while ( !StackEmpty(S) ) {
Pop (S,i);
printf (“%d. %c”, i, G.xlist[i].data);
count++; //已输出的顶点数
for ( p = G.xlist[i].firstarc; p; p = p-> nextarc ) {
k = p->adjvex;// 弧头顶点,即出边表中各结点
if ( ! (--indegree[k]) ) Push(S, k);
} // for
} // while
if ( count < G.vexnum) return ERROR;// 有回路
elsereturn OK;
} // TopologicalSort
}
}
}
}
8
【实验内容】
邻接表
#include <stdio.h>
#include <malloc.h>
#define MAX_SUM 1000
bool visited[MAX_SUM]= {false}; //设置标志数组
struct ArcNode
{
int adjvex; //该弧所指向的顶点的位置
ArcNode * next; //指向下一条弧的指针
};
typedef struct VNode
{
char vertex; //顶点信息
ArcNode * firstarc; //指向第一条依附该顶点的弧的指针
} AdjList[MAX_SUM];
struct ALGraph
{
AdjList adjList;
int vexNum; //图的顶点数
int arcNum; //图的弧数
};
typedef struct node
{
int data;
node *next;
} Node,*LinkQueueNode;
typedef struct
{
LinkQueueNode front; //队头指针
LinkQueueNode rear; //队尾指针
} LinkQueue;
bool InitQueue(LinkQueue *Q);//链队列初始化
bool EnterQueue(LinkQueue *Q,int x);//入队,将数据元素x插入到队列Q中
bool DeleteQueue(LinkQueue *Q,int *x);//出队,将队列Q的队头元素出队并保存到x所指的存储空间中
bool IsEmpty(LinkQueue *Q);//判断链队列是否为空
void CreateGraph(ALGraph *graph);//创建图
void PrintGraph(ALGraph *graph);//打印图
void depth_first_search(ALGraph *graph,int v);//深度优先遍历
void breadth_first_search(ALGraph *graph);//广度优先遍历
int main(void)
{
printf("邻接表\n");
ALGraph graph;
CreateGraph(&graph);
printf("\n邻接表打印为:\n");
PrintGraph(&graph);
printf("\n深度优先搜索DFS:");
for (int i = 0; i < graph.vexNum; i++)
if (!visited[i])//如果没有访问
depth_first_search(&graph,i);
printf("\n");
printf("\n广度优先搜索BFS:");
breadth_first_search(&graph);
printf("\n");
printf("数据结构小二班刘志江");
return 0;
}
bool InitQueue(LinkQueue *Q)//链队列初始化
{
Q->front=(LinkQueueNode)malloc(sizeof(Node));
if(Q->front!=NULL)
{
Q->rear=Q->front;
Q->front->next=NULL;
return true;
}
else
return false;//溢出
}
bool EnterQueue(LinkQueue *Q,int x)//入队,将数据元素x插入到队列Q中
{
LinkQueueNode NewNode;
NewNode=(LinkQueueNode)malloc(sizeof(Node));
if(NewNode!=NULL)
{
NewNode->data=x;
NewNode->next=NULL;
Q->rear->next=NewNode;
Q->rear=NewNode;
}
else
return false;//溢出
}
bool DeleteQueue(LinkQueue *Q,int *x)//出队,将队列Q的队头元素出队并保存到x所指的存储空间中
{
LinkQueueNode p;
if(Q->front==Q->rear)//表示队列已空
return false;
p=Q->front->next;
Q->front->next=p->next;//队头元素p出队
if(Q->rear==p)//如果队中只有一个元素p,则p出队后成为空队
Q->rear=Q->front;
*x=p->data;
free(p);//释放存储空间
return true;
}
bool IsEmpty(LinkQueue *Q)//判断链队列是否为空
{
return Q->front == Q->rear;
}
void CreateGraph(ALGraph *graph)//创建图
{
printf("请输入图的顶点数:\n");
scanf("%d",&graph->vexNum);
printf("请输入图的弧数:\n");
scanf("%d",&graph->arcNum);
getchar();
printf("请输入%d个顶点信息:\n",graph->vexNum);
for (int i = 0; i < graph->vexNum; i++)
{
scanf("%c",&graph->adjList[i].vertex);
graph->adjList[i].firstarc = NULL;
}
printf("请输入%d个弧的信息:\n",graph->arcNum);
int h1, h2;
ArcNode * temp;
for (int i = 0; i < graph->arcNum; i++)//根据输入的弧的信息构造邻接表
{
scanf("%d%d",&h1,&h2);
temp = new ArcNode;
temp->adjvex = h2;
temp->next = graph->adjList[h1].firstarc;
graph->adjList[h1].firstarc = temp;
temp = new ArcNode;
temp->adjvex = h1;
temp->next = graph->adjList[h2].firstarc;
graph->adjList[h2].firstarc = temp;
}
}
void PrintGraph(ALGraph *graph)//打印图
{
for (int i = 0; i < graph->vexNum; i++)
{
printf("%c------>",graph->adjList[i].vertex);
ArcNode *p = graph->adjList[i].firstarc;
while (p)
{
printf("%c ",graph->adjList[p->adjvex].vertex);
p = p->next;
}
printf("\n");
}
}
void depth_first_search(ALGraph *graph,int v)//深度优先遍历
{
visited[v] = true;
printf("%c ",graph->adjList[v].vertex);
ArcNode *p = graph->adjList[v].firstarc;
while (p)
{
if (!visited[p->adjvex])
depth_first_search(graph, p->adjvex);
p = p->next;
}
}
void breadth_first_search(ALGraph *graph)//广度优先遍历
{
for (int i = 0; i < MAX_SUM; i++)//初始化访问标志数组
visited[i] = false;
LinkQueue Queue;
int x;
InitQueue(&Queue);
for (int i = 0; i < graph->vexNum; i++)
if (!visited[i])//如果没有访问过
{
visited[i] = true;
EnterQueue(&Queue,i);//访问过的入队列
printf("%c ",graph->adjList[i].vertex);
while (!IsEmpty(&Queue))//队列不为空时
{
DeleteQueue(&Queue,&x);//先取出队首第一个元素,然后将第一个元素删除
ArcNode *p = graph->adjList[x].firstarc;
while(p)//访问未被访问过的邻接顶点
{
if (!visited[p->adjvex])
{
visited[p->adjvex] = true;
printf("%c ",graph->adjList[p->adjvex].vertex);
EnterQueue(&Queue,p->adjvex);
}
p = p->next;
}
}
}
}
邻接矩阵
#include<iostream>
#include<queue>
using namespace std;
queue<int>p;
int l;
typedef struct mygraph{
vector<int>q;
int d,h;
int a[10][10];
}mgraph;
void init(mgraph &g)
{
g.d=0,g.h=0;
if(!g.q.empty()) g.q.pop_back();
for(int i=1;i<=10;i++)
{
for(int j=1;j<=10;j++)
g.a[i][j]=0;
}
}
void creast(mgraph &g)
{
cout<<"请输入顶点数和弧边数"<<endl;
cin>>g.d>>g.h;
cout<<"有向边----1"<<endl;
cout<<"无向边----2"<<endl;
cout<<"请输入你的选择"<<endl;
cin>>l;
if(l==1) cout<<"输入p->q有向边"<<endl;
else cout<<"输入p-q无向边" <<endl;
for(int i=0;i<g.h;i++)
{
int j,h;
cin>>j>>h;
g.a[j][h]=1;
g.q.push_back(j);
if(l==2)
{
g.a[h][j]=1;
g.q.push_back(h);
}
}
}
void dfs(mgraph &g,int v)
{
cout<<v;
for(int i=1;i<=g.d;i++)
{
if(g.a[v][i]==1)
{
g.a[v][i]=0;
if(l==2) g.a[i][v]=0;
dfs(g,i);
}
if(i==g.d) return ;
}
}
void bfs(mgraph &g,int v) //从顶点v进行广度遍历
{
cout<<v;
for(int i=1;i<=g.d;i++)
{
if(g.a[v][i]==1)
{
g.a[v][i]=0;
if(l==2) g.a[i][v]=0;
// cout<<i;
p.push(i);
}
}
p.pop();
// cout<<p.size();
if(p.empty()) return ;
bfs(g,p.front());
}
int main()
{
cout<<"邻接矩阵"<<endl;
mgraph s;
init(s);
creast(s);
cout<<"输入从哪个顶点开始遍历"<<endl;
int v;
cin>>v;
p.push(v);
dfs(s,v);
printf("数据结构小二班刘志江");
return 0;
}
prim算法
#include<stdio.h>
#define MAXVEX 100
#define INF 65535
typedef char VertexType;
typedef int EdgeType;
typedef struct //邻接矩阵结构
{
VertexType vexs[MAXVEX];
EdgeType arc[MAXVEX][MAXVEX];
int numVexs, numEdges;
}AMGraph;
typedef struct //最小边的结构
{
VertexType adjvex; //最小边所对应的生成树内顶点
EdgeType lowcost; //最小边上的权值
}MinEdge;
MinEdge closedge[MAXVEX]; //定义最小边数组
void CreateAMGraph(AMGraph &G) //创建邻接矩阵
{
int i, j, k, w;
printf("请输入顶点数和边数:");
scanf("%d %d", &G.numVexs, &G.numEdges);
fflush(stdin);
for(i = 0; i < G.numVexs; i++)
{
printf("请第%d个顶点信息:", i + 1);
scanf("%c", &G.vexs[i]);
fflush(stdin);
}
for(i = 0; i < G.numVexs; i++)
for(j = 0; j < G.numVexs; j++)
G.arc[i][j] = INF;
for(k = 0; k < G.numEdges; k++)
{
printf("请输入第%d条边的两个顶点及其权值:", k+1);
scanf("%d %d %d", &i, &j, &w);
G.arc[i-1][j-1] = w;
G.arc[j-1][i-1] = w;
}
}
int LocateAMGraph(AMGraph G, VertexType u) //取顶点u的位置
{
for(int i = 0; i < G.numVexs; i++)
{
if(G.vexs[i] == u)
return i;
}
return -1;
}
int Min(MinEdge closedge[], int size) //取最小权值边的顶点位置
{
int k = -1;
for(int i = 0; i < size; i++)
{
if(closedge[i].lowcost > 0)
{
int min = closedge[i].lowcost;
for(int j = i; j < size; j++)
{
if(closedge[j].lowcost > 0 && min >= closedge[j].lowcost)
{
min = closedge[j].lowcost;
k = j;
}
}
break;
}
}
return k;
}
void Display(AMGraph G, MinEdge closedge[]) //遍历最小边数组和顶点
{
for(int i = 0; i < G.numVexs; i++)
{
printf("%c\t", closedge[i].adjvex);
}
printf("\n");
for(int j = 0; j < G.numVexs; j++)
{
printf("%c\t", G.vexs[j]);
}
printf("\n");
for(int k = 0; k < G.numVexs; k++)
{
if(closedge[k].lowcost == INF)
printf("∞\t");
else
printf("%d\t", closedge[k].lowcost);
}
printf("\n");
}
void MiniSpanTree_Prim(AMGraph G, VertexType u) //最小生成树Prim算法
{
int i, j, k = LocateAMGraph(G, u);
//初始化每个顶点到顶点u的最小边权值
for(i = 0; i < G.numVexs; i++)
{
closedge[i].adjvex = u;
closedge[i].lowcost = G.arc[k][i];
}
//边权值置为0,表示该顶点已加入最小生成树中
closedge[k].lowcost = 0;
//Display(G, closedge);
//printf("最小边数组初始化完成!\n");
//对剩余的n-1条边进行操作
for(i = 1; i < G.numVexs; i++)
{
//取最小边的顶点位置
k = Min(closedge, G.numVexs);
printf("---(%d)--->%c\n", closedge[k].lowcost, G.vexs[k]);
//边权值置为0,表示该顶点已加入最小生成树中
closedge[k].lowcost = 0;
//更新边的权值,使得生成树内顶点到其他顶点的边值最小
for(j = 0; j < G.numVexs; j++)
{
if(G.arc[k][j] < closedge[j].lowcost)
{
closedge[j].adjvex = G.vexs[k];
closedge[j].lowcost = G.arc[k][j];
}
}
}
}
int main()
{
AMGraph G;
CreateAMGraph(G);
//从顶点'A'开始创建最小生成树
MiniSpanTree_Prim(G, 'A');
printf("数据结构小二班刘志江");
return 0;
}