BUAA数据结构期末考试(18级)

BUAA数据结构期末考试(18级)

1. 学生在线上机时间统计

题目

问题描述

教学平台日志数据中记录了学生使用系统的情况。假设已获取了某段时 间内学生使用系统的情况(包括姓名、学号及使用系统的时间),由于 学生可以多次登陆、注销,所以有可能记录了某位学生多次使用系统的 情况,请统计合并每位学生使用系统的时间,并按由小至大排序输出(时 间相同时按学号由小到大排序输出)。

输入形式

首先从控制台输入学生使用系统次数 n(大于等于 1 并且小于等于 100), 然后分行输入 n 条学生使用系统情况,每行输入信息包括学生姓名(由 3-20 个英文字母组成,中间无空格)、学号(由 8 位 0-9 的数字组成)、 使用系统时间(大于 0 并且小于等于 86400 的整数,单位为秒),各信 息由一个空格分隔,每行末尾有换行符。输入信息中不会出现学号相同 而姓名不同的情况。

输出形式

按照使用系统时间由小到大的顺序分行输出每位学生使用系统情况,时 间相同的按照学号由小到大的顺序输出。每行信息分别包括学生姓名 (3-20 位英文字母)、学生学号(8 位数字)和使用系统时间,各数据 间以一个空格分隔,每行最后一个数据后与回车间无空格。

样例1输入

10
wanghai 19373001 3600
liupeng 19374521 1796
zhanghuimei 19182538 2421
lipengyou 19230908 7329
qinhong 19060211 650
zhaopin 17182785 1076
sunliang 15375026 2028
zhanghuimei 19182538 2537
jikehong 16373890 4263
wanghai 19373001 58

样例1输出

qinhong 19060211 650
zhaopin 17182785 1076
liupeng 19374521 1796
sunliang 15375026 2028
wanghai 19373001 3658
jikehong 16373890 4263
zhanghuimei 19182538 4958
lipengyou 19230908 7329

样例1说明

输入了 10 条学生使用系统情况信息,其中学号为 19373001 和 19182538 的学生各有两条信息,合并使用时间后按照使用时间由小到大排序输出。

样例2输入

20
wanghai 19373001 3600
liupeng 19374521 1796
zhanghuimei 19182538 2421
lipengyou 19230908 7329
qinhong 19060211 650
zhaopin 17182785 1076
sunliang 15375026 2028
zhanghuimei 19182538 2537
jikehong 16373890 4263
wanghai 19373001 58
qinhong 19060211 123
zhanghuimei 19182538 3311
liuxu 19373289 1239
hongfei 19372976 900
sunliang 15375026 1000
liupeng 19374521 1862
zhanghuimei 19182538 32
sunliang 15375026 630
wanghai 19375091 9023
zhanghuimei 19182538 1096

样例2输出

qinhong 19060211 773
hongfei 19372976 900
zhaopin 17182785 1076
liuxu 19373289 1239
sunliang 15375026 3658
wanghai 19373001 3658
liupeng 19374521 3658
jikehong 16373890 4263
lipengyou 19230908 7329
wanghai 19375091 9023
zhanghuimei 19182538 9397

样例2说明

输入了 20 条学生使用系统情况信息,其中有两条是学号为 19373001 的 学生使用系统情况,使用时间分别为 3600 秒和 58 秒,合并后为 3658 秒, 类似的有:19374521有两条、19182538有五条、19060211有两条、15375026 有三条,其他学生都只有一条,因此共记录了 11 位同学的使用系统情况 信息,最后将这 11 位同学的使用系统情况信息按照使用时间由小到大的 顺序输出。其中有三位同学都使用了 3658 秒,这时按照学号由小到大的 顺序输出。

评分标准

该程序要求统计学生使用系统时间信息,提交程序文件名为 time.c。

问题分析

本题开始肯定是读入,然后以学生的学号为关键字排序,唯一需要思考一下的是如何合并一个学生的所有日志信息的时间。这有很多方法,我采用的方法是每次都记录当前这个学生的第一条日志信息的地址(指针),然后往后遍历时若遍历到和该条日志中的学生学号相同的日志,就通过指针改变第一条中的时间信息,而遍历到别的学生时,就先把上一个学生的第一条日志的指针进入数组(指针数组),最后对指针数组排序输出即可。由于每年的第一题都差不多,所以这里不再展示具体处理过程,直接给出完整代码(具体过程可以参考19,20,21级期末考试的文章):

完整代码

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    int identidier;
    char name[20];
    int time;
} logging;

logging gather[1000];

int cmp_1(const void *a, const void *b);
int cmp_2(const void *a, const void *b);

int main()
{

    int n;
    scanf("%d", &n);

    for (int i = 0; i < n; i++) // 读入
        scanf("%s %d %d", gather[i].name, &gather[i].identidier, &gather[i].time);

    qsort(gather, n, sizeof(logging), cmp_1); // 排序

    int std_num = 0; // 学生总数
    logging *p = &gather[0];
    for (int i = 1; i < n; i++)
    {
        if (gather[i].identidier == p->identidier) // 和当前的学生相同,增加其时间
            p->time += gather[i].time;
        else
        { // 遍历到别的学生了,先把上一个学生的日志信息(指针)入栈,然后改变p的值指到当前的学生的第一条日志
            gather[std_num++] = *p;
            p = &gather[i];
        }
    }

    // 最后一个数据入栈
    gather[std_num++] = *p;

    // 再排序,输出
    qsort(gather, std_num, sizeof(logging), cmp_2);
    for (int i = 0; i < std_num; i++)
        printf("%s %d %d\n", gather[i].name, gather[i].identidier, gather[i].time);

    return 0;
}

int cmp_1(const void *a, const void *b)
{
    return ((logging *)a)->identidier - ((logging *)b)->identidier;
}

int cmp_2(const void *a, const void *b)
{
    logging *e1 = (logging *)a;
    logging *e2 = (logging *)b;
    if (e1->time != e2->time)
        return e1->time - e2->time;
    return e1->identidier - e2->identidier;
}

3. 网络打印机选择

题目

问题描述

某单位信息网络结构呈树型结构,网络中节点可为交换机、计算机和打 印机三种设备,计算机和打印机只能位于树的叶节点上。如要从一台计 算机上打印文档,请为它选择最近(即经过交换机最少)的打印机。 在该网络结构中,根交换机编号为 0,其它设备编号可为任意有效正整数, 每个交换机有 8 个端口(编号 0-7)。当存在多个满足条件的打印机时, 选择按树前序遍历序排在前面的打印机。

在这里插入图片描述

输入形式

首先从标准输入中输入两个整数,第一个整数表示当前网络中设备数目, 第二个整数表示需要打印文档的计算机编号。两整数间以一个空格分隔。 假设设备总数目不会超过 300。 然后从当前目录下的 in.txt 读入相应设备配置表,该表每一行构成一个 设备的属性,格式如下:

<设备 ID> <设备父节点 ID> <类型> <端口号>

<设备 ID>为一个非负整数,表示设备编号;<设备父结点 ID>为相应结点 父结点编号,为一个有效非负整数;<类型>分为:0 表示交换机、1 表示 计算机、2 表示打印机;<端口号>为相应设备在父结点交换机中所处的 端口编号,分别为 0-7。由于设备配置表是按设备加入网络时的次序编排 的,因此,表中第一行一定为根交换机(其属性为 0 -1 0 -1);其它每 个设备结点一定在其父设备结点之后输入。每行中设备属性间由一个空 格分隔,最后一个属性后有换行符。

输出形式

向控制台输出所选择的打印机编号,及所经过的交换机的编号,顺序是 从需要打印文档的计算机开始,编号间以一个空格分隔。

样例输入

37 19

in.txt 中的信息如下:

0 -1 0 -1
1 0 0 0
2 1 0 2
3 1 1 5
4 0 0 1
5 4 1 0
6 2 2 2
7 4 0 2
8 0 0 4
9 2 0 0
10 9 0 0
11 10 2 3
12 9 0 2
13 7 0 0
14 13 0 0
15 7 2 3
16 8 0 1
17 16 0 0
18 17 1 5
19 9 1 5
20 12 0 1
21 14 1 1
22 14 1 2
23 13 1 2
24 12 1 5
25 20 0 1
26 20 1 2
27 14 0 7
28 16 0 1
29 4 1 3
30 16 0 7
31 28 0 0
32 31 2 0
33 30 1 2
34 31 1 2
35 31 0 5
36 35 1 3

样例输出

11 9 10

样例说明

样例输入中 37 表示当前网络共有 37 台设备,19 表示编号为 19 的计算机 要打印文档。in.txt 设备表中第一行 0 -1 0 -1 表示根节点交换机设备, 其设备编号为 0 、父结点设备编号-1 表示无父设备、设备类型为 0(交 换机)、端口-1 表示无接入端口;设备表第二行 1 0 0 0 表示设备编号 为 1 、父结点设备编号 0(根交换机)、设备类型为 0(交换机)、端口 0 表示接入父结点端口 0;设备表中行 5 4 1 0 表示设备编号为 5 、父结 点设备编号 4、设备类型为 1(计算机)、端口 0 表示接入 4 号交换机端 口 0;设备表中行 6 2 2 2 表示设备编号为 6 、父结点设备编号 2、设备 类型为 2(打印机)、端口 2 表示接入 2 号交换机端口 2。 样例输出 11 9 10 表示选择设备编号为 11 的打印机打印文档,打印需要 经过 9 号和 10 号交换机(尽管 6 号和 11 号打印机离 19 号计算机距离相 同,但 11 号打印机按树前序遍历时排在 6 号之前)。

评分标准

按题目要求实现相关功能,提交程序文件名为 print.c。

问题分析

本题建树的过程都好说,主要是建完树后如何找到距离目标计算机最近的打印机,因为我们知道距离最近的打印机不一定在目标计算机的子树中,而是可以“向上”走,甚至可能越过根结点。但其实树中任意两个结点之间必有唯一的一条路径,寻找的方法就是先找到两结点的最近公共祖先,然后从这个公共祖先分别走向两个结点即可。

具体处理过程

首先要完成建树的工作(这里可以先把最基础的树建立起来,后面要加功能的话可以再改代码),首先声明结点类型:

typedef struct device
{
    int id;   // 设备ID
    int type; // 设备类型,0->交换机,1->计算机,2->打印机
    struct device *child[8];
} device;

主函数应当设计如下:

int main()
{
    int device_num, target_number;
    scanf("%d%d", &device_num, &target_number);

    freopen("in.txt", "r", stdin);
    device *root = (device *)malloc(sizeof(device));
    // 初始化根结点
    root->id = 0; 
    root->type = 0; 
    for (int i = 0; i < 8; i++)
    {
        root->child[i] = NULL;
    }

    int tmp_id, tmp_par_id, tmp_type, tmp_number;
    // 先把第一个读了
    scanf("%d%d%d%d", &tmp_id, &tmp_par_id, &tmp_type, &tmp_number);
    for (int i = 1; i < device_num; i++)
    {
        scanf("%d%d%d%d", &tmp_id, &tmp_par_id, &tmp_type, &tmp_number);
        insert(root, tmp_id, tmp_par_id, tmp_type, tmp_number);
    }
    printf("%d", root->child[1]->id);
    return 0;
}

其中insert函数实现寻找目标的双亲结点,并插入其孩子结点,代码如下:

void insert(device *root, int id, int par_id, int type, int number)
{
    if (root->id == par_id) // 找到了目标结点,插入
    {
        device *tmp = (device *)malloc(sizeof(device));
        tmp->id = id;
        tmp->type = type;
        for (int i = 0; i < 8; i++)
            tmp->child[i] = NULL;
        root->child[number] = tmp;
        return;
    }
    for (int i = 0; i < 8; i++)
    {
        if (root->child[i])
        {
            insert(root->child[i], id, par_id, type, number);
        }
    }
}

至此,就完成了最基本的建树工作。然后我们思考一下,为了方便后面遍历,我们应当在插入的时候就记录下来目标的计算机结点。同时,如何寻找最近公共祖先呢?我的想法是先把目标计算机结点到根结点路径上所有结点都记录下来(这需要通过孩子结点访问双亲结点,因此建树的时候应该给结点类型中加一个双亲域),然后对于每个打印机结点,也向上寻祖,直到某个结点是目标打印机结点到根结点路径上的一个结点。

这样,修改代码如下,首先是结构声明和全局变量的定义:

typedef struct device
{
    int id;   // 设备ID
    int type; // 设备类型,0->交换机,1->计算机,2->打印机
    struct device *child[8];
    struct device *parent; // 双亲结点的地址
} device;

device *target;                       // 目标打印机结点的地址
device *target_to_root_path[100];     // 目标结点和root之间的结点,数组下标代表两者距离
int target_to_root_path_num;          // 上面那个数组的元素个数
int print_to_computer_path_len[1000]; // 打印机结点到目标结点的路径距离

int min_len = 1000000;   // 最短距离
device *common_ancestor; // 目标的打印机结点和目标结点的共同祖先
device *target_print;    // 目标打印机的地址

然后insert函数修改如下:

void insert(device *root, int id, int par_id, int type, int number, int target_id)
{
    if (root->id == par_id)
    {
        device *tmp = (device *)malloc(sizeof(device));
        tmp->id = id;
        tmp->type = type;
        for (int i = 0; i < 8; i++)
            tmp->child[i] = NULL;
        tmp->parent = root;
        root->child[number] = tmp;

        // 判断是否是目标结点
        if (id == target_id)
        {
            target = tmp;
        }
        return;
    }
    for (int i = 0; i < 8; i++)
    {
        if (root->child[i])
        {
            insert(root->child[i], id, par_id, type, number, target_id);
        }
    }
}

主函数中,建完树后要进行前序遍历,这里直接比较出目标的打印机结点:

// 主函数中
    // 把目标结点到root之间的所有结点记录下来
    device *p = target;
    while (p)
    {
        target_to_root_path[target_to_root_path_num++] = p;
        p = p->parent;
    }
    // 前序遍历,把所有打印机结点到目标结点之间的距离记录下来
    bfs(root);

bfs函数设计如下:

void bfs(device *root) // 前序遍历,把所有的打印机结点到目标结点之间的距离记录下来
{
    if (root == NULL)
        return;
    if (root->type == 2)
    { // 是打印机
        device *p = root;
        int flag = 0;
        int print_to_common_ancestor_len = 0; // 到最近公共祖先的距离
        int all_len; // 总距离
        while (!flag)
        {
            for (int i = 0; i < target_to_root_path_num; i++)
            {
                if (target_to_root_path[i] == p)
                {
                    all_len = print_to_common_ancestor_len + i;
                    if (all_len < min_len) // 更新最短距离
                    {
                        min_len = all_len;
                        common_ancestor = p;
                        target_print = root;
                    }
                    flag = 1;
                    break;
                }
            }
            if (!flag)
            {
                print_to_common_ancestor_len++;
                p = p->parent;
            }
        }
    }
    for (int i = 0; i < 8; i++)
    {
        bfs(root->child[i]);
    }
}

最后在主函数中输出即可:

// 主函数中
    printf("%d ", target_print->id);
    if (target != common_ancestor)
    {
        target = target->parent;
        while (target != common_ancestor)
        {
            printf("%d ", target->id);
            target = target->parent;
        }
    }

    printf("%d ", common_ancestor->id);
    target_print = target_print->parent;
    print_target_print_to_common_ancestor();

其中 print_target_print_to_common_ancestor打印目标打印机到公共祖先之间的路径,这个也见过很多次了,直接递归打印即可:

void print_target_print_to_common_ancestor()
{
    if (target_print != common_ancestor)
    {
        device *tmp = target_print;
        target_print = target_print->parent;
        print_target_print_to_common_ancestor();
        printf("%d ", tmp->id);
    }
}

完整代码

#include <stdio.h>
#include <stdlib.h>

typedef struct device
{
    int id;   // 设备ID
    int type; // 设备类型,0->交换机,1->计算机,2->打印机
    struct device *child[8];
    struct device *parent; // 双亲结点的地址
} device;

device *target;                       // 目标打印机结点的地址
device *target_to_root_path[100];     // 目标结点和root之间的结点,数组下标代表两者距离
int target_to_root_path_num;          // 上面那个数组的元素个数
int print_to_computer_path_len[1000]; // 打印机结点到目标结点的路径距离

int min_len = 1000000;   // 最短距离
device *common_ancestor; // 目标的打印机结点和目标结点的共同祖先
device *target_print;    // 目标打印机的地址

void insert(device *root, int id, int par_id, int type, int number, int target_id);
void bfs(device *root);
void print_target_print_to_common_ancestor();

int main()
{
    int device_num, target_id;
    scanf("%d%d", &device_num, &target_id);

    freopen("in.txt", "r", stdin);
    device *root = (device *)malloc(sizeof(device));
    root->id = 0;
    root->type = 0;
    for (int i = 0; i < 8; i++)
    {
        root->child[i] = NULL;
    }
    root->parent = NULL;

    int tmp_id, tmp_par_id, tmp_type, tmp_number;
    // 先把第一个读了
    scanf("%d%d%d%d", &tmp_id, &tmp_par_id, &tmp_type, &tmp_number);
    for (int i = 1; i < device_num; i++)
    {
        scanf("%d%d%d%d", &tmp_id, &tmp_par_id, &tmp_type, &tmp_number);
        insert(root, tmp_id, tmp_par_id, tmp_type, tmp_number, target_id);
    }

    // 把目标结点到root之间的所有结点记录下来
    device *p = target;
    while (p)
    {
        target_to_root_path[target_to_root_path_num++] = p;
        p = p->parent;
    }
    // 前序遍历,把所有打印机结点到目标结点之间的距离记录下来
    bfs(root);
    printf("%d ", target_print->id);
    if (target != common_ancestor)
    {
        target = target->parent;
        while (target != common_ancestor)
        {
            printf("%d ", target->id);
            target = target->parent;
        }
    }

    printf("%d ", common_ancestor->id);
    target_print = target_print->parent;
    print_target_print_to_common_ancestor();
    return 0;
}

void insert(device *root, int id, int par_id, int type, int number, int target_id)
{
    if (root->id == par_id)
    {
        device *tmp = (device *)malloc(sizeof(device));
        tmp->id = id;
        tmp->type = type;
        for (int i = 0; i < 8; i++)
            tmp->child[i] = NULL;
        tmp->parent = root;
        root->child[number] = tmp;

        // 判断是否是目标结点
        if (id == target_id)
        {
            target = tmp;
        }
        return;
    }
    for (int i = 0; i < 8; i++)
    {
        if (root->child[i])
        {
            insert(root->child[i], id, par_id, type, number, target_id);
        }
    }
}

void bfs(device *root) // 前序遍历,把所有的打印机结点到目标结点之间的距离记录下来
{
    if (root == NULL)
        return;
    if (root->type == 2)
    { // 是打印机
        device *p = root;
        int flag = 0;
        int print_to_common_ancestor_len = 0; // 到最近公共祖先的距离
        int all_len;                          // 总距离
        while (!flag)
        {
            for (int i = 0; i < target_to_root_path_num; i++)
            {
                if (target_to_root_path[i] == p)
                {
                    all_len = print_to_common_ancestor_len + i;
                    if (all_len < min_len) // 更新最短距离
                    {
                        min_len = all_len;
                        common_ancestor = p;
                        target_print = root;
                    }
                    flag = 1;
                    break;
                }
            }
            if (!flag)
            {
                print_to_common_ancestor_len++;
                p = p->parent;
            }
        }
    }
    for (int i = 0; i < 8; i++)
    {
        bfs(root->child[i]);
    }
}

void print_target_print_to_common_ancestor()
{
    if (target_print != common_ancestor)
    {
        device *tmp = target_print;
        target_print = target_print->parent;
        print_target_print_to_common_ancestor();
        printf("%d ", tmp->id);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值