西南交通大学【数据结构实验课程设计】

1.课程设计内容与要求

用字符文件提供数据建立DAG(有向无环图)合适的存储结构。编写程序,输出所有可能的拓扑排序序列。要求输出的拓扑排序结果用顶点序号或字母表示。输出结果需存于字符文件。输出结果中应显示全部拓扑排序序列的数目。如果DAG存在环(即拓扑排序失败),输出结果中应显示拓扑排序序列的数目为0。

课程设计报告要求给出详细算法描述,在结论部分应分析算法的时间复杂度和空间复杂度,并给出分析的结果。

实验目的:掌握图的存储结构;掌握图的拓扑排序算法

2.程序设计报告

程序采用C++语言(非面向对象)进行开发。

2.1 总体设计

程序采用深度优先搜索算法配合上回溯算法生成所有可能得拓扑排序序列。

使用辅助入度数组和辅助判断是否访问数组进行节点的选择,利用递归实现每一组可能得序列遍历完后回溯到上一级递归的状态,再次选择不同的节点进行再次递归。

通过主函数main调用众多子函数完成功能。执行流程图如下图所示。

                   

为了得到所有拓扑序列,采用回溯法,并用一个栈来存储拓扑序列的顺序。最开始以每一个节点作为起始点尝试能否递归下去(后面的每次递归也是尝试以每一个节点作为下一个拓扑排序节点),每一次递归都将此次入栈的节点在辅助判断是否被访问数组中的值改为false,并且将此节点所有的邻接点的入度在入度数组中的值全部减一。以这个状态进行下一层级的递归。当下一层级的所有递归都完成之后,将状态回溯,此节点的辅助判断是否被访问数组中的值改为true,并且将此节点所有的邻接点的入度在入度数组中的值全部加一。以此状态回溯到上一层级的递归,然后上一层级会尝试以另一节点开始递归。直到栈的长度等于节点总个数,说明栈中的序列是一个拓扑排序序列的逆序列,在输出函数中将逆序列反向输出到文件中,一个拓扑排序序列就被获取。然后向上一层级递归回溯。直到所有递归完成,所有可能的拓扑排序序列全部输出到文件中。再调用计算个数函数,得到文件中的行数(一行就是一个序列),最后将行数(个数)输出到文件中。

最后将动态申请的内存释放,做好收尾工作,所有的功能就实现完成。

2.2 详细数据结构设计

1. 存储结构

top:栈顶

base:栈底

size:空间大小

  1. 图的类型

DG:有向图

DN:有向网

UDG:无向图

UDN:无向网

  1. 图的邻接表节点

adjvex:数据域,以一个char类型作为数据类型

nextarc:下一个邻接点,类似于链表

  1. 顶点节点

data:数据域,以一个char类型作为数据类型

firstarc:第一个邻接点

  1. 图的邻接表

vexs:节点数组

vex_num:节点数

arc_num:弧数

kind:图类型

  1. 输入文件cd_input.txt的格式

第一行为节点数和弧数,第二行为各节点的数据,后面为各弧。

示例如下图所示。

  1. 输出文件cd_output.txt的格式

每一个拓扑序列占一行,最后是总拓扑序列的个数。

示例如下图所示。

  1. 辅助入度数组

indegree[i]:为以i为下标的G中节点的入度

  1. 辅助判断是否访问数组

visited[i]:为以i为下标的G中节点是否被访问,true为被访问,false为未被访问。

2.3 详细算法设计

1. 递归与回溯算法

递归是借用了深度优先搜素算法的特性,在每一层级的递归,都去判断每一个节点是否被访问过且入度是否为0,如果两个条件都满足,则从这个节点开始下一层级的递归。每一层级递归开始前都会先判断存储拓扑排序序列节点的栈的长度是否等于图的节点个数,如果相等,就调用函数将其输出到文件中。不等就遍历所有节点寻找符合之前两个条件的节点入栈,然后生成一个新状态:将此入栈节点的所有邻接点的入度减一,此节点在辅助判断是否访问数组中的值改为true。由这个新状态开始下一层级的递归。当以此节点开始的下面所有层级的递归结束后,将状态回溯至此节点未入栈之前的状态:将此节点的所有邻接点的入度加一,此节点在辅助判断是否访问数组中的值改为false,把此节点从栈中弹出。然后在当前层级遍历后面的节点,如有符合条件的节点,像刚刚那个节点一样开始下一层级的递归。

直到所有层级的递归完成后,全部可能得拓扑排序序列已输出到文件中,如果没有,文件中将是空白,然后得到文件中的行数,将序列数输出到文件中(空白文件将会输出0)。

  1. 通过文件创建图算法

对文件数据的读取是通过逐个读取每一个字符得到的。所以这对文件格式的要求十分严格。对于个数的读取,如节点个数、弧个数,是通过每个数据之间的空格判断一个数据是否读完,如果未读到空格则将之前读取到的数据乘以10再加上新读到的数据。读取到弧数之后通过一个for循环读取每一个弧的信息,每次读取以换行符结束,但读取最后一行数据时,为了迎合日常操作习惯,最后一行数据同时支持读到EOF结束。(即在字符文件中输入数据时,输完最后一行数据不必再次换行)同时注意,对于是否带权值的弧,读取代码有差异,故本版本代码只支持没有带权值的弧的读取。

注意:因文件输出方式为在尾部添加,故每次运行代码需先将cd_output.txt清空才行。

3.程序测试报告

注:本代码在vscode上测试时出现可能栈溢出问题,故在测试的时候选用的VS测试,VS测试未出现任何问题。其他编译器暂且不清楚。

测试实例(1)

程序输入                              示意图

                      

程序输出

测试实例(2)

程序输入                              示意图                      

                                 

程序输出

测试实例(3)

程序输入                              示意图

                            

程序输出

4.结论

此处对整个程序的核心部分进行时间复杂度和空间复杂度的分析。核心部分即printALL函数和tuopu函数。

时间复杂度分析:算法使用了递归来实现深度优先搜索。对于每个节点,递归调用tuopu函数。在每次调用中,都会遍历图中所有节点,因此总体时间复杂度为O(V*V!),其中V为节点个数。

空间复杂度分析:

  1. 栈的空间复杂度:递归调用会使用系统调用栈,其深度取决于递归的深度。在最坏情况下,递归深度可能达到节点数,因此栈的空间复杂度为O(V)。
  2. 入度数组和访问数组:算法使用了两个数组来存储入度和是否访问信息,它们的空间复杂度都是O(V)。
  3. 其他辅助空间:除了栈、入度数组和访问数组外,还有一些常量级别的辅助空间。因此,总体空间复杂度为O(V)。

综合考虑,此算法的总体空间复杂度是O(V)。这里V表示节点数。

需要注意的是,时间复杂度中的V! 是由于算法的性质,此算法尝试了图中所有可能的拓扑排序。

在输入文件格式正确情况下,在输入文件中输入一个弧不带权值的有向图的节点数、弧数、每个弧的起始点和终止点,每个节点的数据域的信息通过此程序可以得到输入有向图的所有可能得拓扑排序序列。

5.源程序附录

#include <iostream>

#include <fstream>

#define Inisize  100  // 栈初始化大小

#define Increse  20  // 每次栈增加的大小

#define MAX_VERTEX_N   20  // 最大节点数

using namespace std;

// 自定义栈

typedef struct {

    int* top;

    int* base;

    int size;

}myStack;

// 图的类型

typedef enum {

    DG,  // 有向图

    DN,  // 有向网

    UDG,  // 无向图

    UDN  // 无向网

}GraphKind;

// 边节点

typedef struct ArcNode

{

    char adjvex;  // 数据域

    struct ArcNode* nextarc;  // 下一个邻接点

}ArcNode;

// 节点

typedef struct

{

    char data;  // 数据域

    ArcNode* firstarc;  // 第一个邻接点

}VNode, AdjList[MAX_VERTEX_N];

// 图

typedef struct

{

    AdjList vexs;  // 节点数组

    int vex_num;  // 节点数

    int arc_num;  // 弧数

    GraphKind kind;  // 图类型

}ALGraph;

// 初始化栈

void Inistack(myStack& s) {

    s.base = new int[Inisize];

    s.top = s.base;

    s.size = Inisize;

}

// 销毁栈

void Destory(myStack& s) {

    delete s.base;

    s.base = NULL;

    s.top = NULL;

    s.size = 0;

}

// 判断栈是否为空

bool Empty(myStack& s) {

    return s.top == s.base;

}

// 获取栈的长度

int Length(myStack& s) {

    return s.top - s.base;

}

// 入栈

void Push(myStack& s, int e) {

    if (s.top - s.base >= s.size) {

        int* a = new int[s.size + Increse];

        for (int i = 0; i < s.top - s.base; i++) {

            a[i] = s.base[i];

        }

        delete s.base;

        s.base = a;

        s.top = s.base + s.size;

        s.size += Increse;

    }

    *(s.top++) = e;

}

// 出栈

int Pop(myStack& s) {

    if (Empty(s)) {

        exit(1);

    }

    return *(--s.top);

}

// 获取数据为e的节点下标

int locate(ALGraph G, char e) {

    int i;

    for (i = 0; i < G.vex_num; ++i) {

        if (G.vexs[i].data == e) {

            break;

        }

    }

    return i;

}

// 从字符文件获取数据创建邻接表

void CreateUDN(ALGraph& G) {

    ifstream ifs;

    ifs.open("cd_input.txt", ios::in);

    char ch;

    int num = 0, index = 0, i;

    // 读取节点数和弧数

    while ((ch = ifs.get()) != ' ')

    {

        num = num * 10 + (ch - '0');

    }

    G.vex_num = num;

    num = 0;

    while ((ch = ifs.get()) != '\n')

    {

        num = num * 10 + (ch - '0');

    }

    G.arc_num = num;

    num = 0;

    // 初始化节点数组

    while ((ch = ifs.get()) != '\n')

    {

        if (ch != ' ') {

            G.vexs[index].data = ch;

            G.vexs[index].firstarc = NULL;

            ++index;

        }

    }

    // 创建弧节点

    for (int k = 0; k < G.arc_num; ++k) {

        char v1, v2;

        while ((ch = ifs.get()) != ' ')

        {

            v1 = ch;

        }

        while ((ch = ifs.get()) != '\n')

        {

            if (ch == EOF) {

                break;

            }

            v2 = ch;

        }

        // 将<v1, v2>添加到v1的邻接点中

        ArcNode* temp = new ArcNode;

        temp->adjvex = v2;

        temp->nextarc = NULL;

        i = locate(G, v1);

        // 如果v1的邻接表为空

        if (!G.vexs[i].firstarc) {

            G.vexs[i].firstarc = temp;

        }

        else {

            ArcNode* p = G.vexs[i].firstarc;

            ArcNode* q = NULL;

            while (p)

            {

                q = p;

                p = p->nextarc;

            }

            q->nextarc = temp;

        }

    }

    G.kind = DG;

    ifs.close();

}

// 将输出拓扑排序序列输出到文件中

void to(myStack S, ALGraph G) {

    ofstream ofs;

    ofs.open("cd_output.txt", ios::app);

    for (int i = 0; i < S.top - S.base; i++) {

        ofs << G.vexs[S.base[i]].data << " ";

    }

    ofs << endl;

    ofs.close();

}

// 获取拓扑序列的个数

void get_num() {

    ofstream ofs;

    ifstream ifs;

    char ch;

    int num = 0;

    ifs.open("cd_output.txt", ios::in);

    // 通过读取换行符的个数得到序列个数

    while ((ch = ifs.get()) != EOF)

    {

        if (ch == '\n') {

            ++num;

        }

    }

    ifs.close();

    ofs.open("cd_output.txt", ios::app);

    // 将个数输出到文件

    ofs << num << endl;

    ofs.close();

}

// 生成所有可能得拓扑排序序列

void tuopu(myStack& S, int* indegree, bool* visited, ALGraph G) {

    ArcNode* p = NULL;

    // 如果栈的长度是节点个数

    if (Length(S) == G.vex_num)

    {

        to(S, G);

    }

    // 以每一个节点为起始点尝试进行拓扑排序

    for (int i = 0; i < G.vex_num; i++)

    {

        // 如果入度为0且未被访问

        if (indegree[i] == 0 && !visited[i])

        {

            visited[i] = true;

            Push(S, i);

            // 如果此节点有邻接点

            if (G.vexs[i].firstarc != NULL)

            {

                p = G.vexs[i].firstarc;

                // 将此节点的所有邻接点的入度-1

                while (p != NULL)

                {

                    indegree[locate(G, p->adjvex)]--;

                    p = p->nextarc;

                }

            }

            // 递归再次进行拓扑排序

            tuopu(S, indegree, visited, G);

            // 此次递归完成后 将状态回溯 进行其他可能的拓扑排序

            if (G.vexs[i].firstarc != NULL)

            {

                p = G.vexs[i].firstarc;



                while (p != NULL)

                {

                    indegree[locate(G, p->adjvex)]++;

                    p = p->nextarc;

                }

            }

            visited[i] = false;

            Pop(S);

        }

    }

}

// 获取所有的拓扑排序序列

void printALL(ALGraph G) {

    // 入度数组

    int* indegree = new int[G.vex_num];

    myStack S;

    // 是否访问数组

    bool* visited = new bool[G.vex_num];

    ArcNode* p = NULL;



    Inistack(S);

    // 初始化入度数组和是否访问数组

    for (int i = 0; i < G.vex_num; ++i) {

        indegree[i] = 0;

        visited[i] = false;

    }

    for (int i = 0; i < G.vex_num; ++i) {

        for (p = G.vexs[i].firstarc; p; p = p->nextarc) {

            indegree[locate(G, p->adjvex)]++;

        }

    }

    // 进行拓扑排序

    tuopu(S, indegree, visited, G);



    // 释放空间

    delete[] indegree;

    delete[] visited;

    Destory(S);

}

// 释放创建图时动态申请的内存

void des(ALGraph& G) {

    for (int i = 0; i < G.vex_num; ++i) {

        ArcNode* p = G.vexs[i].firstarc;

        ArcNode* q = NULL;

        while (p)

        {

            q = p->nextarc;

            delete p;

            p = q;

        }

    }

}

int main(void) {

    ALGraph G;

    // 创建图

    CreateUDN(G);

    // 生成所有拓扑序列

    printALL(G);

    // 输出个数

    get_num();

    cout << "成功!!!" << endl;

    des(G);

    system("pause");

    return 0;

}

  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 西南交通大学数据结构A期末考试是考察学生对于数据结构知识的掌握程度和应用能力的考试。该门课程是计算机科学与技术专业的基础课程之一,对于学生后续的学习和工作都具有重要意义。 期末考试可能包括选择题、填空题、编程题等。选择题主要通过给出多个选项,考察学生对于概念、原理、算法等知识点的理解。填空题则需要学生根据题目的要求,填写合适的答案,考察学生对于知识点的掌握程度和灵活运用能力。而编程题则要求学生根据给定的问题和要求,编写符合要求的程序,考察学生的编程能力和解决实际问题的能力。 在备考阶段,重点应该放在对于数据结构的基本概念、常见算法和数据结构操作的理解上。需要熟悉各种数据结构(如栈、队列、链表、二叉树等)的定义、特点以及相关的算法和操作。同时,还需要理解各种数据结构之间的联系和应用场景,能够根据实际问题选择合适的数据结构和算法进行解决。 在考试过程中,需要认真阅读题目,理清题意,注意答题的格式和要求。对于选择题,可以先排除明显错误的选项,再根据知识点和逻辑进行选择。对于填空题,要根据题目的要求进行填写,尽量准确和简洁。对于编程题,要先理解问题的要求和限制,再根据自己的编程思路进行代码的编写和调试。 总之,通过充分的复习和实践,加强对于数据结构的理解和应用能力,相信你能够顺利应对西南交通大学数据结构A期末考试。加油! ### 回答2: 西南交通大学数据结构A期末考试是课程结束后的最后一次考核,考察学生对于数据结构的理解和应用能力。考试内容通常包括理论知识和编程实践两部分。 在理论知识方面,考试会涉及数据结构的基本概念、性质和操作等内容。学生需要掌握各种数据结构的特点和适用场景,包括数组、链表、栈、队列、树、图等。同时,还需要了解各种数据结构之间的关系和相互转换的方法。考试中可能会出现选择题、判断题、填空题等形式,要求学生熟悉数据结构的定义、性质和相关算法。 在编程实践方面,考试通常会要求学生根据给定的问题,设计和实现相应的数据结构和算法。这要求学生能够将所学的知识运用到实际问题中,解决实际的编程难题。考试中可能会出现编程题,要求学生用编程语言(如C++、Java)实现某一特定的数据结构或算法。学生需要注意代码的逻辑正确性、健壮性和效率问题。 为了应对数据结构A期末考试,学生需要进行充分的复习和准备。可以参考教材、课堂笔记和习题集,巩固数据结构的相关知识。还可以编写一些小程序来加深和巩固对数据结构的理解。另外,参加课程的讨论和习题讲解活动,及时解决遇到的问题,提高编程能力。 总之,西南交通大学数据结构A期末考试是对学生对于数据结构理论知识和实践能力的综合考核。通过充分的复习和准备,学生可以顺利应对考试,取得好成绩。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

还有糕手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值