BUAA数据结构期末复习各路模拟题

BUAA期末复习模拟题

1. 干员整理

题目

问题描述

小S管理着一艘舰船,同时他需要负责对于舰船上所有干员(在这里指参与战斗的工作人员)的统计与调度。

但最近小S没有完成的工作还有很多,还不能休息。同时也就导致了小S没时间完成干员的统计与调度,他听说你的编程水平十分优秀,于是计划把这个工作交给你。

小S的助理艾雅已经将现在所有干员的信息列在了一张表单上输入给你,上面列明了每个干员的编号i、姓名s、职业r与等级l。(保证任意两个干员编号不同)

你需要将表单分别根据:1、职业优先,职业相同按照编号从小到大,和2、等级从小到大优先,等级相同按照编号从小到大排序,并分别输出。

同时,你还会读取M个调度需求,每个需求包括四个数值:要求的干员数量n,要求的干员职业r,要求的干员等级最小值(含) l m i n l_{min} lmin和最大值(含) l m a x l_{max} lmax。对于每个需求,你需要判断是否能满足需求,如果可以满足,优先选取编号小的干员并按照编号从小到大输出其编号到标准输出流中。如果不能满足某个需求,输出"DAMEDANE"并换行。(不包括引号)

注意:如果一个干员已经在一个可以被满足的需求中被调度,在之后的需求中便不能再被调度,即优先满足靠前的调度需求。(若一个调度需求不可满足但能被部分满足,可以被满足的部分对应的干员不会被调度)

附录:干员职业包括如下八个(不包括引号):“caster”,“medic”,“supporter”,“specialist”,“vanguard”,“guard”,“defender”,“sniper”。按照干员职业排序时请按照此处出示的顺序从后往前排序。

输入形式

标准输入流中你将读取如下内容:

总计M+N+1行,第一行为两个整型:N和M,以空格分隔。前者代表干员总数,后者代表调度需求数量。

此后的N行为干员列表,每行包括:编号i,姓名s,职业r和等级l。

此后的M行为调度需求,每行包括:干员数量n,职业r,两个整型:干员等级最小值(含) l m i n l_{min} lmin和最大值(含) l m a x l_{max} lmax

保证上述字符串中均不包括空白符,且整型均包括在unsigned int的数值范围内。

输出形式

你需要在标准输出流中输出如下内容:

总计2N+M行。

开始的N行,输出排序2所要求的内容,每行包括:整型i,字符串s,字符串r和整型l,含义如上。。

此后的N行,输出排序1所要求的内容,每行包括:整型i,字符串s,字符串r和整型l,含义如上。

此后的M行,每行对应一个调度需求,如果不能满足需求则在本行输出字符串"DAMEDANE"(不包括引号),如果可以满足需求则按照编号从小到大顺序输出所调度干员的干员编号,以空格分割。

样例输入

5 2
1 Ptilopsis medic 88
2 Silence medic 66
3 Ifrit caster 99
4 Magallan supporter 99
5 Rosmontis sniper 2
2 medic 1 100
6 supporter 50 60

样例输出

5 Rosmontis sniper 2
2 Silence medic 66
1 Ptilopsis medic 88
3 Ifrit caster 99
4 Magallan supporter 99
5 Rosmontis sniper 2
4 Magallan supporter 99
1 Ptilopsis medic 88
2 Silence medic 66
3 Ifrit caster 99
1 2
DAMEDANE

数据范围与提示

保证 5 ⩽ n ⩽ N ⩽ 1000 , 0 ⩽ M ⩽ 10 , 0 ⩽ i , l ⩽ 1000 , 0 ⩽ l m i n ⩽ l m a x ⩽ 1000 5 \leqslant n \leqslant N \leqslant 1000, 0 \leqslant M \leqslant 10, 0 \leqslant i, l \leqslant 1000,0 \leqslant l_{min} \leqslant l_{max} \leqslant 1000 5nN1000,0M10,0i,l1000,0lminlmax1000, 保证干员姓名不超过五十个字符

问题分析

本题就是一道基本的结构体排序题,后面加了一点队列的操作。读懂题后可以发现其实题目就是让我们依次做这几件事情:

  1. 读入数据并存储——这个肯定是开结构体数组了;
  2. 分别按照题目中要求的两种方式排序,并输出相应的结果——这也很容易,我们只要按照要求写两个cmp函数,然后主函数中qsort就行;
  3. 按照题目要求进行多轮的人员调度——这个调度的第一关键字是职业,第二关键字是编号,这就很符合题目中排序1的要求,所以可以在此基础上进行操作。对于每次调度,我们要先找到这种职业在数组中出现的位置(如果没有的话,需要特判),然后遍历数组中所有这种职业的人员,如果某个人员符合题目的所有要求并且没有被调度过(可以想见,为了判断是否被调度过,结构体中应该多开一个域记录是否被调度过),就把这个位置记录下来。注意此时不能直接对这个人员进行调度,因为我们不知道这一轮调度到底能不能成功,所以可以开一个队列,将该人员“入队”记录下来。遍历完后根据队列中元素个数就可以判断本轮调度是否成功。如果本轮调度成功,就将队中元素依次输出,并把其对应位置上的数组元素标记为已被调度过。

具体处理过程

首先声明结构类型:

typedef struct
{
    int i;       // 编号
    char s[51];  // 姓名
    char r[10];  // 职业
    int l;       // 等级
    int is_used; // 判断是否被调度过了
} individual;

然后开全局变量,根据上面的分析,大概需要开这么几个全局变量:

int M, N;
individual gather[1005]; // 人员集合
int cmp_table[27];       // 比较职业的索引表
individual *queue[1000]; // 调动人员的队列

其中“比较职业的索引表”是可选项,我这么做事为了简化一下代码——因为我们发现这8种职业的倒数第三个字母都不同,所以我们可以用这个字母作为关键字,将字母转化为1-26(或0-25),比较cmp_table中对应位置的值的大小(这个需要我们自己在主函数中初始化),就可以按照题目要求队职业排序了(当然,完全可以不这么干)

主函数中对cmp_table的初始化如下:

// 主函数中
    // 初始化cmp_table
    cmp_table['s' - 'a'] = 7;
    cmp_table['d' - 'a'] = 6;
    cmp_table['p' - 'a'] = 5;
    cmp_table['e' - 'a'] = 4;
    cmp_table['n' - 'a'] = 3;
    cmp_table['a' - 'a'] = 2;
    cmp_table['f' - 'a'] = 1;
    cmp_table['i' - 'a'] = 0;

然后读入人员信息:

// 朱函数中
    for (int i = 0; i < N; i++)
        // 读入干员数据
        scanf("%d %s %s %d", &gather[i].i, gather[i].s, gather[i].r, &gather[i].l);

接着进行两次排序,并按照要求输出:

    // 第一次排序,输出
    qsort(gather, N, sizeof(gather[0]), cmp_2);
    for (int i = 0; i < N; i++)
        printf("%d %s %s %d\n", gather[i].i, gather[i].s, gather[i].r, gather[i].l);

    // 第二次排序,输出
    qsort(gather, N, sizeof(gather[0]), cmp_1);
    for (int i = 0; i < N; i++)
        printf("%d %s %s %d\n", gather[i].i, gather[i].s, gather[i].r, gather[i].l);

其中,根据要求,cmp_1和cmp_2的设计分别如下:

int cmp_1(const void *a, const void *b)
{
    individual *e1 = (individual *)a;
    individual *e2 = (individual *)b;
    if (e1->r[2] != e2->r[2])
        return cmp_table[e1->r[2] - 'a'] - cmp_table[e2->r[2] - 'a'];
    return e1->i - e2->i;
}

int cmp_2(const void *a, const void *b)
{
    individual *e1 = (individual *)a;
    individual *e2 = (individual *)b;
    if (e1->l != e2->l)
        return e1->l - e2->l;
    return e1->i - e2->i;
}

然后在主函数中进行人员的多轮调度:

// 主函数中
    int n, lmin, lmax;
    char r[10];
    for (int i = 0; i < M; i++)
    {
        scanf("%d %s %d %d", &n, r, &lmin, &lmax);
        select(n, r, lmin, lmax);
    }

select函数实现人员调度,其函数原型为void select(int n, char *r, int lmin, int lmax),在函数内部,首先应当找到目标职业在数组中的起始位置,注意要特判一下没找到的情况:

// select函数中
    int beg; // 目标职业起始位置
    for (beg = 0; beg < N; beg++)
    {
        if (strcmp(gather[beg].r, r) == 0) // 找到了起始位置
            break;
    }

    if (beg == N)
    { // 没有该职业,调动失败
        printf("DAMEDANE\n");
        return;
    }

接下来从起始位置开始遍历,进行人员的检索,满足要求的就先入队,当队列中的元素等于要求的n个,或者遍历到别的职业的位置上时(就是失败了),退出循环:

// select函数中
    int queue_num = 0; // 满足要求的个数

    for (int i = beg; i < N; i++)
    {
        if (strcmp(gather[i].r, r)) // 该职业的已经遍历完了
            break;
        if (!gather[i].is_used && gather[i].l >= lmin && gather[i].l <= lmax)
        { // 入队
            queue[queue_num++] = gather + i;
        }
        if (queue_num == n) // 够了n个,跳出循环
            break;
    }

然后再判断一下调度是否成功,并进行相应的操作:

// select函数中
    if (queue_num == n)
    { // 满足调动要求
        for (int i = 0; i < queue_num; i++)
        {
            printf("%d ", queue[i]->i);
            queue[i]->is_used = 1;
        }
        printf("\n");
    }
    else
        printf("DAMEDANE\n");

完整代码

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

typedef struct
{
    int i;       // 编号
    char s[51];  // 姓名
    char r[10];  // 职业
    int l;       // 等级
    int is_used; // 判断是否被调度过了
} individual;

int M, N;
individual gather[1005]; // 人员集合
int cmp_table[27];       // 比较职业的索引表
individual *queue[1000]; // 调动人员的队列

int cmp_1(const void *a, const void *b);
int cmp_2(const void *a, const void *b);
void select(int n, char *r, int lmin, int lmax);

int main()
{

    // 初始化cmp_table
    cmp_table['s' - 'a'] = 7;
    cmp_table['d' - 'a'] = 6;
    cmp_table['p' - 'a'] = 5;
    cmp_table['e' - 'a'] = 4;
    cmp_table['n' - 'a'] = 3;
    cmp_table['a' - 'a'] = 2;
    cmp_table['f' - 'a'] = 1;
    cmp_table['i' - 'a'] = 0;

    scanf("%d%d", &N, &M);
    for (int i = 0; i < N; i++)
        // 读入干员数据
        scanf("%d %s %s %d", &gather[i].i, gather[i].s, gather[i].r, &gather[i].l);

    // 第一次排序,输出
    qsort(gather, N, sizeof(gather[0]), cmp_2);
    for (int i = 0; i < N; i++)
        printf("%d %s %s %d\n", gather[i].i, gather[i].s, gather[i].r, gather[i].l);

    // 第二次排序,输出
    qsort(gather, N, sizeof(gather[0]), cmp_1);
    for (int i = 0; i < N; i++)
        printf("%d %s %s %d\n", gather[i].i, gather[i].s, gather[i].r, gather[i].l);

    int n, lmin, lmax;
    char r[10];
    for (int i = 0; i < M; i++)
    {
        scanf("%d %s %d %d", &n, r, &lmin, &lmax);
        select(n, r, lmin, lmax);
    }

    return 0;
}

int cmp_1(const void *a, const void *b)
{
    individual *e1 = (individual *)a;
    individual *e2 = (individual *)b;
    if (e1->r[2] != e2->r[2])
        return cmp_table[e1->r[2] - 'a'] - cmp_table[e2->r[2] - 'a'];
    return e1->i - e2->i;
}

int cmp_2(const void *a, const void *b)
{
    individual *e1 = (individual *)a;
    individual *e2 = (individual *)b;
    if (e1->l != e2->l)
        return e1->l - e2->l;
    return e1->i - e2->i;
}

void select(int n, char *r, int lmin, int lmax)
{
    int beg; // 目标职业起始位置
    for (beg = 0; beg < N; beg++)
    {
        if (strcmp(gather[beg].r, r) == 0) // 找到了起始位置
            break;
    }

    if (beg == N)
    { // 没有该职业,调动失败
        printf("DAMEDANE\n");
        return;
    }

    int queue_num = 0; // 满足要求的个数

    for (int i = beg; i < N; i++)
    {
        if (strcmp(gather[i].r, r)) // 该职业的已经遍历完了
            break;
        if (!gather[i].is_used && gather[i].l >= lmin && gather[i].l <= lmax)
        { // 入队
            queue[queue_num++] = gather + i;
        }
        if (queue_num == n) // 够了n个,跳出循环
            break;
    }
    if (queue_num == n)
    { // 满足调动要求
        for (int i = 0; i < queue_num; i++)
        {
            printf("%d ", queue[i]->i);
            queue[i]->is_used = 1;
        }
        printf("\n");
    }
    else
        printf("DAMEDANE\n");
}

2. Wendy的程序

题目

题目背景

最近Toby突发灵感创造了新的一种编程语言,并将其取名为Wody。并且邀请Wendy来测试。

然后Toby和Wendy分别写了两个程序,你的任务是要判断这两个程序的输出是否一致。

问题描述

因为功能尚不完善,所以Wody现在不支持读入数据,唯一的数据类型就是int型数组,而且只支持四种操作,不仅如此,Wody语言所有的代码都必须在一行写完,而且中间不能有任何空格。

以下是四种操作:

  • [a1,a2,...,an]:创建一个长度为n,内部元素依次为 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an的数组。这个表达式的值就是这个数组;
  • merge(list1,list2):将两个数组拼接。list1,list2是两个数组,这个表达式的值是将两个数组首尾相连形成的新数组;
  • sort(list):将list数组从小到大排序。这个表达式的值就是排序后的数组;
  • shuffle(list):利用下面给定的函数随机打乱list数组。这个表达式的值就是打乱后的数组(用于打乱的函数和方法会在下面给出)。
/* mylist是欲打乱的数组的首地址(或数组名),len是数组长度 */
void shuffle(int* mylist, int len)
{
    static unsigned lucky_number = 520;
    while(len > 1)
    {
        int temp = *mylist;
        *mylist = *(mylist + lucky_number % len);
        *(mylist + lucky_number % len) = temp;
        mylist++; len--;
        lucky_number *= 113344;
        lucky_number += 993311;
    }
}

题目要求:题目会给出两行代码,你需要比较两行代码的结果是否一致,一致输出TobyWendy,不一致输出Failed(输出不带引号)

输入形式

两行字符串分别代表两个程序。

保证字符串中间没有任何空白符。

保证只会出现"merge",“sort”,"shuffle"以及小括号、中括号、逗号和数字。

保证输入符合Wody的语法要求。

保证所有数字在int范围内。

保证不存在空数组(即中括号之间必有至少一个数)。

输出形式

一行,“TobyWendy"或者"Failed”。

样例输入1

[1,2,5,-1]
shuffle(merge([1,2,-1],[5]))

样例输出1

TobyWendy

样例1解释

两个程序的结果都是[1,2,5,-1]

样例输入2

sort([7,3,2,8])
shuffle(shuffle([7,3,2,8]))

样例输出2

TobyWendy

样例2解释

两个程序的结果都是[2,3,7,8]

样例输入3

merge(sort([9,1,5]),[-5])
sort(merge([9,1,5],[-5]))

样例输出3

Failed

样例3解释

第一个程序的结果是[1,5,9,-5] 第二个程序的结果是[-5,1,5,9]

数据范围与提示
数据范围

每段程序字符串长度<200

提示
  • 创建数组操作可以用malloc指令
  • merge操作可以realloc然后memcpy
  • sort操作可以用qsort函数
  • shuffle操作函数已经给出了
  • 所以本题只是考一个括号匹配而已

问题分析

提示说的realloc可以自行去了解一下,我没有用这个去实现,但题目的确给了我另外两点提示:

  1. 的确是括号匹配的问题,这个和作业题里的表达式计算有点像,肯定是要用到栈了;
  2. 本题的基本数据类型应当是数组——而且是不定长的数组(有点像MATLAB的设计),而为了实现这一点,我不打算用C语言内置的数组类型,而打算自己定义一个结构体(一个int型指针表示数组首地址加一个int型变量记录数组的长度)来表示数组。

而具体如何设计呢?通过观察,我们发现其实本题只有两种匹配

  1. 小括号的匹配——这个显然就是函数了,所以我们可以给题目中的函数分别记录为1,2,3,档读到左小括号时,判断出相应的函数,并把对应的数字入栈;当读到右小括号时,对栈顶的相应数量的数组类型的变量进行操作即可——其实通过这个分析,就可以得知我们的表达式栈应当是int型(记录是哪个函数)和数组类型的联合——这和表达式计算时很像;
  2. 中括号的匹配,读到中括号时,把相应的数组构建出来后直接把这个数组入栈。

具体处理过程

首先我们自定义数组类型:

typedef struct
{
    int *begin; // 首地址
    int num;    // 元素个数
} array;        // 数组类型

然后定义表达式的结点类型,并创建表达式栈:

typedef struct
{
    union
    {
        array arr;
        int op; // 0为merge, 1为sort,2为shuffle
    } data;
    int type; // 标志变量,0为数组数据,1为操作函数

} node;
node stack[1000]; // 栈

然后主函数中读入两次字符串,并计算其值:

    int res1_num, res2_num; // 结果数组的元素个数
    int *res1, *res2;       // 结果数组
    char buf[512];
    gets(buf);
    res1_num = calculate(buf, &res1);
    gets(buf);
    res2_num = calculate(buf, &res2);

calculate函数是本题的核心,它接受一个字符串的输入,返回结果数组和其长度,其中应当是通过一个主循环遍历表达式各位的:

int calculate(char *buf, int **res_num)
{
    int stack_num = 0; // 表达式栈中元素个数
    for (int i = 0; buf[i]; i++)
    {
        // 进行操作
    }
}

其实说是要遍历字符串的每一位,实际上只要挑一些特殊的字符去判断就行:

// calculate函数主循环中
        if (buf[i] == '(')
        { // 函数的左括号
		// 这里要判断是哪一个函数,并把相应的数字入栈
        }
        else if (buf[i] == ')')
        { // 函数的右括号
			// 这里要计算
        }
        else if (buf[i] == '[')
        {                 // 是数组
       		// 计算出数组,并把数组入栈
        }
    }
    *res_num = stack[0].data.arr.begin;
    return stack[0].data.arr.num;

在读到’('时,要判断上一个函数是什么,只要判断前两位的字符是什么(观察一下sort,merge, shuffle,发现他们倒数第二个字符都不一样,可以通过这一点来判断(和上一题判断职业先后时取巧原理相同)):

// 读到左括号时的具体处理
        if (buf[i] == '(')
        { // 函数的左括号
            stack[stack_num].type = 1;
            if (buf[i - 2] == 'g') // 是merge
                stack[stack_num++].data.op = 0;
            else if (buf[i - 2] == 'r') // 是sort
                stack[stack_num++].data.op = 1;
            else // 是shuffle
                stack[stack_num++].data.op = 2;
        }

当读到右括号时,要判断栈中上一个函数是什么,由于sortshuffle函数的操作对象都是一个数组,所以如果是这两个函数的话,栈中的上两个元素一定是int型(代表函数),否则,就是merge(操作两个数组)了:

// 读到右括号时的具体处理
		else if (buf[i] == ')')
        { // 函数的右括号
            if (stack[stack_num - 2].type == 1) // 上一个函数是sort或shuffle
            {
                if (stack[stack_num - 2].data.op == 1)
                { // 是sort

                }
                else
                { // 是shuffle

                }
            }
            else
            { // 上一个函数是merge

            }
        }

然后对这三种情况分别操作即可:排序就用qsort,shuffle已经给了,merge的话,就新建一个array类型变量记录就行——处理后把结果数组入栈,并修改对应的stack_num变量:

// 读到右括号时的具体处理
        else if (buf[i] == ')')
        { // 函数的右括号
            if (stack[stack_num - 2].type == 1)
            {
                if (stack[stack_num - 2].data.op == 1)
                { // 是sort
                    qsort(stack[stack_num - 1].data.arr.begin, stack[stack_num - 1].data.arr.num, sizeof(int), cmp);
                    stack[stack_num - 2] = stack[stack_num - 1];
                    stack_num--;
                }
                else
                { // 是shuffle
                    shuffle(stack[stack_num - 1].data.arr.begin, stack[stack_num - 1].data.arr.num);
                    stack[stack_num - 2] = stack[stack_num - 1];
                    stack_num--;
                }
            }
            else
            { // 上一个函数是merge
                array tmp;
                tmp.begin = (int *)malloc(sizeof(int) * (stack[stack_num - 1].data.arr.num + stack[stack_num - 2].data.arr.num));
                tmp.num = stack[stack_num - 1].data.arr.num + stack[stack_num - 2].data.arr.num;
                for (int j = 0; j < stack[stack_num - 2].data.arr.num; j++)
                {
                    tmp.begin[j] = stack[stack_num - 2].data.arr.begin[j];
                }
                for (int j = stack[stack_num - 2].data.arr.num; j < stack[stack_num - 1].data.arr.num + stack[stack_num - 2].data.arr.num; j++)
                {
                    tmp.begin[j] = stack[stack_num - 1].data.arr.begin[j - stack[stack_num - 2].data.arr.num];
                }
                stack[stack_num - 3].type = 0;
                stack[stack_num - 3].data.arr = tmp;
                stack_num -= 2;
            }
        }

cmp函数的设计如下:

int cmp(const void *a, const void *b)
{
    return *((int *)a) - *((int *)b);
}

然后我们考虑下一种情况,读到’['(即数组),我们要把这个数组读完,并把结果入栈,具体实现如下:

        else if (buf[i] == '[')
        {                    // 是数组
            int tmp[100];    // 暂存读到的数字
            int num = 0;     // 该数组的数字个数
            int tmp_num = 0; // 暂存数字
            int sign = 1;    // 判断正负的标识
            i++;
            while (1)
            {
                if (buf[i] == '-') // 先判断负号
                {
                    sign = -1;
                    i++;
                }
                else
                    sign = 1;

                while (buf[i] != ',' && buf[i] != ']') // 把当前这个数字读完
                {
                    tmp_num = tmp_num * 10 + buf[i] - '0';
                    i++;
                }
                tmp_num *= sign;      // 带上正负
                tmp[num++] = tmp_num; // 该数字进入临时数组
                tmp_num = 0;
                if (buf[i] == ',')
                    i++;
                else // 为']'
                {
                    array temp;
                    temp.num = num;
                    temp.begin = (int *)malloc(sizeof(int) * num);
                    for (int j = 0; j < num; j++)
                    {
                        temp.begin[j] = tmp[j];
                    }
                    // 结果数组入栈
                    stack[stack_num].type = 0;
                    stack[stack_num].data.arr = temp;
                    stack_num++;
                    break;
                }
            }
        }
    }

最后通过二级指针返回表达式最终结果的数组,并return返回数组中元素个数:

    *res_num = stack[0].data.arr.begin;
    return stack[0].data.arr.num;

然后再在主函数中进行输出:先判断两数组是否等长,不等长直接输出Failed,等长的话继续判断其各位是否相等:

// 主函数中
    if (res1_num != res2_num)
        printf("Failed");
    else
    {
        for (int i = 0; i < res1_num; i++)
        {
            if (res1[i] != res2[i])
            {
                printf("Failed");
                return 0;
            }
        }
        printf("TobyWendy");
    }

完整代码

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

typedef struct
{
    int *begin; // 首地址
    int num;    // 元素个数
} array;        // 数组类型

typedef struct
{
    union
    {
        array arr;
        int op; // 0为merge, 1为sort,2为shuffle
    } data;
    int type; // 标志变量,0为数组数据,1为操作函数

} node;
node stack[1000]; // 栈

void shuffle(int *mylist, int len);
int cmp(const void *a, const void *b);
int calculate(char *buf, int **res_num);

int main()
{

    int res1_num, res2_num; // 结果数组的元素个数
    int *res1, *res2;       // 结果数组
    char buf[512];
    gets(buf);
    res1_num = calculate(buf, &res1);
    gets(buf);
    res2_num = calculate(buf, &res2);

    if (res1_num != res2_num)
        printf("Failed");
    else
    {
        for (int i = 0; i < res1_num; i++)
        {
            if (res1[i] != res2[i])
            {
                printf("Failed");
                return 0;
            }
        }
        printf("TobyWendy");
    }

    return 0;
}

int calculate(char *buf, int **res_num)
{
    int stack_num = 0; // 表达式栈中元素个数
    for (int i = 0; buf[i]; i++)
    {
        if (buf[i] == '(')
        { // 函数的左括号
            stack[stack_num].type = 1;
            if (buf[i - 2] == 'g') // 是merge
                stack[stack_num++].data.op = 0;
            else if (buf[i - 2] == 'r') // 是sort
                stack[stack_num++].data.op = 1;
            else // 是shuffle
                stack[stack_num++].data.op = 2;
        }
        else if (buf[i] == ')')
        { // 函数的右括号
            if (stack[stack_num - 2].type == 1)
            {
                if (stack[stack_num - 2].data.op == 1)
                { // 是sort
                    qsort(stack[stack_num - 1].data.arr.begin, stack[stack_num - 1].data.arr.num, sizeof(int), cmp);
                    stack[stack_num - 2] = stack[stack_num - 1];
                    stack_num--;
                }
                else
                { // 是shuffle
                    shuffle(stack[stack_num - 1].data.arr.begin, stack[stack_num - 1].data.arr.num);
                    stack[stack_num - 2] = stack[stack_num - 1];
                    stack_num--;
                }
            }
            else
            { // 上一个函数是merge
                array tmp;
                tmp.begin = (int *)malloc(sizeof(int) * (stack[stack_num - 1].data.arr.num + stack[stack_num - 2].data.arr.num));
                tmp.num = stack[stack_num - 1].data.arr.num + stack[stack_num - 2].data.arr.num;
                for (int j = 0; j < stack[stack_num - 2].data.arr.num; j++)
                {
                    tmp.begin[j] = stack[stack_num - 2].data.arr.begin[j];
                }
                for (int j = stack[stack_num - 2].data.arr.num; j < stack[stack_num - 1].data.arr.num + stack[stack_num - 2].data.arr.num; j++)
                {
                    tmp.begin[j] = stack[stack_num - 1].data.arr.begin[j - stack[stack_num - 2].data.arr.num];
                }
                stack[stack_num - 3].type = 0;
                stack[stack_num - 3].data.arr = tmp;
                stack_num -= 2;
            }
        }
        else if (buf[i] == '[')
        {                    // 是数组
            int tmp[100];    // 暂存读到的数字
            int num = 0;     // 该数组的数字个数
            int tmp_num = 0; // 暂存数字
            int sign = 1;    // 判断正负的标识
            i++;
            while (1)
            {
                if (buf[i] == '-') // 先判断负号
                {
                    sign = -1;
                    i++;
                }
                else
                    sign = 1;

                while (buf[i] != ',' && buf[i] != ']') // 把当前这个数字读完
                {
                    tmp_num = tmp_num * 10 + buf[i] - '0';
                    i++;
                }
                tmp_num *= sign;      // 带上正负
                tmp[num++] = tmp_num; // 该数字进入临时数组
                tmp_num = 0;
                if (buf[i] == ',')
                    i++;
                else // 为']'
                {
                    array temp;
                    temp.num = num;
                    temp.begin = (int *)malloc(sizeof(int) * num);
                    for (int j = 0; j < num; j++)
                    {
                        temp.begin[j] = tmp[j];
                    }
                    // 结果数组入栈
                    stack[stack_num].type = 0;
                    stack[stack_num].data.arr = temp;
                    stack_num++;
                    break;
                }
            }
        }
    }
    *res_num = stack[0].data.arr.begin;
    return stack[0].data.arr.num;
}

int cmp(const void *a, const void *b)
{
    return *((int *)a) - *((int *)b);
}

/* mylist是欲打乱的数组的首地址(或数组名),len是数组长度 */
void shuffle(int *mylist, int len)
{
    static unsigned lucky_number = 520;
    while (len > 1)
    {
        int temp = *mylist;
        *mylist = *(mylist + lucky_number % len);
        *(mylist + lucky_number % len) = temp;
        mylist++;
        len--;
        lucky_number *= 113344;
        lucky_number += 993311;
    }
}

3. 包装机

题目

问题描述

一种自动包装机的结构如图 1 所示。

首先机器中有 N 条轨道,放置了一些物品。

轨道下面有一个筐。

当某条轨道的按钮被按下时,活塞向左推动,将轨道尽头的一件物品推落筐中。

当 0 号按钮被按下时,机械手将抓取筐顶部的一件物品,放到流水线上。

图 2 显示了顺序按下按钮 3、2、3、0、1、2、0 后包装机的状态。

在这里插入图片描述

图 1 自动包装机的结构

在这里插入图片描述

图 2 顺序按下按钮 3、2、3、0、1、2、0 后包装机的状态

一种特殊情况是,因为筐的容量是有限的,当筐已经满了,但仍然有某条轨道的按钮被按下时,系统应强制启动 0 号键,先从筐里抓出一件物品,再将对应轨道的物品推落。

此外,如果轨道已经空了,再按对应的按钮不会发生任何事;同样的,如果筐是空的,按 0 号按钮也不会发生任何事。

现给定一系列按钮操作,请你依次列出流水线上的物品。

输入形式

输入第一行给出 3 个正整数 N、M 和 Smax,分别为轨道的条数(于是轨道从 1 到 N 编号)、每条轨道初始放置的物品数量、以及筐的最大容量。

随后 N 行,每行给出 M 个英文大写字母,表示每条轨道的初始物品摆放。

最后一行给出一系列数字,顺序对应被按下的按钮编号,直到 −1 标志输入结束,这个数字不要处理。

数字间以空格分隔。题目保证至少会取出一件物品放在流水线上。

输出形式

在一行中顺序输出流水线上的物品,不得有任何空格。

数据范围

1≤N≤100,
1≤M≤1000,
1≤Smax≤100,
按下按钮的操作不超过 105 次。

样例输入

3 4 4
GPLT
PATA
OMSA

样例输出

MATA

问题分析

本题是一个大模拟,按照题目要求处理就好,步骤如下:

  1. 创建栈的结构,并读入数据(注意读轨道上的数据时要把字符串“颠倒”过来,因为正好和栈的顺序相反);
  2. 进行操作,当把筐中元素输出时,要先特判一下筐是否是空的;当把轨道的元素压到筐中时,首先要特判轨道是否空了,然后再特判筐是否满了——注意顺序不要反了!否则逻辑就出问题了(想一下就明白了)

具体处理过程

首先声明栈的类型,并创建全局变量:

typedef struct
{
    char data[1005]; // 轨道上的物品
    int top;         // 栈顶上一个位置(栈空时为0)
} stack;             // 栈类型

stack gather[105]; // 轨道
stack basket;      // 筐
int N, M, Smax;

主函数中读入数据:

// 主函数中
    scanf("%d%d%d", &N, &M, &Smax);
    getchar();                   // 吞掉换行符
    for (int i = 1; i <= N; i++) // 题目要求轨道从1开始编号
    {
        gets(gather[i].data);
        gather[i].top = strlen(gather[i].data);
        convers(gather[i].data, gather[i].top); // 顺序颠倒
    }

其中convers函数实现颠倒字符串,实现如下:

void convers(char *s, int len)
{
    int i = 0, j = len - 1, tmp;
    while (i < j)
    {
        tmp = s[i];
        s[i++] = s[j];
        s[j--] = tmp;
    }
}

然后读入相应的操作:

// 主函数中
    int op;
    while (scanf("%d", &op) && op != -1)
    {
        if (op == 0)
        {                        // 要出筐
            if (basket.top == 0) // 筐上现在没有物品
                continue;
            printf("%c", basket.data[--basket.top]); // 输出一个到流水线上
        }
        else
        {
            if (gather[op].top == 0) // 轨道上现在没有物品了
                continue;
            if (basket.top == Smax)
            {
                // 筐上现在已经满了
                printf("%c", basket.data[Smax - 1]);                         // 输出栈顶元素
                basket.data[Smax - 1] = gather[op].data[--(gather[op].top)]; // 取轨道栈顶元素放到筐中
            }
            else
                basket.data[basket.top++] = gather[op].data[--(gather[op].top)];
        }
    }

完整代码

#include <stdio.h>
#include <string.h>

typedef struct
{
    char data[1005]; // 轨道上的物品
    int top;         // 栈顶上一个位置(栈空时为0)
} stack;             // 栈类型

stack gather[105]; // 轨道
stack basket;      // 筐
int N, M, Smax;

void convers(char *s, int len);

int main()
{
    scanf("%d%d%d", &N, &M, &Smax);
    getchar();                   // 吞掉换行符
    for (int i = 1; i <= N; i++) // 题目要求轨道从1开始编号
    {
        gets(gather[i].data);
        gather[i].top = strlen(gather[i].data);
        convers(gather[i].data, gather[i].top); // 顺序颠倒
    }

    int op;
    while (scanf("%d", &op) && op != -1)
    {
        if (op == 0)
        {                        // 要出筐
            if (basket.top == 0) // 筐上现在没有物品
                continue;
            printf("%c", basket.data[--basket.top]); // 输出一个到流水线上
        }
        else
        {
            if (gather[op].top == 0) // 轨道上现在没有物品了
                continue;
            if (basket.top == Smax)
            {
                // 筐上现在已经满了
                printf("%c", basket.data[Smax - 1]);                         // 输出栈顶元素
                basket.data[Smax - 1] = gather[op].data[--(gather[op].top)]; // 取轨道栈顶元素放到筐中
            }
            else
                basket.data[basket.top++] = gather[op].data[--(gather[op].top)];
        }
    }

    return 0;
}

void convers(char *s, int len)
{
    int i = 0, j = len - 1, tmp;
    while (i < j)
    {
        tmp = s[i];
        s[i++] = s[j];
        s[j--] = tmp;
    }
}

4. 查家谱(BUAA数据结构16级期末考试题)

题目

问题描述

同姓氏中国人见面常说的一句话是“我们五百年前可能是一家”。从当前目录下的文件in.txt中读入一家谱,从标准输入读入两个人的名字(两人的名字肯定会在家谱中出现),编程查找判断这两个人相差几辈,若同辈,还要查找两个人共同的最近祖先以及与他(她)们的关系远近。假设输入的家谱中每人最多有两个孩子,例如下图是根据输入形成的一个简单家谱:
在这里插入图片描述

通过该家谱,可以看到wangliang、wangguoping和wangguoan都有两个孩子,wangtian、wangxiang和wangsong有一个孩子,wangguang、wangqinian、wangping和wanglong还没有孩子。若要查找的两个人是wangqinian和wangguoan,从家谱中可以看出两人相差两辈;若要查找的两个人是wangping和wanglong,可以看出两人共同的最近祖先是wangguoan,和两人相差两辈。

输入形式

从当前目录下的in.txt中读入家谱。文件中第一行是家谱中有孩子的人数,后面每行内容是每个人的名字和其孩子的名字,名字都由1到20个英文字母构成,各名字间以一个空格分隔,整个家谱中的人员都不会重名;若只有一个孩子,则第二个孩子的名字为NULL;若没有孩子,则不需输入;输入的顺序是按照辈份从高到低依次输入,若孩子A出现在孩子B之前,则A的孩子应在B的孩子之前输入。假设以该形式读入的家谱肯定能够形成类似上图所示的一棵二叉树形式的家谱,家谱中任何两人相差的辈份不会超过100。

从标准输入读入要查找的两个人的名字,两名字间也以一个空格分隔。

输出形式

所有信息输出到标准输出上。

若要查找的两人不同辈,则先输出辈份低的名字,再输出辈份高的名字,然后输出相差几辈,都以一个空格分隔;

若两人同辈,按照两人名字从标准输入读取的先后顺序,分行输出两人的最近祖先名字、两人姓名以及相差几辈,各数据间以一个空格分隔。

样例输入1

假设当前目录下in.txt文件内容为:

6

wangliang wangguoping wangguoan

wangguoping wangtian wangguang

wangguoan wangxiang wangsong

wangtian wangqinian NULL

wangxiang wangping NULL

wangsong wanglong NULL

从标准输入读取:

wangqinian wangliang

样例输出1

wangqinian wangliang 3

样例输入2

假设当前目录下in.txt文件内容为:

6

wangliang wangguoping wangguoan

wangguoping wangtian wangguang

wangguoan wangxiang wangsong

wangtian wangqinian NULL

wangxiang wangping NULL

wangsong wanglong NULL

从标准输入读取:

wangping wanglong

样例输出2

wangguoan wangping 2

wangguoan wanglong 2

样例说明

【样例1说明】

家谱中输入了六个人名及其孩子的人名,形成了“问题描述”中的家谱,要查找的两人是wangqinian和wangliang,wangliang比wangqinian高3辈。

【样例2说明】

和样例1同样输入了一家谱,wangping和wanglong共同的最近祖先是wangguoan,该祖先与两人相差两辈。

说在前面

所有树都可以用指针式和数组式两种方法实现,数组式往往bug更少,本题我写了指针式的题解,并给出数组式的代码,下一题会给出数组式的题解,并给出指针式的代码。

问题分析

本题是考察的是树中的一个经典算法——最近公共祖先,而且是已经简化过的了——因为本题只有在两结点同层次时才要求找最近公共祖先。为了实现这一点,我在建树时就多加一个域指向双亲——这样就简化为两个等长链表寻找第一个公共结点了。

指针式具体处理过程

首先声明结构类型,并开全局变量:

typedef struct individual
{
    char name[25];               // 名字
    struct individual *child[2]; // 孩子
    struct individual *parent;   // 双亲结点地址
    int level;                   // 层次(辈分)
} individual;

char name_1[25], name_2[25];     // 两个人的名字
individual *target_1, *target_2; // 两个目标的地址
int dif;                         // 到最近公共祖先的距离

主函数中也先把基本数据读入,并进行一系列初始化,然后建树:

// 主函数中
    scanf("%s %s", name_1, name_2); // 读入名字
    freopen("in.txt", "r", stdin); // 输入重定向
    individual *root = NULL;       // 家谱
    char tmp_parent[25];
    char tmp_child[25];
    int num;
    scanf("%d", &num);
    for (int i = 0; i < num; i++)
    {
        scanf("%s", tmp_parent);
        for (int j = 0; j < 2; j++)
        {
            scanf("%s", tmp_child);
            if (strcmp(tmp_child, "NULL") != 0)          // 有这个孩子
                insert(tmp_parent, tmp_child, &root, 1); // 该孩子插入树中
        }
    }

insert函数的设计我们已经太熟悉了(可以参考我第五次作业,以及往年考题的文章)——谨记要传二级指针,不然要de很久的bug,同时,我们可以在建树的时候就把target_1, target_2记录下来:

void insert(char *target_parent, char *target_child, individual **root, int height)
{
    if (*root == NULL)
    { // 第一个结点读入情况特判
        *root = (individual *)malloc(sizeof(individual));
        (*root)->level = 1;
        strcpy((*root)->name, target_parent);
        (*root)->child[0] = (*root)->child[1] = NULL;
        (*root)->parent = NULL;

        if (strcmp(name_1, (*root)->name) == 0) // 找到了目标1
            target_1 = *root;
        if (strcmp(name_2, (*root)->name) == 0) // 找到了目标2
            target_2 = *root;
    }

    if (strcmp((*root)->name, target_parent) == 0)
    { // 找到了目标双亲结点
        individual *tmp = (individual *)malloc(sizeof(individual));
        tmp->level = height + 1;
        strcpy(tmp->name, target_child);
        tmp->child[0] = tmp->child[1] = NULL;
        tmp->parent = *root;

        if (strcmp(name_1, tmp->name) == 0) // 找到了目标1
            target_1 = tmp;
        if (strcmp(name_2, tmp->name) == 0) // 找到了目标2
            target_2 = tmp;

        if ((*root)->child[0] == NULL)
            (*root)->child[0] = tmp;
        else
            (*root)->child[1] = tmp;
        return;
    }
    for (int i = 0; i < 2; i++)
    {
        if ((*root)->child[i])
            insert(target_parent, target_child, &((*root)->child[i]), height + 1);
    }
}

下面实现最近公共祖先的算法,由于两结点深度相同,所以只要两条链表同时顺着往上找就行:

individual *gca(individual *p1, individual *p2) // 寻找两结点的最近公共祖先
{
    int l1 = p1->level;
    int l2 = p2->level;

    while (p1)
    {
        if (strcmp(p1->name, p2->name) == 0) // 找到了
            return p1;
        dif++;
        p1 = p1->parent;
        p2 = p2->parent;
    }
}

然后在主函数中输出,我这里写的比较丑,但也懒得美化了

// 主函数中
    int level_1 = target_1->level, level_2 = target_2->level;
    if (level_1 != level_2) // 二者不同辈
        printf("%s %s %d", level_1 > level_2 ? target_1->name : target_2->name, level_2 > level_1 ? target_1->name : target_2->name, level_1 > level_2 ? level_1 - level_2 : level_2 - level_1);
    else
        printf("%s %s %d\n%s %s %d", gca(target_1, target_2)->name, target_1->name, dif, gca(target_1, target_2)->name, target_2->name, dif / 2);
    // 第二项要除以2是因为这种“简便”的输出方式调动了两次gca函数

指针式的完整代码

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

typedef struct individual
{
    char name[25];               // 名字
    struct individual *child[2]; // 孩子
    struct individual *parent;   // 双亲结点地址
    int level;                   // 层次(辈分)
} individual;

char name_1[25], name_2[25];     // 两个人的名字
individual *target_1, *target_2; // 两个目标的地址
int dif;                         // 到最近公共祖先的距离

void insert(char *target_parent, char *target_child, individual **root, int height);
individual *gca(individual *p1, individual *p2);

int main()
{

    scanf("%s %s", name_1, name_2); // 读入名字
    freopen("in.txt", "r", stdin);  // 输入重定向
    individual *root = NULL;        // 家谱
    char tmp_parent[25];
    char tmp_child[25];
    int num;
    scanf("%d", &num);
    for (int i = 0; i < num; i++)
    {
        scanf("%s", tmp_parent);
        for (int j = 0; j < 2; j++)
        {
            scanf("%s", tmp_child);
            if (strcmp(tmp_child, "NULL") != 0)          // 有这个孩子
                insert(tmp_parent, tmp_child, &root, 1); // 该孩子插入树中
        }
    }

    // 建树完成
    int level_1 = target_1->level, level_2 = target_2->level;
    if (level_1 != level_2) // 二者不同辈
        printf("%s %s %d", level_1 > level_2 ? target_1->name : target_2->name, level_2 > level_1 ? target_1->name : target_2->name, level_1 > level_2 ? level_1 - level_2 : level_2 - level_1);
    else
        printf("%s %s %d\n%s %s %d", gca(target_1, target_2)->name, target_1->name, dif, gca(target_1, target_2)->name, target_2->name, dif / 2);
    // 第二项要除以2是因为这种“简便”的输出方式调动了两次gca函数

    return 0;
}

void insert(char *target_parent, char *target_child, individual **root, int height)
{
    if (*root == NULL)
    { // 第一个结点读入情况特判
        *root = (individual *)malloc(sizeof(individual));
        (*root)->level = 1;
        strcpy((*root)->name, target_parent);
        (*root)->child[0] = (*root)->child[1] = NULL;
        (*root)->parent = NULL;

        if (strcmp(name_1, (*root)->name) == 0) // 找到了目标1
            target_1 = *root;
        if (strcmp(name_2, (*root)->name) == 0) // 找到了目标2
            target_2 = *root;
    }

    if (strcmp((*root)->name, target_parent) == 0)
    { // 找到了目标双亲结点
        individual *tmp = (individual *)malloc(sizeof(individual));
        tmp->level = height + 1;
        strcpy(tmp->name, target_child);
        tmp->child[0] = tmp->child[1] = NULL;
        tmp->parent = *root;

        if (strcmp(name_1, tmp->name) == 0) // 找到了目标1
            target_1 = tmp;
        if (strcmp(name_2, tmp->name) == 0) // 找到了目标2
            target_2 = tmp;

        if ((*root)->child[0] == NULL)
            (*root)->child[0] = tmp;
        else
            (*root)->child[1] = tmp;
        return;
    }
    for (int i = 0; i < 2; i++)
    {
        if ((*root)->child[i])
            insert(target_parent, target_child, &((*root)->child[i]), height + 1);
    }
}

individual *gca(individual *p1, individual *p2) // 寻找两结点的最近公共祖先
{
    int l1 = p1->level;
    int l2 = p2->level;

    while (p1)
    {
        if (strcmp(p1->name, p2->name) == 0) // 找到了
            return p1;
        dif++;
        p1 = p1->parent;
        p2 = p2->parent;
    }
}

数组式的完整代码

#include <stdio.h>
#include <string.h>

typedef struct
{
    char name[25]; // 名字
    int child1;
    int child2;
    int parent;
    int level;
} node;

node tree[100]; // 家谱
int pos;        // 下一个结点的下标
char name_1[25], name_2[25];
int target_1, target_2; // 两个目标结点的下标

void insert(char *target_parent, char *target_child);

int main()
{
    pos = 2;
    scanf("%s %s", name_1, name_2); // 读入名字

    // freopen("in.txt", "r", stdin);  // 输入重定向
    char tmp_parent[25];
    char tmp_child[25];
    int num;
    scanf("%d", &num);
    for (int i = 0; i < num; i++)
    {
        scanf("%s", tmp_parent);
        for (int j = 0; j < 2; j++)
        {
            scanf("%s", tmp_child);
            if (strcmp(tmp_child, "NULL") != 0) // 有这个孩子
                insert(tmp_parent, tmp_child);
        }
    }
    // 建树完成
    if (tree[target_1].level != tree[target_2].level)
        printf("%s %s %d", tree[target_1].level > tree[target_2].level ? tree[target_1].name : tree[target_2].name, tree[target_2].level > tree[target_1].level ? tree[target_1].name : tree[target_2].name, tree[target_1].level > tree[target_2].level ? tree[target_1].level - tree[target_2].level : tree[target_2].level - tree[target_1].level);
    else
    {
        int dif = 0;
        while (target_1)
        {
            if (strcmp(tree[target_1].name, tree[target_2].name) == 0)
            { // 找到了最近公共祖先
                printf("%s %s %d\n", tree[target_1].name, name_1, dif);
                printf("%s %s %d\n", tree[target_2].name, name_2, dif);
                break;
            }
            target_1 = tree[target_1].parent;
            target_2 = tree[target_2].parent;
            dif++;
        }
    }
    return 0;
}

void insert(char *target_parent, char *target_child)
{
    if (tree[1].level == 0)
    { // 特判第一个读入数据
        strcpy(tree[1].name, target_parent);
        tree[1].child1 = tree[1].child2 = tree[1].parent = 0; // 初始化(由于是全局变量,所以这句可以不写)
        tree[1].level = 1;
        if (strcmp(target_parent, name_1) == 0)
            target_1 = 1;
        if (strcmp(target_parent, name_2) == 0)
            target_2 = 1;
    }

    for (int i = 1; i < pos; i++)
    { // 遍历家谱去找
        if (strcmp(tree[i].name, target_parent) == 0)
        { // 找到了
            tree[pos].parent = i;
            tree[pos].level = tree[i].level + 1;
            strcpy(tree[pos].name, target_child);

            if (strcmp(target_child, name_1) == 0)
                target_1 = pos;
            if (strcmp(target_child, name_2) == 0)
                target_2 = pos;

            if (tree[i].child1 != 0) // 该结点还没有第一个孩子
                tree[i].child1 = pos;
            else
                tree[i].child2 = pos;
            pos++;
            return;
        }
    }
}

5. 喜鹊筑巢

题目

问题描述

喜鹊通常会选择一棵树最高处分枝最多(即分枝最多的结点中高度最高的结点)的地方筑巢(结构最稳定)。给定一棵三叉树,计算出喜鹊筑窝的地点(可能有多个,多个时应按树前序遍历的顺序给出相应的结点信息,包括结点号以及按照前序遍历第几个访问到该结点)。例如下图是一颗三叉树:

在这里插入图片描述

该树中结点最多有三个分枝,拥有三个分枝的最高的结点为23号和15号结点,按照前序遍历顺序将会第19个访问到23号结点,第31个访问到15号结点。

要求:按照前序遍历访问三叉树的分枝时应先访问左分枝,再访问中间分枝,最后访问右分枝;根结点为第1个访问到结点,且树中最多有100个结点。

输入形式

先从控制台输入一个整数表示树结点关系的条目数,接着在下一行开始,从根开始依次输入树结点之间的关系。其中结点编号从数字1开始,如1表示树根结点,其它结点的编号大于1,不会重复,但编号没有规律。树中结点间关系用下面方式描述:

R S1 S2 S3

其中R为分叉结点,S1,S2,S3分别为树叉R的左、中、右子结点,由于是三叉树,S1,S2,S3中至多可以2项为空,该项为空时用0表示。各项间以一个空格分隔,最后有一个回车。如:

1 5 6 0

表明编号1的树根有两个子叉,编号分别为5和6。

注意:结点关系的输入顺序是依次先从根结点输入根与子结点的关系,再输入分叉结点与子结点的关系,输入某个结点关系时,其父结点一定已经存在,但不一定按照层次输入。

输出形式

按照前序遍历顺序输出分枝最多的结点中高度最高的结点信息,每个结点信息占一行,包括结点编号和按照前序遍历第几个访问到该结点,以一个空格分隔数据。

样例输入

18

1 2 3 4

4 5 0 0

3 6 7 8

2 9 10 0

7 21 22 0

21 23 24 0

23 25 26 27

9 28 29 0

28 30 31 37

30 32 0 0

31 33 36 0

33 34 35 0

5 11 12 13

11 14 0 0

12 15 0 0

13 16 0 0

15 17 18 19

19 20 0 0

样例输出

23 19

15 31

样例说明

输入了18条树结点关系,按照这些关系建立的三叉树如上图所示。该树中结点最多有三个分枝,拥有三个分枝的最高的结点为23号和15号结点,按照前序遍历顺序将会第19个访问到23号结点,第31个访问到15号结点。根结点为第1个访问到的结点。

问题分析

本题将给出数组式写法的思路,对于数组式写法不熟悉的同学可以去搜搜字典树的数组式实现方式——这和大作业很相关,会得到一些启示吧,启示核心思路就是以数组的下标来代表结点的位置(下标就相当于指针)

总的思路肯定是先建树,然后进行顺序遍历,但建树时我们最好就能把最后目标的结点的分支数和层次数记录下来——其逻辑是每次插入一个结点后,判断其双亲结点的孩子数是否已经超过当前记录的最大的孩子数,如是,更新;然后再判断其双亲结点的层次数是否大于当前记录的“有最多分叉数的结点的最深层次数”,如是,更新。

然后遍历时,只要判断当前这个结点的这两个域是否等于我们记录的“最大”的变量即可。

数组式具体实现过程

首先声明结点,并创建相应的全局变量:

typedef struct
{
    int number;      // 编号
    int children[3]; // 孩子
    int child_num;   // 孩子数
    int level;       // 层次数
} node;

int max_child;   // 最大的分叉数
int max_level;   // 有最多分叉树的结点的最深深度
node tree[1005]; // 树
int pos;         // 下一个结点的下标
int order = 0;   // 前序序列的编号

主函数中,对各个量进行初始化:

 // 主函数中
	// 初始化
    pos = 2;
    tree[1].level = 1; // 第一个结点的信息已知
    tree[1].number = 1;
    order = 1;

然后读入数据,并插入,由于是数组式的写法,所以找结点时就从头遍历数组即可:

// 主函数中
    int n;
    scanf("%d", &n);
    int parent_number, child_number;
    for (int i = 0; i < n; i++)
    {
        int parent_loc; // 目标双亲结点的位置(下标)
        scanf("%d", &parent_number);
        for (int j = 0; j < pos; j++)
        {
            if (tree[j].number == parent_number) // 找到了双亲结点
            {
                parent_loc = j;
                break;
            }
        }

        int child_num = 0; // 该双亲结点的孩子数
        for (int j = 0; j < 3; j++)
        {
            scanf("%d", &child_number);
            if (child_number != 0)
            {
                child_num++;
                tree[pos].level = tree[parent_loc].level + 1; // 新结点的层次数
                tree[pos].number = child_number;              // 新结点的编号
                tree[parent_loc].children[j] = pos++;         // 双亲结点孩子域更新
            }
        }
        tree[parent_loc].child_num = child_num;
        if (child_num > max_child) // 更新树中最大度
            max_child = child_num;
        if (child_num == max_child && tree[parent_loc].level > max_level) // 更新有最大度的结点的最大深度
            max_level = tree[parent_loc].level;
    }

然后进行前序遍历,并对满足条件的结点输出:

void dfs(int root) // 前序遍历
{
    if (tree[root].child_num == max_child && tree[root].level == max_level) // 找到了目标的筑巢点
        printf("%d %d\n", tree[root].number, order);
    for (int i = 0; i < 3; i++)
    {
        if (tree[root].children[i] != 0)
        { // 有该孩子
            order++;
            dfs(tree[root].children[i]);
        }
    }
}

数组式完整代码

#include <stdio.h>

typedef struct
{
    int number;      // 编号
    int children[3]; // 孩子
    int child_num;   // 孩子数
    int level;       // 层次数
} node;

int max_child;   // 最大的分叉数
int max_level;   // 有最多分叉树的结点的最深深度
node tree[1005]; // 树
int pos;         // 下一个结点的下标
int order = 0;   // 前序序列的编号

void dfs(int root);

int main()
{
    // 初始化
    pos = 2;
    tree[1].level = 1; // 第一个结点的信息已知
    tree[1].number = 1;
    order = 1;

    int n;
    scanf("%d", &n);
    int parent_number, child_number;
    for (int i = 0; i < n; i++)
    {
        int parent_loc; // 目标双亲结点的位置(下标)
        scanf("%d", &parent_number);
        for (int j = 0; j < pos; j++)
        {
            if (tree[j].number == parent_number) // 找到了双亲结点
            {
                parent_loc = j;
                break;
            }
        }

        int child_num = 0; // 该双亲结点的孩子数
        for (int j = 0; j < 3; j++)
        {
            scanf("%d", &child_number);
            if (child_number != 0)
            {
                child_num++;
                tree[pos].level = tree[parent_loc].level + 1; // 新结点的层次数
                tree[pos].number = child_number;              // 新结点的编号
                tree[parent_loc].children[j] = pos++;         // 双亲结点孩子域更新
            }
        }
        tree[parent_loc].child_num = child_num;
        if (child_num > max_child) // 更新树中最大度
            max_child = child_num;
        if (child_num == max_child && tree[parent_loc].level > max_level) // 更新有最大度的结点的最大深度
            max_level = tree[parent_loc].level;
    }
    dfs(1);

    return 0;
}

void dfs(int root) // 前序遍历
{
    if (tree[root].child_num == max_child && tree[root].level == max_level) // 找到了目标的筑巢点
        printf("%d %d\n", tree[root].number, order);
    for (int i = 0; i < 3; i++)
    {
        if (tree[root].children[i] != 0)
        { // 有该孩子
            order++;
            dfs(tree[root].children[i]);
        }
    }
}

指针式完整代码

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

typedef struct node
{
    int number;               // 编号
    struct node *children[3]; // 孩子
    int child_num;
    int level;
} node;

int max_child, max_level;
int order;

void insert(node **root, int parent_number, int child_number, int which_child);
void dfs(node *root);

int main()
{
    order = 1;
    node *root = (node *)malloc(sizeof(node));
    // 初始化根结点
    root->child_num = 0;
    root->children[0] = root->children[1] = root->children[2] = NULL;
    root->level = 1;
    root->number = 1;

    int n;
    scanf("%d", &n);
    int child_number, parent_number;
    for (int i = 0; i < n; i++)
    {
        scanf("%d", &parent_number);
        for (int j = 0; j < 3; j++)
        {
            scanf("%d", &child_number);
            if (child_number != 0)
            { // 有这个孩子
                insert(&root, parent_number, child_number, j);
            }
        }
    }

    dfs(root);

    return 0;
}

void insert(node **root, int parent_number, int child_number, int which_child)
{
    if ((*root)->number == parent_number)
    { // 找到了目标双亲结点
        node *tmp = (node *)malloc(sizeof(node));
        tmp->child_num = 0;
        tmp->children[0] = tmp->children[1] = tmp->children[2] = NULL;
        tmp->level = (*root)->level + 1;
        tmp->number = child_number;
        (*root)->children[which_child] = tmp;
        (*root)->child_num++;

        if ((*root)->child_num > max_child) // 更新最大孩子数
            max_child = (*root)->child_num;
        if ((*root)->child_num == max_child && (*root)->level > max_level) // 更新有最大孩子数的最深结点数
            max_level = (*root)->level;
        return;
    }
    for (int i = 0; i < 3; i++)
    {
        if ((*root)->children[i] != NULL)
        {
            insert(&((*root)->children[i]), parent_number, child_number, which_child);
        }
    }
}

void dfs(node *root)
{
    if (root->child_num == max_child && root->level == max_level)
    {
        printf("%d %d\n", root->number, order);
    }
    for (int i = 0; i < 3; i++)
    {
        if (root->children[i] != NULL)
        {
            order++;
            dfs(root->children[i]);
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值