声明: 以下是我们学校在学习数据结构时进行的实训,如涉及侵权马上删除文章
声明:本文主要用作技术分享,所有内容仅供参考。任何使用或依赖于本文信息所造成的法律后果均与本人无关。请读者自行判断风险,并遵循相关法律法规。
实训五 图的基本操作
一、实训类型
验证性实训
二、实训目的与任务
1.掌握图的逻辑结构;
2. 掌握图的邻接矩阵存储结构;
3. 掌握图在邻接矩阵存储结构上遍历算法的实现。
4. 掌握图的邻接表存储结构;
5. 掌握图的邻接表存储结构下遍历算法的实现。
三、实训基本原理
1. 邻接矩阵
用一维数组存储图中顶点的信息,用一个二维数组表示图中各顶点之间的邻接关系信息,这个二维数组称为邻接矩阵。设图G=(V,E)有n个确定的顶点,即V={v0,v1,…,vn-1},则表示G中各顶点相邻关系为一个n×n的矩阵,矩阵的元素为:
2. 邻接表
对图G中的每个顶点vi,将所有邻接于vi的顶点vj链成一个单链表,这个单链表就称为顶点vi的邻接表。
再将所有点的邻接表表头放到数组中,就构成了图的邻接表。其中,单链表中的结点称为表结点,每个单链表设的一个头结点称为顶点结点。
3. 深度优先遍历
⑴ 访问某个顶点v;
⑵ 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
⑶ 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到
⑷ 若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
4. 广度优先遍历
⑴ 访问某一顶点v;
⑵ 依次访问v的各个未被访问的邻接点v1,v2,……,vk;
⑶ 分别从v1,v2,…,vk出发依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。直至图中所有与顶点v有路径相通的顶点都被访问到。
(4)若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
5.普利姆算法
初始化:设网图G=(V,E) ,V顶点集,E带权边集。
初始化集合U和T,U存放最小生成树中的顶点,T保存最小生成树中的边。U的初值为U={u0}(设构造最小生成树从顶点u0出发),T的初值={ }。
开始:从所有u∈U,v∈V-U的顶点中,选取具有最小权值的边(u,v),将顶点v加入集合U中,将边(u,v)加入集合T中,如此不断重复,直到U=V时,最小生成树构造完毕,这时集合T中包含了最小生成树的所有边。
四、实训设备
1.计算机
五、实训内容
1. 建立无向图的邻接矩阵存储;对建立的无向图,进行广度优先遍历。
2. 建立无向图的邻接矩阵存储;对建立的无向图,进行深度优先遍历。
3. 建立一个有向图的邻接表存储结构;对建立的有向图,进行深度优先遍历;
4. 建立一个有向图的邻接表存储结构;对建立的有向图,进行广度优先遍历。
5. 验证普利姆算法。
6. 求无向连通图的生成树(设计实训选作)
1. 2. 建立无向图的邻接矩阵存储;对建立的无向图,进行广度优先遍历,深度优先遍历。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <queue>
#define MAX 100 /*图中最大顶点个数*/
typedef struct
{
int n,e; /*顶点数,边数*/
char vexs[MAX]; /*顶点数组*/
int edges[MAX][MAX]; /*边的邻接矩阵*/
}MGraph;
// 初始化访问标记数组为未访问
bool visited[MAX] = {false};
// 广度优先遍历辅助函数
void BFS(MGraph G, int v) {
std::queue<int> q;
visited[v] = true;
printf("%c ", G.vexs[v]);
q.push(v);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < G.n; i++) {
if (G.edges[u][i] == 1 &&!visited[i]) {
visited[i] = true;
printf("%c ", G.vexs[i]);
q.push(i);
}
}
}
}
// 广度优先遍历
void BFSTraverse(MGraph G) {
for (int i = 0; i < G.n; i++) {
visited[i] = false;
}
for (int i = 0; i < G.n; i++) {
if (!visited[i]) {
BFS(G, i);
}
}
}
// 深度优先遍历辅助函数
void DFS(MGraph G, int v) {
visited[v] = true;
printf("%c ", G.vexs[v]);
for (int i = 0; i < G.n; i++) {
if (G.edges[v][i] == 1 &&!visited[i]) {
DFS(G, i);
}
}
}
// 深度优先遍历
void DFSTraverse(MGraph G) {
for (int i = 0; i < G.n; i++) {
visited[i] = false;
}
for (int i = 0; i < G.n; i++) {
if (!visited[i]) {
DFS(G, i);
}
}
}
void CreateMGraph(MGraph *G)
{
int i,j,k;
char ch1,ch2;
printf("请输入顶点数:");
scanf("%d",&G->n);
printf("请输入边数:");
scanf("%d",&G->e);
printf("请输入各顶点信息(每个顶点以回车作为结束):\n");
for(i=0;i<G->n;i++)
{
getchar();
printf("输入第%d个顶点:",i+1);
scanf("%c",&(G->vexs[i]));
}
for(i=0;i<G->n;i++)
for(j=0;j<G->n;j++)
G->edges[i][j]=0; /*将邻接矩阵元素全部置0*/
for(k=0;k<G->e;k++)
{
getchar();
printf("建立第%d条边(输入格式:顶点1,顶点2):",k+1);
scanf("%c,%c",&ch1,&ch2);
for(i=0;i<G->n;i++)
for(j=0;j<G->n;j++)
if((ch1==G->vexs[i] && ch2==G->vexs[j]) || (ch2==G->vexs[i] && ch1==G->vexs[j]))
{
G->edges[i][j]=1;
G->edges[j][i]=1;
}
}
}
void DispMGraph(MGraph G)
{
int i,j;
printf("\n图的邻接矩阵:\n");
for(i=0;i<G.n;i++)
{
for(j=0;j<G.n;j++)
printf("%5d",G.edges[i][j]);
printf("\n");
}
}
int main()
{
MGraph G;
CreateMGraph(&G);
printf("图的邻接矩阵:\n");
DispMGraph(G);
printf("广度优先遍历结果: ");
BFSTraverse(G);
printf("\n");
printf("深度优先遍历结果: ");
DFSTraverse(G);
printf("\n");
return 0;
}
3,4建立一个有向图的邻接表存储结构;对建立的有向图,进行深度优先遍历和广度优先遍历。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <queue>
#define MAX 100 /*图中最大顶点个数*/
typedef struct
{
int n,e; /*顶点数,边数*/
char vexs[MAX]; /*顶点数组*/
int edges[MAX][MAX]; /*边的邻接矩阵*/
}MGraph;
// 初始化访问标记数组
bool visited[MAX] = {false};
// 深度优先遍历辅助函数
void DFS(MGraph G, int v) {
visited[v] = true;
printf("%c ", G.vexs[v]);
for (int i = 0; i < G.n; i++) {
if (G.edges[v][i] == 1 &&!visited[i]) {
DFS(G, i);
}
}
}
// 深度优先遍历
void DFSTraverse(MGraph G) {
for (int i = 0; i < G.n; i++) {
visited[i] = false;
}
for (int i = 0; i < G.n; i++) {
if (!visited[i]) {
DFS(G, i);
}
}
}
// 广度优先遍历辅助函数
void BFS(MGraph G, int start) {
std::queue<int> q;
visited[start] = true;
q.push(start);
while (!q.empty()) {
int v = q.front();
q.pop();
printf("%c ", G.vexs[v]);
for (int i = 0; i < G.n; i++) {
if (G.edges[v][i] == 1 &&!visited[i]) {
visited[i] = true;
q.push(i);
}
}
}
}
// 广度优先遍历
void BFSTraverse(MGraph G) {
for (int i = 0; i < G.n; i++) {
visited[i] = false;
}
for (int i = 0; i < G.n; i++) {
if (!visited[i]) {
BFS(G, i);
}
}
}
void CreateMGraph(MGraph *G)
{ /*图的邻接矩阵建立函数*/
int i,j,k;
char ch1,ch2;
printf("请输入顶点数:");
scanf("%d",&G->n);
printf("请输入边数:");
scanf("%d",&G->e);
printf("请输入各顶点信息(每个顶点以回车作为结束):\n");
for(i=0;i<G->n;i++)
{ getchar();
printf("输入第%d个顶点:",i+1);
scanf("%c",&(G->vexs[i]));
}
for(i=0;i<G->n;i++)
for(j=0;j<G->n;j++)
G->edges[i][j]=0; /*将邻接矩阵元素全部置0*/
for(k=0;k<G->e;k++)
{ getchar();
printf("建立第%d条边(输入格式:顶点1,顶点2):",k+1);
scanf("%c,%c",&ch1,&ch2);
for(i=0;i<G->n;i++)
for(j=0;j<G->n;j++)
if(ch1==G->vexs[i]&&ch2==G->vexs[j])
{ G->edges[i][j]=1;
}
}
}
void DispMGraph(MGraph G)
{ /*图的邻接矩阵输出函数*/
int i,j;
printf("\n图的邻接矩阵:\n");
for(i=0;i<G.n;i++)
{ for(j=0;j<G.n;j++)
printf("%5d",G.edges[i][j]);
printf("\n");
}
}
int main()
{
MGraph G;
CreateMGraph(&G);
printf("图的邻接矩阵:\n");
DispMGraph(G);
printf("深度优先遍历结果: ");
DFSTraverse(G);
printf("\n");
printf("广度优先遍历结果: ");
BFSTraverse(G);
printf("\n");
return 0;
}
5. 验证普利姆算法。
#include <stdio.h>
#define MAX 100
#define M 32767 /*图中最大顶点个数, 32767 代表无穷大*/
// 创建无向图的邻接矩阵,并返回顶点数
int Creatcost(int cost[][MAX])
{
int vnum, anum, i, j, k, v1, v2, w;
printf("\n请输入顶点数和边数:");
scanf("%d%d", &vnum, &anum);
for (i = 0; i < vnum; i++)
{
for (j = 0; j < vnum; j++)
{
cost[i][j] = M;
}
}
printf("请输入各边及权值(顶点序号从 1 开始):\n");
for (k = 0; k < anum; k++)
{
printf("v1 v2 w :");
scanf("%d%d%d", &v1, &v2, &w);
cost[v1 - 1][v2 - 1] = w;
cost[v2 - 1][v1 - 1] = w;
}
return vnum;
}
// 普里姆算法计算最小生成树
void Prim(int c[MAX][MAX], int n)
{
int i, j, k, min, lowcost[MAX], closest[MAX];
for (i = 1; i < n; i++) /*从顶点 0 开始*/
{
lowcost[i] = c[0][i];
closest[i] = 0;
}
closest[0] = -1;
for (i = 1; i < n; i++)
{
min = M;
k = 1;
for (j = 0; j < n; j++)
{
if (lowcost[j] < min && closest[j]!= -1)
{
min = lowcost[j];
k = j;
}
}
printf("(%d, %d) 权值 %d\n", closest[k], k, lowcost[k]); /*打印生成树该边及其权值*/
closest[k] = -1;
for (j = 1; j < n; j++)
{
if (closest[j]!= -1 && c[k][j] < lowcost[j])
{
lowcost[j] = c[k][j];
closest[j] = k;
}
}
}
}
int main()
{
int n;
int cost[MAX][MAX];
n = Creatcost(cost);
printf("\n最小生成树为:\n");
Prim(cost, n);
return 0;
}
6. 求无向连通图的生成树(设计实训选作)
#include <stdio.h>
#include <stdbool.h>
#include <limits.h>
#define V 5 // 顶点数量
// 找到距离生成树集合最近的顶点
int minKey(int key[], bool mstSet[]) {
int min = INT_MAX, min_index;
for (int v = 0; v < V; v++)
if (mstSet[v] == false && key[v] < min)
min = key[v], min_index = v;
return min_index;
}
// 打印生成树
void printMST(int parent[], int graph[V][V]) {
printf("Edge Weight\n");
for (int i = 1; i < V; i++)
printf("%d - %d %d \n", parent[i], i, graph[i][parent[i]]);
}
// 普里姆算法
void primMST(int graph[V][V]) {
int parent[V]; // 存储生成树的父节点
int key[V]; // 用于选择最小权重边的键值
bool mstSet[V]; // 标记顶点是否在生成树中
// 初始化
for (int i = 0; i < V; i++)
key[i] = INT_MAX, mstSet[i] = false;
key[0] = 0; // 从第一个顶点开始
parent[0] = -1; // 第一个顶点没有父节点
for (int count = 0; count < V - 1; count++) {
int u = minKey(key, mstSet);
mstSet[u] = true;
for (int v = 0; v < V; v++)
if (graph[u][v] && mstSet[v] == false && graph[u][v] < key[v])
parent[v] = u, key[v] = graph[u][v];
}
printMST(parent, graph);
}
// 测试案例
int main() {
int graph[V][V] = {
{0, 2, 0, 6, 0},
{2, 0, 3, 8, 5},
{0, 3, 0, 0, 7},
{6, 8, 0, 0, 9},
{0, 5, 7, 9, 0}
};
printf("使用普里姆算法构建的生成树:\n");
primMST(graph);
return 0;
}
六、实训注意事项
1.题目自选,至少要完成两个题目。
2.若完成题目个数多或设计的算法效率高,予以加分。
七、预习与思考题
1.图的邻接矩阵、邻接表的表示方法。
2.图的深度优先遍历算法、图的广度优先遍历算法。
八、实训报告要求
1.书写算法或完整程序。
2.若调试程序时程序出错,请分析出错原因及修改方法。