JLU数据结构荣誉课——第三次上机实验

Posted by φκι on Monday, September 20, 2021

JLU数据结构荣誉课——第三次上机实验

7-1 二叉树最长路径 (100 分)

给定一棵二叉树T,求T中的最长路径的长度,并输出此路径上各结点的值。若有多条最长路径,输出最右侧的那条。

输入格式:

第1行,1个整数n,表示二叉树有n个结点, 1≤n≤100000.

第2行,2n+1个整数,用空格分隔,表示T的扩展先根序列, -1表示空指针,结点用编号1到n表示。

输出格式:

第1行,1个整数length,length表示T中的最长路径的长度。

第2行,length+1个整数,用空格分隔,表示最右侧的最长路径。

输入样例:

在这里给出一组输入。例如:

5
1 2 -1 -1 3 4 -1 -1 5 -1 -1

输出样例:

在这里给出相应的输出。例如:

2
1 3 5

考察方法: 本题考查树的基本运算,包括: 建立一棵二叉树``遍历树``求树的深度 思路:题中所给按照先根遍历建树,然后按照递归遍历求出高度,这里是可以用递归的,只要先求出每个结点的高度 再存入数组下标为树节点data所对应的值,即可保存在之后输出路径的时候可以直接查找比较。

下面就是代码:

建树:

struct TNode {
    ElementType Data;
    BinTree Left;
    BinTree Right;
};//树节点
BinTree CreatBinTree()
{
    int num;
    BinTree t = NULL;
    scanf("%d", &num);
    if (num == -1)
    {
        return NULL;
    }//递归出口
    t = (BinTree)malloc(sizeof(struct TNode));
    t->Data = num;
    t->Left = CreatBinTree();//创建左子树
    t->Right = CreatBinTree();//创建右子树
    return t;
}

求深度:

int height(BinTree T)
{
    if (T == NULL)
    {
        return -1;
    }
    int hl = height(T->Left);//求取左子树
    int hr = height(T->Right);//求取右子树
    if (hl > hr)
    {
        d[T->Data] = hl + 1;
        return hl + 1;
    }
    else
    {
        d[T->Data] = hr + 1;
        return hr + 1;
    }
}

求路径:

void path(BinTree T)
{
    while (T != NULL)
    {
        int hl=0, hr=0;
        if (T->Left != NULL)
            hl = d[(T->Left)->Data];
        else
            hl = -1;
        if (T->Right != NULL)
            hr = d[(T->Right)->Data];
        else
            hr = -1;
        if (count1 == -1 && hl > hr) { count1 = hl; printf("%d\n", count1 + 1); }
        else if (count1 == -1 && hl <= hr) { count1 = hr; printf("%d\n", count1 + 1); }
        printf("%d", T->Data);
        if (T->Left != NULL || T->Right != NULL)
            printf(" ");
        if (hl > hr) T = T->Left;
        else T = T->Right;
    }
}

原程序:

#include<cstdio>
#include<cstdlib>
using namespace std;
typedef int ElementType;
typedef struct TNode *Position;
typedef Position BinTree;
int d[100001];//存路径长度数组
int count1 = -1;
struct TNode {
    ElementType Data;
    BinTree Left;
    BinTree Right;
};
BinTree CreatBinTree()
{
    int num;
    BinTree t = NULL;
    scanf("%d", &num);
    if (num == -1)
    {
        return NULL;
    }
    t = (BinTree)malloc(sizeof(struct TNode));
    t->Data = num;
    t->Left = CreatBinTree();
    t->Right = CreatBinTree();
    return t;
}
int height(BinTree T)
{
    if (T == NULL)
    {
        return -1;
    }
    int hl = height(T->Left);
    int hr = height(T->Right);
    if (hl > hr)
    {
        d[T->Data] = hl + 1;
        return hl + 1;
    }
    else
    {
        d[T->Data] = hr + 1;
        return hr + 1;
    }

    //return 1+max(height(T->Left),height(T->Right));
}
void path(BinTree T)
{
    while (T != NULL)
    {
        int hl=0, hr=0;
        if (T->Left != NULL)
            hl = d[(T->Left)->Data];
        else
            hl = -1;
        if (T->Right != NULL)
            hr = d[(T->Right)->Data];
        else
            hr = -1;
        if (count1 == -1 && hl > hr) { count1 = hl; printf("%d\n", count1 + 1); }
        else if (count1 == -1 && hl <= hr) { count1 = hr; printf("%d\n", count1 + 1); }
        printf("%d", T->Data);
        if (T->Left != NULL || T->Right != NULL)
            printf(" ");
        if (hl > hr) T = T->Left;
        else T = T->Right;
    }
}
int main()
{
    int num;
    scanf("%d", &num);
    BinTree T = CreatBinTree();
    height(T);
    path(T);
    printf("\n");
    return 0;
}

反思:上机考试的时候被卡空间了,但是没想那么多,但其实节省空间的方法有很多。

7-2 森林的层次遍历 (100 分)

给定一个森林F,求F的层次遍历序列。森林由其先根序列及序列中每个结点的度给出。

输入格式:

第1行,1个整数n,表示森林的结点个数, 1≤n≤100000.

第2行,n个字符,用空格分隔,表示森林F的先根序列。字符为大小写字母及数字。

第3行,n个整数,用空格分隔,表示森林F的先根序列中每个结点对应的度。

输出格式:

1行,n个字符,用空格分隔,表示森林F的层次遍历序列。

输入样例:

在这里给出一组输入。例如:

14
A B C D E F G H I J K L M N
4 0 3 0 0 0 0 2 2 0 0 0 1 0

输出样例:

在这里给出相应的输出。例如:

A M B C G H N D E F I L J K

解法一:

思路:本题使用原始方法,包括: 分为森林的建立``森林的层次遍历 解法:运用递归来建立树(树用三个数组来模拟,未建立结构体),其中引入虚根来使整个森林变成一棵树,然后通过队列结构进行遍历输出, 在虚根处引入特判。

建树:

void Createforest()//是原森林中的每一棵树的创建
{
    int j = num2;
    for (int i = 1; i <= data1[j]; i++)
    {
        child[j].push_back(++num2);
        if (data1[num2] != 0)
        {
            Createforest();
        }
    }
}
inline void Createforest1()//调用Createforest,将每次生成的树连到虚根(这里记为全局数组统一标号为[0]的结点)
{
    while (num2 <= num1)
    {
        child[0].push_back(num2);
        Createforest();
        num2++;
    }
}

遍历树:

inline void printforest()
{
    while (!Q.empty())
    {
        int n = Q.front();
        Q.pop();
        int size = child[n].size();
        for (int i = 0; i < size; i++)//该结点的子结点全部入队
        {
            Q.push(child[n][i]);//这里把字符数组标号压入
        }//进队操作
        if (n != 0)//特判:不能把虚根的数据输出来
        {
            printf("%c", ch[n]);
            if (!Q.empty())
                printf(" ");
        }
    }
}

代码:

#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
int data1[100002];//存放每个字符对应的子结点数
char ch[100002];//存放每个字符
vector<int>child[100002];//存放每个结点的子结点标号,child[x].size()为子结点数
int num2 = 1;
int  num1;
queue<int>Q;
inline void printforest()
{
    while (!Q.empty())
    {
        int n = Q.front();
        Q.pop();
        int size = child[n].size();
        for (int i = 0; i < size; i++)
        {
            Q.push(child[n][i]);
        }
        if (n != 0)
        {
            printf("%c", ch[n]);
            if (!Q.empty())
                printf(" ");
        }
    }
}
void Createforest()
{
    int j = num2;
    for (int i = 1; i <= data1[j]; i++)
    {
        child[j].push_back(++num2);
        if (data1[num2] != 0)
        {
            Createforest();
        }
    }
}
inline void Createforest1()
{
    while (num2 <= num1)
    {
        child[0].push_back(num2);
        Createforest();
        num2++;
    }
}
int main()
{
    scanf("%d\n", &num1);
    for (int i = 1; i <= num1; i++)
    {
        scanf("%c", &ch[i]);
        getchar();
    }
    int i = 1;
    for (; i <= num1; i++)
    {
        scanf("%d", &data1[i]);
    }
    Createforest1();
    Q.push(0);
    printforest();
    //printf("\n");
    return 0;
}

反思:效率还行,空间复杂度可以达到O(n),但是要是直接开成直观的结点结构体,会显示内存超限,因此不是最优解。

方法二:

思路:通过栈来存储,在压栈的过程中,可以通过栈的深度来确定树的层数(深度),然后将每个结点压入所在层的容器中 然后在层次遍历中及直接遍历各层的容器就可以得到层次遍历效果。

比较:与方法一比较,时间复杂度未变,空间复杂度降为O(1)值得使用。

代码如下:

#include<vector>
#include<stack>
#include<list>
#include<algorithm>
#include<map>
#include<iostream>
using namespace std;
map <int,list<char> >p;
char m[100005];int a[100005];
int top=0;
int main() {
    int n, i, j, k, l;
    scanf("%d", &n);
    char c;
    for (i = 0;i < n;++i) {
        scanf("%c", &c);
        while (1) {
            if((c >= '0' && c<='9') || (c >= 'a' && c<='z') || (c >= 'A' && c<='Z'))
                break;     //读取字符的时候可能有换行或者空格
            scanf("%c", &c);
        }
        m[i] = c;
    }
    for (i = 0;i < n;++i) {
        scanf("%d", &a[i]);
    }
    stack<int>q;//存储其下标
    top = 0;
    while (top < n) {//使每一个元素都入栈一次
        q.push(top);
        if (!q.empty()) {//由于是多棵树所以我们要判栈空
            i = q.top();//取出第一个元素
            while (!a[i]) {//度为0则出栈然后使其父结点度-1如果仍为0则继续出栈直到不为0或栈空。
                q.pop();
                j = q.size();
                p[j].push_back(m[i]);//在第j层加入对应的元素
                if (!q.empty()) {
                    i = q.top();    a[i]--;
                }
                else//栈空就跳出循环
                    break;
            }
        }
        top++;//加入新元素
    }
    printf("%c", p[0].front());//按照规定输出
    p[0].pop_front();
    j = 0;
    for (i = 1;i < n;++i) {
        if (p[j].empty())
            j++;
        printf(" %c", p[j].front());p[j].pop_front();
    }
}

7-3 纸带切割 (100 分)

有一条细长的纸带,长度为 L 个单位,宽度为一个单位。现在要将纸带切割成 n 段。每次切割把当前纸带分成两段,切割位置都在整数单位上,切割代价是当前切割纸带的总长度。每次切割都选择未达最终要求的最长纸带切割,若这样的纸带有多条,则任选一条切割。如何切割,才能完成任务,并且总代价最小。

输入格式:

第1行,1个整数n,表示切割成的段数,1≤n≤100000.

第2行,n个整数Li,用空格分隔,表示要切割成的各段的长度,1≤Li≤2000000001≤i≤n.

输出格式:

第1行,1个整数,表示最小的总代价。

第2行,若干个整数,用空格分隔,表示总代价最小时每次切割的代价。

输入样例:

在这里给出一组输入。例如:

5
5 6 7 2 4

输出样例:

在这里给出相应的输出。例如:

54
24 13 11 6

思路:通过题目得知为哈夫曼树。所以直接使用哈夫曼树进行处理,题目隐性要求就是输出每次创建的 结点。 方法:采用堆优化,使用优先队列。 代码如下:

#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
int num = 0;
vector<long long>ans;
typedef long long ll;
priority_queue<ll, vector<ll> ,greater<ll> >Q;
void CreateTree()
{
    int k = 0;
    ll sum = 0;
    while (Q.size()!=1)
    {
        int num1 = Q.top();
        Q.pop();
        int num2 = Q.top();
        Q.pop();
        sum += (num1 + num2);
        Q.push(num1 + num2);
        ans.push_back(num1+num2);
    }//创立结点后,将结点送入ans中,便于下面的遍历输出
    printf("%lld\n", sum);
    for (auto i = ans.rbegin(); i != ans.rend(); i++)
    {
        printf("%lld", *i);
        printf("%c", i == ans.rend() - 1 ? '\n' : ' ');
    }
}
int main()
{
    scanf("%d", &num);
    int size1;
    for (int i = 0; i < num; i++)
    {
        scanf("%d", &size1);
        Q.push(size1);
    }
        CreateTree();
    return 0;
}

反思:如果用的数组模拟,注意1的特判,输出应该统一为”0\n”

7-4 序列乘积 (100 分)

两个递增序列A和B,长度都是n。令 Ai 和 Bj 做乘积,1≤i,j≤n.请输出n*n个乘积中从小到大的前n个。

输入格式:

第1行,1个整数n,表示序列的长度, 1≤n≤100000.

第2行,n个整数Ai,用空格分隔,表示序列A,1≤Ai≤40000,1≤i≤n.

第3行,n个整数Bi,用空格分隔,表示序列B,1≤Bi≤40000,1≤i≤n.

输出格式:

1行,n个整数,用空格分隔,表示序列乘积中的从小到大前n个。

输入样例:

在这里给出一组输入。例如:

5
1 3 5 7 9 
2 4 6 8 10

输出样例:

在这里给出相应的输出。例如:

2 4 6 6 8
作者` `谷方明` `单位` `吉林大学` `代码长度限制` `16 KB` `时间限制` `100 ms` `内存限制` `5 MB

思路:n*n的数据经过排列形成特殊矩阵,该矩阵的特点是:最左列对于每一行来说最小 所以先将最左列压入优先队列中,然后弹出第一小的数输出,取该数在矩阵中的位置,将该数 的坐标加一送到优先队列中,保持优先队列始终有且只有100个元素。 方法:涉及堆优化和对坐标的存储。

代码如下:

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int a[100001];//A序列
int b[100001];//B序列
typedef long long ll;
struct point
{
    int x1, y1;
    ll val;
    bool operator<(const point&r)const
    {
        return r.val < val;
    }
    point(int x, int y, ll v) :x1(x), y1(y), val(v) {};
};
priority_queue<point>Q;
int main()
{
    int num;
    scanf("%d", &num);
    for (int i = 1; i <= num; i++)
        scanf("%d", &a[i]);
    for (int i= 1; i <= num; i++)
    {
        scanf("%d",&b[i]);
        if (i == 1)
        {
            for (int j = 1; j <= num; j++)
                Q.push(point(j, 1, a[j] * b[1]));//先将第一列放入堆中
        }
    }
    for (int i = 1; i <= num; i++)
    {
        point p = Q.top();
        printf("%lld",p.val);
        Q.pop();
        if (p.y1 < num)Q.push(point(p.x1, p.y1 + 1, a[p.x1] * b[p.y1 + 1]));//行元素右移操作
        printf("%c", i == num ? '\n' :' ');
    }
    return 0;
}

反思:大佬给的经验,用结构体来当结点,定位方便。注意移位的时候,可能超出范围列的界限。