记第一次大作业:校园导航系统

前言

大二迎来了人生中的第一次大作业,因为上个学期转专业过来,学C的基础不扎实,凑巧这学期又初识了Java,可以算是我第一门系统学习的语言,本来是想用Java进行设计的,没想到我们的数据结构老师十分的坚持,一定要用C语言进行实现,那就没办法啦,就借着这个机会对C语言进行复习一下吧!

背景

因为我们老师的要求是数据库与数据结构结合进行实现,想着简简单单搞个系统得了。但看到课设表上一水的管理系统就决定做点不一样的出来。当公布这学期要进行课设时,我们已经学到了树,我对巨佬们的算法啧啧称奇。学到哈夫曼树的时候就已经有寻路的想法,后面学图就是如虎添翼了。

过程

基础

大学校园广而大,其中又有很多闻名的景色值得前去观赏,更是吸引很多校外人员前来观赏以及刚刚入学新生对校园不够了解路线及所在地。

如若我们来之前没有做够足够的准备,那么当我们到来的时候就会给我们带来极大的困扰。咸阳师范学院校内平面图
为了方便校内外人员,以及新生学生的开学观光,我们计划做出一份校园旅游的系统提供给大家,给每一个人带来方便与快捷。

思路

我利用咸阳师范的平面图抽象一些重要的点即可得到一份 无向带权图 ,以此为基础实现校园导航系统。抽象出来的无向网
对于上图的每一个顶点来说,他们的信息我分为了3个部分4个项:即顶点的 下标 、顶点的 名称 以及顶点的 简要介绍和详细介绍 。最终利用一个 顶点数组 进行存储以后续使用。

至于邻接矩阵的结构体,这里仅在其中定义定义了一个int类型的 权值,下文就不再提及了。

//邻接矩阵
typedef struct VexAdj {
    //权值
    int Adj;
} AdjMatrix[MAX_NUM][MAX_NUM];

图与顶点的结构体
因为在校园中,不像在校外,每一条路都是无向的带权路径,因此使用无向网是最好的选择。但因为校园的特殊性,面积必定会不断扩增,因此利用 无向网的邻接矩阵 无疑是最好的选择。当我们将无向网抽象出来后,利用两个数组建立后的邻接矩阵应如下图:
构造好的无向邻接矩阵
当无向网与邻接矩阵建立好后,就可以正式拉开校园导航系统的帷幕了!

各项函数的建立

功能梳理

在这里插入图片描述

1.图的创建

此时我们要注意,因为我们是利用无向图进行实现,所以每条路径我们要存储两遍:并且在顶点下标方面,为了操作方便,我们跳过了下标为0的情况,并且我们多创建了一个空间将总顶点数设为13确保所有顶点能够全部录入。

此处为图的初始化,可以考虑用文件进行读写,但因为我那一方面的知识不牢固,就 浅浅地摆一下 这样吧。

注意
这里值得注意的是,因为c语言中不存在java的String类型,多数采用char类型的数组进行实现,我们这里需要导入包调用 strcpy() 函数进行字符串的写入。
char *strcpy(char *dest, const char *src)
用于把 src 所指向的字符串复制到 dest。
使用时需要导入<string.h>

#include<string.h>
void CreateDG(MyGraph *G) {
    //设置图的顶点数与边数
    G->VexNum = 13;
    G->ArcNum = 37;
    //对各个顶点的信息进行循环录入
    G->ves[1].position = 1;
    strcpy(G->ves[1].name, "JiaoXueLouQun");
    strcpy(G->ves[1].introduction, "Teaching building,which have one,two,three and five building");
    strcpy(G->ves[1].Super_introduction,
           "Building One houses the School of History, Politics and Management,Building 2 has the School of English Communication,Building three houses the schools of Education, Computer Science and Physics.");
    G->ves[2].position = 2;
    strcpy(G->ves[2].name, "ChuangXinChuangYeDaLou");
    strcpy(G->ves[2].introduction, "Innovation and entrepreneurship building");
    strcpy(G->ves[2].Super_introduction,
           "The Innovation and Entrepreneurship Building is a base for students preparing for entrepreneurship to study and to carry out innovation and entrepreneurship");
    G->ves[3].position = 3;
    strcpy(G->ves[3].name, "Library");
    strcpy(G->ves[3].introduction, "It's a place of learning");
    strcpy(G->ves[3].Super_introduction,
           "The third to tenth floors of the library are places to borrow books for study, and the eleventh and twelfth floors are places for students to prepare for the postgraduate entrance exam");
    G->ves[4].position = 4;
    strcpy(G->ves[4].name, "XueXinYuan");
    strcpy(G->ves[4].introduction, " It's the biggest canteen in the school");
    strcpy(G->ves[4].Super_introduction,
           "It's the biggest canteen in the school,With a two-level dining area, there is a huge choice of food");
    G->ves[5].position = 5;
    strcpy(G->ves[5].name, "GongYuQun");
    strcpy(G->ves[5].introduction, "It s the place where students sleep after class");
    strcpy(G->ves[5].Super_introduction,
           "It is the largest dormitory area of the school, with two boys' dormitories and six girls' dormitories");
    G->ves[6].position = 6;
    strcpy(G->ves[6].name, "Ping-Pang");
    strcpy(G->ves[6].introduction, "It is a place for table tennis practice and competition");
    strcpy(G->ves[6].Super_introduction,
           "It's the main venue for table tennis competition in the school and also the place for other staff to exercise");
    G->ves[7].position = 7;
    strcpy(G->ves[7].name, "TiYuChang");
    strcpy(G->ves[7].introduction, "This place where students attend classes and play sports");
    strcpy(G->ves[7].Super_introduction,
           "Inside the stadium there are basketball courts, football fields, badminton courts and tennis courts where the students of the school have classes");
    G->ves[8].position = 8;
    strcpy(G->ves[8].name, "JuWeiYuan");
    strcpy(G->ves[8].introduction, "The nearest canteen to the attached middle school");
    strcpy(G->ves[8].Super_introduction,
           "It's the cleanest school, the cheapest to eat, and the most middle school students' canteen");
    G->ves[9].position = 9;
    strcpy(G->ves[9].name, "ShiYanLou");
    strcpy(G->ves[9].introduction, "It's the teaching building where experiments are done");
    strcpy(G->ves[9].Super_introduction,
           "The laboratory building is a classroom where most students of the School of Chemistry and physics conduct experiments");
    G->ves[10].position = 10;
    strcpy(G->ves[10].name, "ShiYuanFuZhong");
    strcpy(G->ves[10].introduction, "It's the secondary school attached to the school");
    strcpy(G->ves[10].Super_introduction,
           "The High School attached to the Normal School is an affiliated middle school with students of all grades");
    G->ves[11].position = 11;
    strcpy(G->ves[11].name, "12A/12B");
    strcpy(G->ves[11].introduction, "Two dormitory buildings ");
    strcpy(G->ves[11].Super_introduction,
           "It's the closest boys' dormitory from West Gate of the school, with six rooms in a particularly good environment");
    G->ves[12].position = 12;
    strcpy(G->ves[12].name, "XiaoNeiCaiNiao");
    strcpy(G->ves[12].introduction, "It's a place to send the express");
    strcpy(G->ves[12].Super_introduction,
           "It's a place where students mail and receive deliveries, including all kinds of deliveries");
    //将每个边的权值设为最大
    for (int i = 1; i < G->VexNum; i++) {
        for (int j = 1; j < G->VexNum; j++) {
            G->arcs[i][j].Adj = INFINITY;
        }
    }
    //录入各个边的信息
    G->arcs[1][2].Adj = 3;
    G->arcs[1][4].Adj = 5;
    G->arcs[1][12].Adj = 3;
    G->arcs[2][1].Adj = 3;
    G->arcs[2][3].Adj = 4;
    G->arcs[3][2].Adj = 4;
    G->arcs[3][4].Adj = 2;
    G->arcs[3][9].Adj = 1;
    G->arcs[4][1].Adj = 5;
    G->arcs[4][3].Adj = 2;
    G->arcs[4][5].Adj = 3;
    G->arcs[4][6].Adj = 2;
    G->arcs[4][12].Adj = 2;
    G->arcs[5][4].Adj = 3;
    G->arcs[5][6].Adj = 3;
    G->arcs[5][7].Adj = 3;
    G->arcs[6][4].Adj = 2;
    G->arcs[6][5].Adj = 3;
    G->arcs[6][7].Adj = 2;
    G->arcs[6][8].Adj = 2;
    G->arcs[7][5].Adj = 3;
    G->arcs[7][6].Adj = 2;
    G->arcs[7][8].Adj = 3;
    G->arcs[7][11].Adj = 1;
    G->arcs[8][6].Adj = 2;
    G->arcs[8][7].Adj = 3;
    G->arcs[8][9].Adj = 2;
    G->arcs[8][10].Adj = 2;
    G->arcs[9][3].Adj = 1;
    G->arcs[9][8].Adj = 2;
    G->arcs[9][10].Adj = 2;
    G->arcs[10][8].Adj = 2;
    G->arcs[10][9].Adj = 2;
    G->arcs[11][7].Adj = 1;
    G->arcs[12][1].Adj = 3;
    G->arcs[12][4].Adj = 2;
}

以下为用户功能实现部分

2.顶点下标的获取

这个函数仅仅是对传入的顶点下标判断在图中的位置,实际因为我们置空0下标已经将下标合法化,但谁有知道用户会输进来什么东西呢?整体的函数就相当于一类 合法性判断 吧,在下文的CRUD以及重头戏查找路径中都能用到,算是一个铺垫吧!

int LocateVex(MyGraph *G, int v) {
    //i因为是需要记录使用的变量,因此定义在外部
    int i = 1;
    //遍历图的一维顶点数组,找到顶点v的下标
    for (; i < G->VexNum; i++) {
        if (G->ves[i].position == v) {
            //如果已经找到直接退出循环,不需要再次进入循环
            break;
        }
    }
    //如果找不到,输出提示语句,返回-1
    if (i > G->VexNum) {
        printf("No such vertex.\n");
        return -1;
    }
    //寻找成功返回v的下标
    return i;
}

3.迪杰斯特拉算法

迪杰斯特拉算法是 求解从顶点出发到其余顶点的最短路径算法 。该算法按照最短路径长度递增的顺序,产生某一顶点到其余各顶点的所有最短路径。

其实一开始本来是想用弗洛伊德算法的,但在不断的学习中,慢慢感觉迪杰斯特拉可能更加符合我对这个功能的预期,主要原因有:

  • 迪杰斯特拉算法是 给出一个出发点,可算出该出发点到所有其它点的最短距离还有具体路径 ;而弗洛伊德算法是算出 所有每对顶点间的最短路径 ,其会利用三层循环创建出一个二维数组,通过对二维数组的不断更新,从而得出最短路径。

  • 弗洛伊德算法因为使用了三层循环进行实现,因此其实现的时间复杂度达到了O(n3),当所处的图逐渐复杂的时候,算法会显得吃力,因此选择弗洛伊德算法实现两点间的最短路径。

在该算法中,主要利用了3个较为重要的数组:

  • D[ ]数组 :表示已经找到的从开始点v0到终点vi的当前最短路径长度。其初值为:如果从v0到vi由弧,则D[i]为弧的权值,否则为∞。
  • P[ ]数组 :主要为了记录从v0出发到其余各点的最短路径(顶点序列)。其初值为:如果从v0到vi有弧,则P[v]为(v0,vi),否则P[i]为空。
  • visited[ ]数组 :用于表示图中的每个顶点是否被访问过。该数组的初值为0(假),一旦顶点vi被访问过,则置visited[i]为1(真),表示该顶点已被访问。

迪杰斯特拉算法的流程图

代码实现

void Dijkstra(MyGraph *G, int vx, int P[G->VexNum], int D[G->VexNum]) {
    //为各个顶点配置一个标记值,用于确认该顶点是否已经找到最短路径
    int visited[G->VexNum];
    //对各数组进行初始化
    for (int v = 1; v < G->VexNum; v++) {
        visited[v] = 0;
        D[v] = G->arcs[vx][v].Adj;
        P[v] = 0;
    }
    //由于以vx位下标的顶点为起始点,所以不用再判断
    D[vx] = 0;
    //将由起点能直接到达的顶点的路径设为起点
    for (int j = 1; j < G->VexNum; ++j) {
        if (G->arcs[vx][j].Adj != INFINITY) {
            P[j] = vx;
        }
    }
    visited[vx] = 1;
    //途径点
    int k = 0;
    for (int i = 1; i < G->VexNum; i++) {
        int min = INFINITY;
        //选择到各顶点权值最小的顶点,即为本次能确定最短路径的顶点
        for (int w = 1; w < G->VexNum; w++) {
            if (!visited[w]) {
                if (D[w] < min) {
                    k = w;
                    min = D[w];
                }
            }
        }
        //设置该顶点的标志位为1,避免下次重复判断
        visited[k] = 1;
        //对v0到各顶点的权值进行更新
        for (int w = 1; w < G->VexNum; w++) {
            if (!visited[w] && (min + G->arcs[k][w].Adj < D[w])) {
                D[w] = min + G->arcs[k][w].Adj;
                //记录各个最短路径上存在的顶点
                P[w] = k;
            }
        }
    }
}

4.展示最短路径

算是上述算法的辅助算法,用于将通过迪杰斯特拉算法产生的 路径数组P[]权值数组D[] 进行输出调用的函数。在最终的主函数页面进行调用实现最短路径的输出。

其主要功能是录入一个起始位置的下标,寻找P数组中从起始位置到达各顶点的最短路径,但要注意的是,为了保证最短路径的准确性,我将最短路径的顺序反过来了。

举例:就好似起点为宿舍群,原本路径应为宿舍群-学馨苑-教学楼群,但在P数组总存储的应为教学楼群-学馨苑-宿舍群,那么当我们需要使用的时候我们就需要对该数组的数据进行一次处理,处理措施就是另设一个数组去存储当前路径信息,输出的时候反向输出调用就可以了。
例子图

void AllPath(MyGraph G, int P[], int D[]) {
    int start = 0;
    //获取起点
    printf("Please tell me where you are ?\n");
    scanf_s("%d", &start);
    //调用迪杰斯特拉获取到所有顶点的最短路径
    Dijkstra(&G, start, P, D);
    for (int i = 1; i < G.VexNum;) {
        //两种情况
        if (i != (start)) {
            //当起点与终点不是同一位置的时候,打印提示语句,询问终点下标
            printf("If you want go to %d %s , you can go this way : ", i, G.ves[i].name);
            //此时创建一个临时变量,因为下面i要发生变化,但是一次路径的终点的不能发生变化的,则创建另一个变量进行存储更改
            int j = i;
            //因为在运用的迪杰斯特拉算法中最后出来的P路径先输出的是终点,在这里创建一个数组对路径进行存储,再次反向打印即可获得正常且正确的路径
            //n是外部变量,要记录最终路径的数组中一共存入了多少个顶点
            int p[G.VexNum - 2], n = 0;
            //对最终路径初始化
            for (int k = 0; k < G.VexNum - 2; ++k) {
                p[k] = INFINITY;
            }
            //将初始路径数组存入最终路径数组,n要发生变化,j在每一次存入后要进行更新
            while (P[j] != 0) {
                p[n] = P[j];
                n++;
                j = P[j];
            }
            //遍历并打印最终路径
            while (n != 0) {
                printf("%d %s - ", p[n - 1], G.ves[p[n - 1]].name);
                n--;
            }
            //打印终点
            printf("%d %s.\n", i, G.ves[i].name);
            //打印路径长度
            printf_s("The length of path is : %d m.\n", D[i] * 100);
            i++;
        } else {
            //如果当前的终点下标与起点下标相同,则跳过当前终点,进入下一次循环
            i++;
        }
    }
}

5.获取所有路径

本函数主要使用到 递归 的思想,同样需要使用到上述的visited数组及路径数组。

在函数的上半部分,是对路径数组的判断,即当路径数组的最后一个顶点下标为终点下标时,则输出当前路径及其 总权值

而在函数的下半部分则是一个循环,其目的是 在确保最终路径是简单路径的前提下不断填充路径数组直到图中的所有顶点全部被访问
流程图

void path(MyGraph *G, int start, int end, int k, int visited[], int p[]) {
    //用来记录每对顶点之间的所有路径的条数
    int s;
    //找到一条路径
    if (p[k] == end) {
        int length = -INFINITY;
        int t = start;
        for (s = 0; s < k; s++)/*输出一条路径*/{
            length = length + G->arcs[t][G->ves[p[s]].position].Adj;
            printf("%d  %s --> ", G->ves[p[s]].position, G->ves[p[s]].name);
            t = G->ves[p[s]].position;
        }
        length = length + G->arcs[t][end].Adj;
        printf("%d  %s .(The path of length is : %d.)\n", G->ves[p[s]].position, G->ves[p[s]].name, length * 100);
    }
    s = 1;
    while (s < G->VexNum) {
        //保证找到的是简单路径
        if (s != start) {//当vk与vs之间有边存在且vs未被访问过
            if (G->arcs[p[k]][s].Adj != INFINITY && visited[s] == 0) {
                visited[s] = 1;/*置访问标志位为1,即已访问的*/
                p[k + 1] = s;/*将顶点s加入到v数组中*/
                path(G, start, end, k + 1, visited, p);/*递归调用之*/
                visited[s] = 0;/*重置访问标志位为0,即未访问的,以便该顶点能被重新使用*/
            }
        }
        s++;
    }
}

6.展示所有路径

主要用于算法5的前期准备(包括起点、终点的录入以及提示语句的打印)及实现调用,是新鲜出炉的 辅助函数 一枚~~

void SearchAllPath(MyGraph *G) {
    int visited[G->VexNum];/*用来记录各顶点被访问的情况*/
    int p[G->VexNum];/*用来存放路径上的各顶点*/
    int start, end;
    printf("Where are you start ?\n");
    scanf_s("%d", &start);
    printf("Where are you want go?\n");
    scanf_s("%d", &end);
    printf("All path of from %s to %s have :\n", G->ves[start].name, G->ves[end].name);/*输出出发景点和目地景点的名称*/
    p[0] = start;
    for (int k = 0; k < G->VexNum; k++) {
        //初始化各顶点的访问标志位,即都为未访问过的
        visited[k] = 0;
    }
    path(G, start, end, 0, visited, p);
}

7.介绍结点以及打印校园地图

两个摸鱼函数,不进行过多介绍了。后期也许可能的话,会去学习EasyX库进行一个前端的小小设计(不过这都是后话了)。开摆!

void IntroductionView(MyGraph G, bool t) {
    //t为true展示简单介绍
        if (t) {
        printf_s("-----------------------------------------------------------------------------------------\n");
        for (int i = 1; i < G.VexNum; i++) {
            printf("%d.  %s   The introduction is : %s\n", G.ves[i].position, G.ves[i].name, G.ves[i].introduction);
        }
        printf_s("-----------------------------------------------------------------------------------------\n");
    } else {//t为false展示详细介绍
        printf("Details of each attraction are as follows :\n");
        for (int i = 1; i < G.VexNum; i++) {
            printf("%d  %s", G.ves[i].position, G.ves[i].name);
            printf_s("The introduction is : %s\n", G.ves[i].introduction);
            printf_s("The super introduction is : %s\n", G.ves[i].Super_introduction);
            printf_s("------------------------------------------------\n");
        }
    }
}

void ShowMap() {
    // \表示转义符号,用两个\\让其表示单纯的反斜杠代表路径
    printf("\n----------------------------------------------------------------------------------------------------------------------\n"
           "11.12A/12B----7.TiYuChuang----5.GongYuQun\n"
           "                                          |        \\               /             |              12.XiaoNeiCaiNiao\n"
           "                                          |          \\           /               |                /              \\\n"
           "                                          |      6.PingPang          |               /                 \\\n"
           "                                          |             /                        |             /                    \\\n"
           "                                          |           /                    4.XueXinYuan       1.JiaoXueLouQun\n"
           "                                     8.JuWeiYuan                         |                               / \n"
           "                                        /              \\                               |                             /\n"
           "                                      /                 \\                              |                           /\n"
           "      10.ShiYuanFuZhong----9.ShiYanLou----3.Library----2.ChuangXinChuangYeDaLou\n"
           "----------------------------------------------------------------------------------------------------------------------\n");
}

void Menu() {
    printf("\n=======================================================\n"
           "||              Welcome to XianYang Normal University!                                   ||\n"
           "||       1.Select the path to visit        2.Select the shortest path                 ||\n"
           "||       3.Introduce the vision            4.Select the super_introduction      ||\n"
           "||       5.Show me the map                6.Alter the graph                                  ||\n"
           "||                        Any number bigger than 6:break                                         ||\n"
           "=======================================================\n");
}

好像还有个菜单函数没说,应该不会被发现吧,溜了溜了~

接下来进行管理员功能的介绍!

8.新增一个顶点或一条边

其实没什么创新点,主要需要注意的就是:

  1. 顶点数必须小于49时才能进行创建,因为一开始自定义了一个声明,当等于49的时候依旧进行创建可能无法保证信息完整,容错率会降低
#define MAX_NUM 50
  1. 对邻接矩阵进行更新的时候需要对行和列同时进行更新,每创建一条边,总边数要加2
void CreateNewVes(MyGraph *c) {
    //对图中的顶点数进行判断,当顶点数等于49时,该图已满不能再进行添加操作
    if (c->VexNum < 49) {
        //当图中的顶点数小于49时
        //增加顶点数
        c->VexNum++;
        //此时从顶点数-1的位置开始插入新的顶点
        printf_s("Please enter the information of Vertex you want to add : \n");
        c->ves[c->VexNum - 1].position = c->VexNum - 1;
        printf_s("The %d vertex's name is : \n", c->VexNum - 1);
        scanf("%s", c->ves[c->VexNum - 1].name);
        printf_s("The %d vertex's introduction is : \n", c->VexNum - 1);
        scanf("%s", c->ves[c->VexNum - 1].introduction);
        printf_s("The %d vertex's Super_introduction is : \n", c->VexNum - 1);
        scanf("%s", c->ves[c->VexNum - 1].Super_introduction);
        //对邻接矩阵的行和列进行更新
        for (int i = 1; i < c->VexNum; ++i) {
            c->arcs[c->VexNum - 1][i].Adj = INFINITY;
            c->arcs[i][c->VexNum - 1].Adj = INFINITY;
        }
        //录入各个边的信息
        for (int i = 1; i < c->VexNum - 1; i++) {
            printf_s("What is the weight of the edge between vertex %s and vertex %s (0 is none) ? \n",
                     c->ves[c->VexNum - 1].name, c->ves[i].name);
            //录入权值并加入邻接矩阵,因为是无向网,此时需要添加两条边,边数也要跟着加2
            int w = 0;
            while (scanf_s("%d", &w) != 0) {
                if (w != 0) {
                    c->arcs[c->VexNum - 1][i].Adj = w;
                    c->arcs[i][c->VexNum - 1].Adj = w;
                    c->ArcNum += 2;
                    printf_s("The adj of from %s to %s is %d\n", c->ves[c->VexNum - 1].name, c->ves[i].name,
                             c->arcs[c->VexNum - 1][i].Adj);
                    break;
                }
                break;
            }
        }
        printf("You create a new vertex.\n");
    } else {
        //当图中的顶点数等于49时
        printf_s("The vertices of the graph are full and no new vertices can be inserted!\n");
    }
}

int CreateNewArc(MyGraph *G) {
    //m 增加的边的起点  n 增加的边的终点  distance 增加的边的权值
    int m, n, distance;
    printf_s("Please enter the start and end point number and weight of the edge.\n");
    scanf_s("%d %d %d", &m, &n, &distance);
    //对输入的起点终点及权值进行合法性检验
    while (m < 0 || m > G->VexNum || n < 0 || n > G->VexNum || distance <= 0) {
        printf("Input error, please re-enter: \n");
        scanf_s("%d %d", &m, &n);
    }
    //判断m结点是否合法
    if (LocateVex(G, m) < 0) {
        printf("Vertex %d has been deleted or does not exist . \n", m);
        return -1;
    }
    //判断n结点是否合法
    if (LocateVex(G, n) < 0) {
        printf("Vertex %d has been deleted or does not exist . \n", n);
        return -1;
    }
    //无向网需要对一条边录入两次,图的边数加2
    G->arcs[m][n].Adj = distance;
    G->arcs[n][m].Adj = distance;
    G->ArcNum = G->ArcNum + 2;
    printf("You created a arc.\n");
    return 1;
}

9.删除一个顶点或者一条边

需要注意的是:

  • 删除顶点时,因为一下无法确定要删除的顶点具有几条边,需要声明一个变量去记录删除边的数量,最后的总边数等于原来的总边数减上删除边的数量。
  • 删除顶点时,邻接矩阵需要进行更新,因为涉及的边数较多,我们选择将下侧的数据向上覆盖,并且针对行和列都要操作。
  • 删除单个的边时,即相当于更改权值,但要更改两次,总边数也得减2
int DeleteVes(MyGraph *G) {
    //v : 需要删除的顶点的位置  m :需要删除的顶点的下标
    int v, m;
    //对图中的顶点数进行判断,当图中的顶点数小于等于0时,操作不合法,返回-1
    if (G->VexNum <= 0) {
        printf("There are no vertex in the graph.\n");
        return -1;
    }
    //打印提示语句,录入需要删除顶点的位置
    printf("Please enter the vertex number you want to delete below: \n");
    scanf_s("%d", &v);
    //当顶点位置小于0或者顶点位置大于顶点数打印提示语句并进行再次录入
    while (v < 0 || v > G->VexNum) {
        printf("Input error. Please enter again!\n");
        scanf_s("%d", &v);
    }
    //利用LocateVex方法获取顶点的下标
    m = LocateVex(G, v);
    //如果m小于0(即为-1)时,说明在该图c中找不到需要寻找的顶点,删除失败,打印提示语句,返回-1
    if (m < 0) {
        printf("This vertex does not exist in the diagram.\n");
        return -1;
    }
    //声明一个变量记录删除该顶点时删除的边数
    int deletedNum = 0;
    //如果该顶点对于图中所拥有的某些顶点有边,则将该边的权值设为INFINITY,代表不存在或删除,删除的边数++
    for (int i = 1; i < G->VexNum; ++i) {
        if (G->arcs[m][i].Adj != INFINITY) {
            deletedNum++;
        }
    }
    //令该顶点下侧的边向上移动
    //行
    for (int i = m; i < G->VexNum - 1; i++) {
        for (int j = 1; j <= G->VexNum; j++) {
            G->arcs[i][j] = G->arcs[i + 1][j];
        }
    }
    //列
    for (int i = m; i < G->VexNum - 1; i++) {
        for (int j = 1; j < G->VexNum; j++) {
            G->arcs[j][i] = G->arcs[j][i + 1];
        }
        }
    //将该顶点的位置设为-1,意为已删除
    G->ves[v].position = -1;
    //该图的顶点数--
    G->VexNum--;
    //该图的边数等于原有的边数减上删除的边数
    G->ArcNum -= deletedNum;
    //返回1,代表删除成功
    return 1;
}

int DeleteArc(MyGraph *G) {
    //v0 要删除边的起点 m 要删除边的起点的下标
    int m, v0;
    //v1 要删除边的终点 n 要删除边的终点的下标
    int n, v1;
    //如果图c中的边数小于等于0,代表图中已经不存在边了,打印提示语句,直接返回-1,删除失败
    if (G->ArcNum <= 0) {
        printf("There is no edge in the picture, and it cannot be deleted.\n");
        return -1;
    }
    //录入想要删除的边的起点与终点的下标
    printf("Enter the numbers of the start and end points of the edge you want to delete: \n");
    scanf_s("%d %d", &v0, &v1);
    //判断m节点是否存在于图c
    m = LocateVex(G, v0);
    if (m < 0) {
        printf_s("Vertex %d does not exist. \n", v0);
        return -1;
    }
    //判断n节点是否存在于图c
    n = LocateVex(G, v1);
    if (n < 0) {
        printf_s("Vertex %d does not exist. \n", v1);
        return -1;
    }
    //进行删除操作
    G->arcs[m][n].Adj = INFINITY;
    G->arcs[n][m].Adj = INFINITY;
    G->ArcNum = G->ArcNum - 2;
    return 1;
}

main函数

注意

  • 我用的环境是Jerbains家的CLion,该环境在调试的时候不会实时打印, 语句环境不一样的前两句可以省略
  • 在c语言中对字符串的比较我这里用的是 strcmp() 函数,当两字符相等时返回值是0,但要注意原字符数组需要有 \0 结束字符,否则你录入的第一位可能会一直是 \000 ,这样就无法进行比较了,也可以再加上 %*c 吸收回车键。
int main() {
    //Clion在调试时没有办法进行实时打印,加上这一句的话,可以让Clion在调试时进行实时打印
    setbuf(stdout, 0);

    //创建图部分
    MyGraph G;
    CreateDG(&G);
    // 记录顶点 0 到各个顶点的最短的路径
    int P[G.VexNum + 1];
    // 记录顶点 0 到各个顶点的总权值
    int D[G.VexNum + 1];
    //设置标志flag : 当flag为真的时候,进行景点的简要介绍;当为False的时候进行景点的详细介绍。
    bool flag;
    //用户在菜单的选择
    int select;
    Menu();
    printf("Please enter your select: \n");
    scanf_s("%d%*c", &select);
    //清屏操作
    system("cls");
    while (select <= 6) {
        switch (select) {
            case 1:
                //功能1 输入一个起点,输出到各顶点的所有最短路径
                AllPath(G, P, D);
                break;
            case 2:
                //功能2 输入一个起点及终点,输出两顶点间的所有路径
                //获取起点
                SearchAllPath(&G);
                break;
            case 3:
                //功能3 景点的简要介绍
                flag = true;
                IntroductionView(G, flag);
                break;
            case 4:
                //功能4 景点的详细介绍
                flag = false;
                IntroductionView(G, flag);
                break;
            case 5:
                //功能5 在命令行窗口直接展示学校的详细地图
                ShowMap();
                break;
            case 6: {
                //功能6 管理员操作,修改图的信息
                //提前创建输入的账号与密码,下面进行录入比较
                char enterAdmin[12];
                char enterPassword[9];
                printf_s("Please enter the admin 's username.\n");
                // %*c 的作用是想吸收掉录入时用户习惯的回车键,以免出现数据录入错误;即使用户不录入回车键,不影响最终结果
                scanf("%s%*c", enterAdmin);
                printf_s("Please enter the admin 's password.\n");
                scanf("%s%*c", enterPassword);
                if ((strcmp(username, enterAdmin) != 0) || (strcmp(password, enterPassword) != 0)) {
                    //当输入的账户密码与原先拥有的账户密码不相等时,输出提示语句,并直接退出该操作
                    printf("Illegal user or wrong password!\n");
                    break;
                } else {
                    //当输入的账户密码相符,则进行管理员操作展示,
                    do {
                        printf_s("\n--------------------------------------------------------------------------------\n"
                                 "       Hello administrator, what do you want to do?\n"
                                 "-----------------------------Class Vertex----------------------------------\n"
                                 "   >>>1.Add one vertex.                       >>>2.Delete one vertex.\n"
                                 "------------------------------Class Arc-------------------------------------\n"
                                 "   >>>3. Add one arc.                            >>>4.Delete one arc.\n"
                                 "                      >>>Any Number bigger than 4:break                    \n"
                                 "--------------------------------------------------------------------------------\n");
                        scanf_s("%d", &select);
                        switch (select) {
                            case 1:
                                //管理员操作功能1 创建一个新的结点
                                CreateNewVes(&G);
                                break;
                            case 2:
                                //管理员操作功能2 删除一个结点
                                DeleteVes(&G);
                                break;
                            case 3:
                                //管理员操作功能3 创建一条新的边
                                CreateNewArc(&G);
                                break;
                            case 4:
                                //管理员操作功能4 删除一条边
                                DeleteArc(&G);
                                break;
                            default:
                                break;
                        }
                        //
                    } while (select <= 4);
                }
                break;
            }
            default:
                break;
        }
        //输出提示语句,提示用户是否继续进行操作
        printf("\nAgain?(0:break/else:again)\n");
        char again;
        scanf("%*c%c", &again);
        for (;;) {
            //利用isdigit方法对输入的选择进行合法性检验 :若为数字,则返回0,继续进行判断;若非数字,返回非0,进行提示并再次进行输入
            if (isdigit(again) != 0) {
                //当用户输入为数字时
                if (again == '0') {
                    //若用户输入数字则直接去向end标识符所在的位置(即直接退出)
                    goto end;
                } else {
                    //若用户输入不为0,则清屏再次出现菜单询问用户选择,进行下一次循环
                    system("cls");
                    Menu();
                    printf("Please enter your select: \n");
                    scanf_s("%d", &select);
                    break;
                }
            } else {
                //当用户输入非数字时,进行提示,并再次进行录入
                printf("Please enter number!\n");
                scanf("%c%*c", &again);
            }
        }
    }
    //end标识符所在位置,输入结束语句,程序终止
    end:
    printf_s("Enjoy your trip!");
    return 0;
}

结束语

总的来说呢,这次课设从11月初到11月中旬,也是断断续续的做,断断续续的改,虽然强度不大,但也算是我人生中第一次的课设了。

希望这对我来说仅仅是个开始!

  • 14
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值