数据结构实验三 图的操作与实现

系列文章:
数据结构实验一 线性表、堆栈和队列的操作与实现
数据结构实验二 二叉树的操作与实现
数据结构实验三 图的操作与实现
数据结构实验四 查找和排序算法实现

一、实验目的:

1、领会图的两种主要存储结构和图的基本运算算法设计;
2、领会图的两种遍历算法;
3、领会Prim算法求带权连通图中最小生成树的过程和相关算法设计;
4、掌握深度优先遍历和广度优先遍历算法在图路径搜索问题中的应用;
5、深入掌握图遍历算法在求解实际问题中的应用。

二、使用仪器、器材

微机一台
操作系统:WinXP
编程软件:C/C++编程软件

三、实验内容及原理

填入自己的内容(思路或算法流程图、源代码、说明等)

1、教材P310实验题1:实现图的邻接矩阵和邻接表的存储

编写一个程序graph.cpp,设计带权图的邻接矩阵与邻接表的创建和输出运算,并在此基础上设计一个主程序exp8-1.cpp完成以下功能。
(1)建立如图8.54所示的有向图G的邻接矩阵,并输出之。
(2)建立如图8.54所示的有向图G的邻接表,并输出之。
(3)销毁图G的邻接表。

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 1024   //最大顶点数
int matrix[maxn][maxn];   //邻接矩阵
int n,m; //顶点数,边数
/*
边结点
*/
struct ArcNode{
    int adjvex;  //该边所指向的顶点的位置
    int lowcost; //权值
    ArcNode* next;  //指向的下一条边的指针
};

ArcNode* ArcList[maxn*(maxn-1)];   //所有边结点
int in = 0;   //下标

/*
顶点
*/
struct{
    ArcNode* firstarc;
}AdjList[maxn];

/*
增加一条从i指向j的权值为k的顶点
*/
void add(int i,int j,int k){
    matrix[i][j] = k;
    ArcNode* p = new ArcNode();
    p->adjvex = j;  //它指向的是j顶点
    p->lowcost = k; //权值为k
    //p插入到链表头部
    p->next = AdjList[i].firstarc;
    AdjList[i].firstarc = p;
    ArcList[in++] = p;  //把这个边结点存储到数组中
}


int main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    n = 6;
    m = 10;
	//初始化邻接矩阵
    for(int i = 0;i<n;++i){
        for(int j = 0;j<n;++j){
            matrix[i][j] = -1;
        }
    }
    //初始化AdjList
    for(int i = 0;i<n;++i){
        AdjList[i].firstarc = NULL;
    }
    //增加m条边
    add(0,1,5);
    add(0,3,3);
    add(1,2,4);
    add(2,0,8);
    add(2,5,9);
    add(3,2,5);
    add(3,5,6);
    add(4,3,5);
    add(5,4,1);
    add(5,0,3);
    //打印邻接矩阵
    cout << "adjacent matrix:" << endl;
    for(int i = 0;i<n;++i){
        for(int j = 0;j<n;++j){
            cout << matrix[i][j] << " \n"[j == n-1];
        }
    }
    //打印邻接表
    cout << "adjacency list:" << endl;
    ArcNode* p = 0;
    for(int i = 0;i < n;++i){
        p = AdjList[i].firstarc;
        cout << i << " : ";
        while(p){
            cout << p->adjvex << "(" << p->lowcost << ")" << " -> ";
            p = p->next;
        }
        cout << "^" << endl;
    }
    p = 0;
    //销毁邻接表
    for(int i = 0;i<in;++i){
        delete ArcList[i];  //删除
        ArcList[i] = 0;  //指针置空
    }
    //修改每个顶点的firstarc为空
    for(int i = 0;i<n;++i){
        AdjList[i].firstarc = 0;
    }
    in = 0;
	return 0;
}

2、教材P310实验题2:实现图的遍历算法

编写一个程序travsal.cpp实现图的两种遍历运算,并在此基础上设计一个程序exp8-2.cpp完成以下功能。
(1) 输出如图8.54的有向图G从顶点0开始的深度优先遍历序列(递归算法)。
(2) 输出如图8.54的有向图G从顶点0开始的深度优先遍历算法(非递归算法)。
(3) 输出如图8.54的有向图G从顶点0开始的广度优先遍历序列。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 1024   //最大顶点数
int n,m; //顶点数,边数
/*
边结点
*/
struct ArcNode{
    int adjvex;  //该边所指向的顶点的位置
    int lowcost; //权值
    ArcNode* next;  //指向的下一条边的指针
};

ArcNode* ArcList[maxn*(maxn-1)];   //所有边结点
int in = 0;   //下标

/*
顶点
*/
struct{
    ArcNode* firstarc;
}AdjList[maxn];

/*
增加一条从i指向j的权值为k的顶点
*/
void add(int i,int j,int k){
    ArcNode* p = new ArcNode();
    p->adjvex = j;  //它指向的是j顶点
    p->lowcost = k; //权值为k
    //p插入到链表头部
    p->next = AdjList[i].firstarc;
    AdjList[i].firstarc = p;
    ArcList[in++] = p;  //把这个边结点存储到数组中
}

/*
    销毁邻接表
*/
void destoryArc(){
    //销毁邻接表
    for(int i = 0;i<in;++i){
        delete ArcList[i];  //删除
        ArcList[i] = 0;  //指针置空
    }
    //修改每个顶点的firstarc为空
    for(int i = 0;i<n;++i){
        AdjList[i].firstarc = 0;
    }
}

bool tag[maxn]; //访问标记
/*
深度优先遍历序列(递归)
v : 当前访问的顶点
*/
void dfs1(int v = 0){
    cout << v << " ";   //输出这个顶点
    tag[v] = 1;     //给这个顶点加入标记
    ArcNode* p = AdjList[v].firstarc; //邻接表
    while(p){  //遍历所有和这个顶点相连的边
        if(!tag[p->adjvex])  //如果准备访问的顶点没加访问标记,就加入
            dfs1(p->adjvex);  //递归
        p = p->next;   //指针后移
    }
    p = 0;  //指针置空
}

int stk[maxn];  //模拟栈
int si = 0;  //栈里元素个数
/*
深度优先遍历 非递归
*/
void dfs2(){
    memset(tag,0,sizeof(tag));  //清空访问标记
    int v = 0;  //当前访问的顶点
    si = 0;  //清空栈的元素
    stk[si++] = v;  //入栈
    tag[v] = 1;  //标记初始顶点
    cout << "dfs(not recursion):";
    ArcNode*p = 0;
    while(si){
        //只要栈内还有元素,就一直循环
        v = stk[--si];  //出栈
        cout << v << " ";  //输出
        p = AdjList[v].firstarc;
        while(p){
            if(!tag[p->adjvex]){
                //只要没被访问过就入栈
                stk[si++] = p->adjvex;
                tag[p->adjvex] = 1;  //标记
            }
            p = p->next;  //指针后移
        }
        p = 0; //指针置空
    }
    cout << endl;
}

int que[maxn];  //队列
int qi = 0,qj = 0;  //头指针和尾指针
/*
广度优先
*/
void bfs(){
    memset(tag,0,sizeof(tag));  //清空访问标记
    int v = 0;  //当前顶点
    qi = 0;
    qj = 0;
    tag[v] = 1;  //标记初始顶点
    cout << "bfs:";
    que[qj++] = v;   //入队
    ArcNode*p = 0;
    while(qi != qj){
        //只要队列不空,就一直循环
        v = que[qi++];  //出队
        cout << v << " "; //输出顶点
        qi %= maxn; //如果qi >= maxn,则从0开始
        p = AdjList[v].firstarc;
        while(p){
            if(!tag[p->adjvex]){
                //只要没被访问过就入栈
                que[qj++] = p->adjvex;
                qj %= maxn;
                tag[p->adjvex] = 1;  //标记
            }
            p = p->next;  //指针后移
        }
        p = 0;  //指针置空
    }
    cout << endl;
}

int main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    n = 6;
    m = 10;
    //初始化AdjList
    for(int i = 0;i<n;++i){
        AdjList[i].firstarc = NULL;
    }
    //增加m条边
    add(0,1,5);
    add(0,3,3);
    add(1,2,4);
    add(2,0,8);
    add(2,5,9);
    add(3,2,5);
    add(3,5,6);
    add(4,3,5);
    add(5,4,1);
    add(5,0,3);
    //-----------深度优先(递归)----------
    memset(tag,0,sizeof(tag));  //清空访问标记
    cout << "dfs(recursion):";
    dfs1();
    cout << endl;
    memset(tag,0,sizeof(tag));  //清空访问标记
    //-----------深度优先(非递归)----------
    dfs2();
    //-----------广度优先----------
    bfs();
    destoryArc();  //销毁邻接表
	return 0;
}

3、教材P311实验题5:采用Prim算法求最小生成树

编写一个程序exp8-5.cpp,实现求带权连通图中最小生成树的Prim算法,如图8.55所示的带权连通图G,输出从顶点0出发的一棵最小生成树。

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 100
int matrix[maxn][maxn];  //邻接矩阵
int n,m;
bool tag[maxn];  //标记,tag[i] = true表示顶点i在集合U中

struct{
    int adjvex;  //最小边在U中的那个顶点
    int lowcost;    //权值
}closedge[maxn];

/*
增加一条连接i和j的权值为k的边
*/
void add(int i,int j,int k){
    matrix[i][j] = k;
    matrix[j][i] = k;
}


int main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    //初始化邻接矩阵
    for(int i = 0;i < maxn;++i){
        for(int j = 0;j < maxn;++j){
            matrix[i][j] = -1;
        }
    }
	n = 6;  //顶点6个
    m = 10; //边10条
    add(0,1,5);
    add(1,2,4);
    add(2,3,5);
    add(3,4,5);
    add(4,5,1);
    add(5,0,3);
    add(0,2,8);
    add(0,3,7);
    add(2,5,9);
    add(3,5,6);
    int v = 0;  //当前顶点为0
    tag[0] = 1;  //从顶点0出发,所以要把顶点0加入到集合U
    //初始化closedge
    for(int i = 0;i<n;++i){
        closedge[i] = {0,matrix[i][0]};
    }
    int T = n-1;
    //循环执行n-1次
    while(T--){
        //寻找closedge里一端不在集合U中的边
        int mi = INT_MAX;
        for(int i = 0;i<n;++i){
            if(!tag[i] && closedge[i].lowcost != -1 && closedge[i].lowcost < mi){
                mi = closedge[i].lowcost;
                v = i;
            }
        }    
        tag[v] = 1;   //加入集合U中
        cout << v << " --- " << closedge[v].adjvex << endl; //输出这条边
        //更新lowcost
        for(int i = 1;i<n;++i){
            if(!tag[i] && (closedge[i].lowcost == -1 || (matrix[i][v] != -1 && closedge[i].lowcost > matrix[i][v]))){
                closedge[i].lowcost = matrix[i][v];
                closedge[i].adjvex = v;
            }
        }
        

    }
	return 0;
}

4、教材P311实验题10:求有向图的简单路径

编写一个程序exp8-10.cpp,设计相关算法完成以下功能。
(1) 输出如图8.56的有向图G从顶点5到顶点2的所有简单路径。
(2) 输出如图8.56的有向图G从顶点5到顶点2的所有长度为3的简单路径。
(3) 输出如图8.56的有向图G从顶点5到顶点2的最短路径。

图8.56 一个有向图

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 100   //最大顶点数
int matrix[maxn][maxn];//邻接矩阵
int n,m; //顶点个数,边的个数
bool tag[maxn];  //标记这个顶点是否已经访问过
int tmp[maxn];  //记录路径

/*
深度优先遍历找所有路径
now:当前顶点
dest:目标
i:当前长度
*/
void dfs(int now,int dest,int i = 0){
    tmp[i] = now;
    if(now == dest){
        //到达终点,输出
        for(int j = 0;j<=i;++j){
            cout << tmp[j] << " \n"[j == i];
        }
        return;
    }
    //遍历从该顶点出发的所有边
    for(int j = 0;j<n;++j){
        if(matrix[now][j] && !tag[j]){
            //边存在并且未被访问过
            tag[j] = 1;   //标记
            dfs(j,dest,i+1);    //递归
            tag[j] = 0;  //取消标记
        }
    }
}

/*
深度优先遍历找指定长度的所有路径
now:当前顶点
dest:目标
len:指定长度
i:当前长度
*/
void dfs1(int now,int dest,int len,int i = 0){
    tmp[i] = now;
    if(now == dest){
        //到达终点,并且长度为len,输出
        if(i == len)
            for(int j = 0;j<=i;++j){
                cout << tmp[j] << " \n"[j == i];
            }
        return;
    }
    if(i >= len){
        return;
    }
    //遍历从该顶点出发的所有边
    for(int j = 0;j<n;++j){
        if(matrix[now][j] && !tag[j]){
            //边存在并且未被访问过
            tag[j] = 1;   //标记
            dfs1(j,dest,len,i+1);    //递归
            tag[j] = 0;  //取消标记
        }
    }
}

/*
使用广度优先遍历计算从start到end的最短路径
*/
int last[maxn];  //记录在bfs过程中的顶点的上一顶点
void bfs(int start,int end){
    queue<int> q;  //队列
    memset(tag,0,sizeof(tag));  //重置标记
    memset(last,-1,sizeof(last));  //重置上一顶点的记录
    cout << "Shortest path from vertex 5 to vertex 2 is:" << endl;
    q.push(start);  //把开始顶点加入到队列
    tag[start] = 1;  //给开始顶点添加标记
    int v;  //当前顶点
    while(!q.empty()){
        //取出队列中的第一个元素
        v = q.front();
        q.pop();
        if(v == end){
            //找到结束点
            break;
        }
        //遍历从v开始的边
        for(int i = 0;i<n;++i){
            //路径存在并且未被访问过
            if(!tag[i] && matrix[v][i]){
                tag[i] = 1; //标记
                last[i] = v;  //记录上一顶点
                q.push(i);
            }
        }
    }
    int i = 0;  //tmp数组的下标(路径长度)
    v = end;
    while(v != -1){
        tmp[i++] = v;
        v = last[v];  //跳到上一顶点
    }
    //输出路径
    while(i){
        cout << tmp[--i] << " ";
    }
    cout << endl;
}

int main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    // 初始化邻接矩阵
    for(int i = 0;i<n;++i){
        for(int j = 0;j<n;++j){
            matrix[i][j] = 0;
        }
    }
	n = 6;
    m = 12;
    //下面m行建图
    matrix[0][1] = 1;
    matrix[0][3] = 1;
    matrix[1][2] = 1;
    matrix[2][5] = 1;
    matrix[2][0] = 1;
    matrix[3][2] = 1;
    matrix[3][5] = 1;
    matrix[4][3] = 1;
    matrix[5][4] = 1;
    matrix[5][3] = 1;
    matrix[5][1] = 1;
    matrix[5][0] = 1;
    //(1)	输出有向图G从顶点5到顶点2的所有简单路径。
    cout << "All the paths from vertex 5 to vertex 2:"<< endl;
    memset(tag,0,sizeof(0));  //初始化tag
    tag[5] = 1;  //标记5
    dfs(5,2);
    tag[5] = 0;  //取消标记5
    //(2)	输出有向图G从顶点5到顶点2的所有长度为3的简单路径。
    cout << "All paths from vertex 5 to vertex 2 of length 3:" << endl;
    memset(tag,0,sizeof(0));  //初始化tag
    tag[5] = 1;  //标记5
    dfs1(5,2,3);
    tag[5] = 0;  //取消标记5
    // (3)输出有向图G从顶点5到顶点2的最短路径。
    bfs(5,2);
	return 0;
}

5、教材P313实验题14:用图搜索方法求解如图3.28(教材P119)的迷宫问题(也可以自建迷宫)

编写一个程序exp8-14.cpp,完成以下功能。
(1) 建立一个迷宫对应的邻接表表示。
(2) 采用深度优先遍历算法输出从入口(1,1)到出口(M,N)的所有迷宫路径。

图3.28 迷宫示意图

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct ArcNode;  //提前声明一下
int m[6][6] = {
    {0,0,0,0,0,0},
    {0,1,1,1,0,0},
    {0,1,0,1,1,0},
    {0,1,1,1,0,0},
    {0,0,1,1,1,0},
    {0,0,0,0,0,0}
};  //地图

int DIRECTIONS[2][4] = {{1,-1,0,0},{0,0,1,-1}};

/*
顶点
*/
struct Point{
    int x,y;
    ArcNode* firstArc;
}*mp[6][6];

/*
边
*/
struct ArcNode{
    Point* p;   //该边指向的顶点
    ArcNode* next; //下一个指针
};

bool tag[6][6];  //标记是否有被访问过

/*
深度优先搜索
x,y:坐标
i:path的下标
*/
Point* path[6*6];  //记录路径
void dfs(int x,int y,int i = 0){
    path[i] = mp[x][y];
    if(x == 4 && y == 4){
        //到达终点
        for(int j = 0;j<=i;++j){
            cout << "(" << path[j]->x << "," << path[j]->y << ")" << " \n"[j == i];  //输出路径
        }
        return;
    }
    ArcNode*p = 0;  //邻接链表
    p = mp[x][y]->firstArc;  //p一开始指向firstArc
    int xi,yi;
    while(p){  //只要p不为空,就一直循环
        xi = p->p->x; 
        yi = p->p->y;
        if(!tag[xi][yi]){
            //只有没被访问过才能访问
            tag[xi][yi] = 1;  //标记
            dfs(xi,yi,i+1);   //递归
            tag[xi][yi] = 0;  //取消标记
        }
        p = p->next;
    }
}



int main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    //初始化mp数组
    for(int i = 0;i<6;++i){
        for(int j = 0;j<6;++j){
            if(m[i][j]){
                mp[i][j] = new Point();
                mp[i][j]->x = i;
                mp[i][j]->y = j;
                mp[i][j]->firstArc = 0;
            }else{
                mp[i][j] = 0;
            }
            
        }
    }
    //初始化邻接表
    int x,y;
    for(int i = 1;i<=4;++i){
        for(int j = 1;j<=4;++j){
            if(m[i][j]){
                for(int k = 0;k<4;++k){
                    x = i + DIRECTIONS[0][k];
                    y = j + DIRECTIONS[1][k];
                    if(m[x][y]){
                        //地图上有点
                        ArcNode* p = new ArcNode();
                        p->p = mp[x][y]; 
                        //链表常规插入操作
                        p->next = mp[i][j]->firstArc;
                        mp[i][j]->firstArc = p;
                    }
                }
            }
            
        }
    }
    memset(tag,0,sizeof(tag));  //初始化tag数组
    cout << "The maze path from entry to exit is as follows:" << endl;
    tag[1][1] = 1;  //给起点添加访问标记
    dfs(1,1);   //深度优先
	return 0;
}

  • 16
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值