数据结构实验--图的三种算法的同步演示

一、课程设计题目与要求

【基本要求】

  • 1)基于图一构造图(权值可以自行设计添加);
  • 2)分别使用深度优先遍历(DFS)、Prim、Dijkstra算法从任意用户输入的顶点开始对图进行遍历、求MST(最小生成树)及最短路径;
  • 3)三个算法同时动态显示构造过程;
  • 4)每一步都要求显示/打印所有试探的路径(见图二);

在这里插入图片描述
在这里插入图片描述

【扩展要求】

  • 1)务必掌握单步调试;
  • 2)鼓励使用MFC或QT进行可视化;
  • 3)使用命令行程序时可考虑使用清屏(命令cls)后重新绘图进行动画演示;

二、需求分析

图是一种常见的结构,应用非常广泛,对其操作也多种多样,其中最常用的三类操作分别为遍历、求最小生成树、求两点之间最短路径 。本程序分别用DFS、Prim、Dijkstra三种算法实现上述功能。

  • DFS是深度优先搜索,特征是沿着一条路径走到头,然后再回溯。
  • Prim算法是在不断的把节点加入最小生成树的过程中 添加边,从而生成最小生成树
  • Dijkstra算法是不断的求起点到每个顶点的最短路径。

三、设计

3.1 设计思想

(1)数据结构设计

本程序主要采用邻接矩阵作为图的存储结构。算法中的顶点集合用数组存储。

(2)算法设计

在这里插入图片描述

三种算法:

DFS:

设置Visited[]保存是否被标记,stack s 用来保存遍历的序列
算法主体为:

  • 1.当前位置Loc进栈,输出栈顶,标记Loc位置
  • 2.找到与当前位置有连接的j,且j位置未标记,递归,把j作为Loc传给下一次的操作,直到所有节点均被访问为止

Prim:

设置Visited[]保存是否被标记,x[i][j]保存最短路径上的边,x[i][j]==1 表示边(i,j)在最短路径上。
算法主体为:

  • 找到这样的两个顶点 即 i位置已经被访问 且j位置未被访问 ,符合条件的 i只有一个而j可以不止一个,
  • 找到path[i][j]的权值最小的一个,visited[j]即对j位置访问,x[i][j]=1 即把边(i,j)纳入到最短路径中。
  • 重复以上步骤直到Visited[]的值全为1为止。

Dijkstra:

设置Visited[]保存是否被标记,Dis[]保存用户起点到下标号的节点路径的长度,Father[]保存最短路径上的前一个节点。
算法主体有三步:

  • 1更新:找到这样的两个顶点 即 i位置已经被访问 且j位置未被访问 且ij有连接,把(0,i)+(i,j)和(0,j)中的较小者赋给j,并且Father[j] = i即i作为j的前驱节点。
  • 2访问:每轮在更新Dis后 判断最小未被标记的顶点 对其访问(vis = 1;)
  • 重复1、2 步骤直到Visited[]的值全为1为止。

3.2 详细设计

“无向图”CC_Group类的构建:

数据成员有:

int C_Adj[MaxSize][MaxSize];
int C_Visited[MaxSize];
int C_PointNum;//顶点数
int C_ArcNum;//边数
C_Adj数组用来存储边的信息,C_Adj[i][j]不为零表示存在边(i,j)且这条边上的权值为C_Adj[i][j]的值。
C_Visited[i]用来存储节点是否被访问标记,为0则未标记,为1则已经被标记。默认全部为零。
C_Point表示当前图中的节点数
C_ArcNum表示当前图中的边数

函数成员有:

CC_Graph(int* PointNum, int* arcNum, int* C_Loc);
void DFS(stack s, int Loc);//深度优先遍历

构造函数的三个参数分别表示:点的数量,边的数量,程序开始的节点位置,在构造函数中会提示输入各种信息以完成对图的创建。

DFS即深度优先遍历,需要的参数是一个int类型的空栈和程序开始位置,采用递归的方法完成对图的深度优先遍历输出。

最小生成树Prim算法的实现:

在函数中定义Vis[i]保存i号节点是否被标记,x[i][j]保存最短路径上的边,x[i][j]不等1表示边(i,j)在最短路径上,数值表示被标记的次序,初始化均为0。vis[Loc] 赋值为1;定义Order来表示访问次序,isALL作为主循环的循环条件,在所有节点均被访问后被赋值为1,否则为0.

在主循环中实现以下算法:找到这样的两个顶点 即 i位置已经被访问 且j位置未被访问 ,符合条件的 i只有一个而j可以不止一个,找到path[i][j]的权值最小的一个,visited[j]即对j位置访问,x[i][j]=Order 即把边(i,j)纳入到最短路径中,并确定其被标记的次序,然后Order加一。

输出最小生成树有两种方法:

  • 1.调用CoutMat(x, n)即自定义的用来输出二维数组的算法,更加直观的表示出哪些边在最小生成树中,以及其在其中的次序。
  • 2.输出语句,首先定义tim=0;第一重循环判断条件tim不等于Order,再来两层循环对x数组做遍历,判断如果[i][j] == tim就输出(j,i)并且 tim++;

最短路径Dijkstra算法的实现:

设置Visited[]保存是否被标记,Dis[]保存用户起点到下标号的节点路径的长度,Father[]保存最短路径上的前一个节点。其他类似于Prim算法。
算法主体有三步:
1.更新:找到这样的两个顶点 即 i位置已经被访问 且j位置未被访问 且ij有连接,把(0,i)+(i,j)和(0,j)中的较小者赋给j,并且Father[j] = i即i作为j的前驱节点。 2.访问:每轮在更新Dis后 判断最小未被标记的顶点 对其访问(vis = 1;)
3.重复1、2 步骤直到Visited[]的值全为1为止。

然后是输出语句:
1.输出用户输入位置到其他各个顶点的最短路径长度:
直接一个循环输出Dis数组的前n位即可
2.输出用户输入位置到其他各个顶点的最短路径对应的路线:
首先设置i从0到n的循环每次都将i赋值给VVV作为终点,v是用户输入的起点位置。

while (Father[vvv] != -1 && Father[vvv] != v)
    {
        cout << Father[vvv] << "<--";
        vvv = Father[vvv];
    }

根据Father数组内存的节点的前驱关系输出路线。

五、用户手册:

打开程序后自动提示输入图的顶点数和边数,输入后敲回车会再次提醒输入边的信息(格式为:端点 端点 权值)一组数据内部用空格分隔开,组与组之间用回车区分,代码最后给出了几组测试数据可以直接用于测试。 信息输入完成后,程序会再次提醒输入算法开始的位置,位置应该小于顶点数减一,正确输入后系统直接清屏后显示计算结果.
最先输出的是图的邻接矩阵表示,然后就是深度优先遍历的结果,格式为(点->边->点->),然后是Prim算法输出的最小生成树,依次输出的是最小生成树上的几个边。最后是Dijkstra输出最短路径,首先输出的是指定起点到各顶点的最短路径长度,然后输出每个最短路径对应的路线。

六、代码实现:

代码仅供学习参考,严禁抄袭!!!

#include<iostream>
#include<stack>

using namespace std;
const int MaxSize = 100;
const int MaxNum = 9999;

class CC_Graph {
public:
    int C_Adj[MaxSize][MaxSize], C_Visited[MaxSize];
    int C_PointNum;//顶点数
    int C_ArcNum;//边数
    CC_Graph() {};
    CC_Graph(int n, int e) {};
    CC_Graph(int* PointNum, int* arcNum, int* C_Loc);
    
    void DFS(stack<int> s, int Loc);//深度优先遍历
};


CC_Graph::CC_Graph(int* PointNum, int* arcNum, int* C_Loc)
{
    cout << "输入顶点数和边数:" << endl;
    cin >> *PointNum >> *arcNum;
    C_PointNum = *PointNum;//顶点数
    C_ArcNum = *arcNum;//边数


    for (int i = 0; i < C_PointNum; i++)
        for (int j = 0; j < C_PointNum; j++)
            C_Adj[i][j] = 0;//权值为零则i与j无连接
    for (int i = 0; i < C_PointNum; i++)
        C_Visited[i] = 0;//默认为零表示i未访问过


    int v1, v2, Power;
    cout << "输入边的信息(端点 端点 权值):" << endl;
    for (int i = 0; i < C_ArcNum; i++) {
        cin >> v1 >> v2 >> Power;
        C_Adj[v1][v2] = Power;//power不为零,表示v1、v2有连接且权值为power
        C_Adj[v2][v1] = Power;//无向图中(v1,v2)与(v2,v1)表示的是同一条边
    }
    cout << "输入程序开始的位置:" << endl;
    cin >> *C_Loc;
    while (*C_Loc >= *PointNum) {
        cout << "输入有误 请重新输入:" << endl;
        system("pause");
        cin >> *C_Loc;
    }
    
}





void CC_Graph::DFS(stack<int> s, int Loc) {
    s.push(Loc);
    cout << s.top() ;
    C_Visited[Loc] = 1;
    for (int j = 0; j < C_PointNum; j++) {
        if (C_Adj[Loc][j] != 0 && C_Visited[j] == 0) {
            cout << "—(" << Loc << "." << j << ")—>";
            DFS(s, j);//递归
        }
           
    }
}


//用矩阵的方式输出一个二维数组,更加直观的表示元素间的关系
void CoutMat(int path[MaxSize][MaxSize], int n) {
    cout << "\n   ";
    for (int i = 0; i < n; i++)
        cout << i << "\t";
    cout << "\n   ";
    for (int i = 0; i < n; i++)
        cout << "|" << "\t";
    for (int i = 0; i < n; i++)
    {
        cout << "\n" << i << "--";
        for (int j = 0; j < n; j++)
            cout << path[i][j] << "\t";
    }
    cout << "\n   ";
    for (int i = 0; i < n; i++)
        cout << "|" << "\t";
}



void CC_Prim(int path[MaxSize][MaxSize], int n, int Loc) //n是顶点个数
{
    //  int p[MaxSize];
    //  int n= sizeof(flag)/ sizeof(int);
 //   int Loc = Loc;//开始的元素

    int vis[MaxSize];
    for (int i = 0; i < n; i++)
        vis[i] = 0;
    int x[MaxSize][MaxSize];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            x[i][j] = 0;//初始化为零
    vis[Loc] = 1;

    int Order=1;//表示边被访问的次序

    int isAll = 0;
    while (!isAll)
    {
        int u = 0, w = 0;
        int tmp = MaxNum;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++)//找到path[v][0-MaxSize]最小的权值
            {
                if (vis[i] && !vis[j] && path[i][j] < tmp && path[i][j] != 0)
                {
                    u = j;//保存序号
                    w = i;
                    tmp = path[i][j];//保存权值
                 //   cout << w << "--" << u << endl;
                }
            }
        }
        vis[u] = 1;//把最小权值对应的 点 纳入到最小生成树

        //单步输出 中间过程
  //      {system("cls");CoutMat(x, n);system("pause");}


        x[u][w] = Order;
        Order++;
      // cout << w << "--" << u << endl;

        //跳出循环条件 所有顶点全加入 
        isAll = 1;
        for (int i = 0; i < n; ++i)
        {
            if (vis[i] == 0)//有顶点未加入
                isAll = 0;
        }
    }

    //输出
    cout << "用边表示:\n";
    int tim=1;//次序
    while(tim != Order){
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++) 
        {
            if (x[i][j] == tim ){
                cout << "—(" << j << "." << i << ")—>" ;
                tim++;
            }
        }
    }
 //   cout << "\n矩阵表示:";
//    CoutMat(x, n);
}


void CC_Dijkstra(int path[MaxSize][MaxSize], int n, int Loc)//n是顶点个数
{

    int v = Loc;  //开始的元素
 //   stack<int> SS[MaxSize];//用来存储路径

    int Father[MaxSize];//用来存储当前节点在最短路径上的 前驱
    for (int i = 0; i < n; i++)
        Father[i] = -1;
    int vis[MaxSize];//visited  用来判断是否已经加入最短路径的判断
    for (int i = 0; i < n; i++)
        vis[i] = 0;
    int Dis[MaxSize];//Distance  对每个i  用来保存v到i的最短路径长度
    for (int i = 0; i < n; i++)
        Dis[i] = MaxNum;

    Dis[v] = 0;
    vis[v] = 1;
    Father[v] = 98765;


    int isAll = 0;
    while (!isAll) {
        int u = 0, w = 0;
        int tmp = MaxNum;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++)//找到path[v][0-MaxSize]最小的权值
            {      //如果i位置已经被访问 且j位置未被访问 且ij有连接
                if (vis[i] && !vis[j] && path[i][j] != 0)//把与之相连的点之间的权值全部 在作比较后赋给Dis
                {
                    Dis[j] = min(path[i][j] + Dis[i], Dis[j]);//把(0,i)+(i,j)和(0,j)中的较小者赋给j
                    Father[j] = i;
                }
                //每轮在更新Dis后 判断最小未被标记的顶点  对其访问(vis = 1;)
                int xxx = -1, kkk = MaxNum;
                for (int k = 0; k < n; ++k)//最小未被标记的顶点做标记
                {
                    if (vis[k] == 0 && Dis[k] < kkk) {
                        kkk = Dis[k];
                        xxx = k;
                    }
                }

                if (xxx != -1) {//xxx!=-1才存在未被标记的顶点,才对其访问
                    vis[xxx] = 1;
                    //   Father[]
                }

                /*{
                    u = j;//保存序号
                    w = i;
                    tmp = path[i][j];//保存权值
                 }*/
            }
        }
        //      Dis[u] = min(tmp + Dis[w], Dis[u]);
       //       vis[u] = 1;//把最小权值对应的 点 标记已访问


              //跳出循环条件 所有顶点全被标记
        isAll = 1;
        for (int i = 0; i < n; ++i)
        {
            if (vis[i] == 0)//有顶点未被标记
                isAll = 0;
        }
    }


    //输出路径长度
 //   for (int i = 0; i < n; i++)
 //       cout << "路" << Loc << "->" << i << "长" << Dis[i] << " ";


    cout << "各路径长度\n" << Loc << "->";
    for (int i = 0; i < n; i++)
        cout << i << "\t";
    cout << "\n   ";
    for (int i = 0; i < n; i++)
        cout << "|" << "\t";
    cout << "\n   ";
    for (int i = 0; i < n; i++)
        cout << Dis[i] << "\t";


    //以下利用Father[]输出路径的过程,可封装为递归的函数 从而正序输出
    int v1 = v;//程序输入的起点


    for (int i = 0; i < n ; ++i)
    {
        if (i==v)continue;//不输出自己到自己的路径

    int vvv = i;//自定义的终点
    cout << "\n" << v << "到" << vvv << "的最短路径对应的路线:\t";
    cout << vvv << "<--";
    while (Father[vvv] != -1 && Father[vvv] != v)
    {
        cout << Father[vvv] << "<--";
        vvv = Father[vvv];
    }
    cout << v;
    }
}

int main() {


    int PointNum=0, arcNum=0, C_Loc=0;
    CC_Graph G(&PointNum, &arcNum, &C_Loc);

    system("cls");
    cout << "构建出的图为:" << endl;
    CoutMat(G.C_Adj, PointNum);
    cout << "\n\n\n******深度优先遍历:******" << endl;
    stack<int> s;
    G.DFS(s, C_Loc);
    cout << "\n\n\n******Prim算法:******" << endl;//输出的是最小生成树中包含的边
    CC_Prim(G.C_Adj, PointNum, C_Loc);
    cout << "\n\n\n******Dijkstra算法:******" << endl;//输出的是Loc端点到其他端点的最小距离,以及对应的路线
    CC_Dijkstra(G.C_Adj, PointNum, C_Loc);
    cout << "\n\n\n\n程序结束";
    // DFS(0, G, s);


    return 0;
}
感谢支持

七、测试数据及测试结果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

posted @ 2022-03-05 15:59  Cheney822  阅读(150)  评论(0编辑  收藏  举报