BUAA数据结构第三次作业2023

BUAA数据结构第三次作业2023

1. 连续线段

问题

问题描述

平面上两个点(一个点由(x,y)坐标组成)可构成一个线段,两个线段如果有一个端点相同,则可构成一个连续线段。假设构成线段的两个端点为 v 1 ( x 1 , y 1 ) v_1(x_1,y_1) v1(x1,y1) v 2 ( x 2 , y 2 ) v_2(x_2,y_2) v2(x2,y2),在此满足 x 1 < x 2 x_1<x_2 x1<x2,其中 v 1 v_1 v1称为线段的起点, v 2 v_2 v2为线段的终点。同样,对于连续线段来说,在此满足 x i < x i + 1 x_i<x_i+1 xi<xi+1(i=1…n-1,n为连续线段中的端点数, x i x_i xi为相应端点的X轴坐标)。输入一组线段(用两个端点的x、y坐标表示线段,线段个数大于等于2,小于等于100),编程计算出连续线段中包含最多线段数的线段,输出相应的线段数和起点位置(注意,不是最长的连续线段,是包含最多线段的连续线段)。例如:

在这里插入图片描述

上图中有10个线段,其中5、10、4号线段连成了一条连续线段,线段数3条,起点位置为5号线段的左端点;6、8、2、3、9号线段连成了另一条连续线段,该连续线段包含的线段数最多,为5条,起点位置是6号线段的左端点。
注意
1)不考虑线段中间相交的情况;
2)不会有三条或三条以上的线段共一个端点;
3)只会出现一条包含最多线段的连续线段;

输入形式

先从控制台输入线段数,然后从下一行开始分行输入各线段两个端点的x、y坐标,其中第一个端点的X轴坐标小于第二个端点的X轴坐标,即 x 1 < x 2 x_1<x_2 x1<x2,x、y坐标都用整数表示,不会超过int的表示范围。各整数坐标值间以一个空格分隔。

输出形式

先在控制台输出包含最多线段数的连续线段的线段数,然后输出连续线段的起点的x、y坐标,输出数据都以一个空格分隔

样例输入

10

80 75 125 75

60 40 80 55

80 55 90 20

140 120 195 205

10 111 70 165

22 35 43 43

22 175 80 205

43 43 60 40

90 20 125 60

70 165 140 120

样例输出

5 22 35

样例说明

输入了十个线段,第一个线段两个端点分别为(80,75)和(125,75),其它线段类似,如上图所示,这些线段所构成的连续线段中包含最多线段数的连续线段的线段数为5,起点为(22,35),所以输出:5 22 35。

评分标准

通过所有测试点将得满分。提交程序名为line.c。

问题分析

本题可以定义一个线段结构,存储起点与终点的横纵坐标。而后对线段以起点横坐标升序排序,而后从头遍历线段,对于每个线段,若它后面某个线段的起点与该线段的终点相同,则可以连接。想到这,很自然地能想到在定义线段结构时可以加一个域记录“到该线段为止能连接的最长的线段”。但如果这样做,我们能够很轻松地拿到所连接的线段的“终点”,而不好拿到起点,所以我们可以按照尾结点横坐标升序排列,在遍历时从尾到头。

具体处理过程

首先定义结构,并创建数组,初始化

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

typedef struct line{
    int start_x;
    int start_y;
    int end_x;
    int end_y;
    int counter;//记录以此线段为起始线段最多能向后连多少个
} line;

int main()
{
    int n;//线段数
    line *gather = malloc(sizeof(line)*n);
    
    for (int i=0;i<n;i++){
        scanf("%d%d%d%d",&gather[i].start_x,&gather[i].start_y,&gather[i].end_x,&gather[i].end_y);
        gather[i].counter = 1;//初始化
    }
    

    return 0;
}

接下来要按照尾结点的横坐标升序排列,选择用qsort函数,需定义cmp函数:

int cmp(const void*e1,const void*e2)
{
    line *data1 = (line*)e1;
    line *data2 = (line*)e2;
    return data1->end_x-data2->end_x;
}

接下来从末尾开始遍历就好:

qsort(gather,n,sizeof(line),cmp);
for(int i=n-1;i>0;i--){//此时第“0”个不需要遍历
    for(int j=i-1;gather[j].end_x>=gather[i].start_x;j--){
        //能连在一起的必要条件是前一个线段终点的横坐标大于等于后一个的
        if(gather[i].start_x==gather[j].end_x&&gather[i].start_y==gather[j].end_y){
            gather[j].counter += gather[i].counter;
            break;
        }
    }
}

而后再对处理后的结果遍历一遍,找出counter最大的那个线段:

int max_num = 0;
line target;
for(int i=0;i<n;i++){
    if(max_num<gather[i].counter){
        max_num = gather[i].counter;
        target = gather[i];
    }
}
printf("%d %d %d",max_num,target.start_x,target.start_y);
return 0;

完整代码

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

int cmp(const void*e1,const void*e2);

typedef struct line{
    int start_x;
    int start_y;
    int end_x;
    int end_y;
    int counter;//记录以此线段为起始线段最多能向后连多少个
} line;

int main()
{
    int n;//线段数
    scanf("%d",&n);
    line *gather = malloc(sizeof(line)*n);
    
    for (int i=0;i<n;i++){
        scanf("%d%d%d%d",&gather[i].start_x,&gather[i].start_y,&gather[i].end_x,&gather[i].end_y);
        gather[i].counter = 1;//初始化
    }
    
    qsort(gather,n,sizeof(line),cmp);
    for(int i=n-1;i>0;i--){//此时第“0”个不需要遍历
        for(int j=i-1;gather[j].end_x>=gather[i].start_x;j--){
            //能连在一起的必要条件是前一个线段终点的横坐标大于等于后一个的
            if(gather[i].start_x==gather[j].end_x&&gather[i].start_y==gather[j].end_y){
                gather[j].counter += gather[i].counter;
                break;
            }
        }
    }

    int max_num = 0;
    line target;
    for(int i=0;i<n;i++){
        if(max_num<gather[i].counter){
            max_num = gather[i].counter;
            target = gather[i];
        }
    }
    printf("%d %d %d",max_num,target.start_x,target.start_y);
    return 0;
}
int cmp(const void*e1,const void*e2)
{
    line *data1 = (line*)e1;
    line *data2 = (line*)e2;
    return data1->end_x-data2->end_x;
}

2. 空闲空间申请模拟(最佳适应)

问题

问题描述

在操作系统中,空闲存储空间通常以空闲块链表方式组织,每个块包含块起始位置、块长度及一个指向下一块的指针。空闲块按照存储位置升序组织,最后一块指向第一块(构成循环链表)。当有空间申请请求时,按照如下原则在空闲块循环链表中寻找并分配空闲块:

1)从当前位置开始遍历空闲块链表(初始是从地址最小的第一个空闲块开始),寻找满足条件的最小块(:大于等于请求空间的最小空闲块,如果有多个大小相同的最小空闲块,则选择遍历遇到的第一个空闲块)(最佳适应原则);

2)如果选择的空闲块恰好与请求的大小相符合,则将它从链表中移除并返回给用户;这时当前位置变为移除的空闲块指向的下一空闲块

3)如果选择的空闲块大于所申请的空间大小,则将大小合适的空闲块返回给用户,剩下的部分留在空闲块链表中;这时当前位置仍然为该空闲块;

4)如果找不到足够大的空闲块,则申请失败;这时当前位置不变

例如:下图示例给出了空闲块链表的初始状态,每个结点表示一个空闲块,结点中上面的数字指空闲块的起始位置,下面的数字指空闲块的长度,位置和长度都用正整数表示,大小不超过int表示范围。当前位置为最小地址为1024的空闲块。

在这里插入图片描述

若有4个申请空间请求,申请的空间大小依次为:1024、2560、10240和512。则从当前位置开始遍历上图的链表,按照上述原则寻找到满足条件的最小块为地址是16384的空闲块,其长度正好为1024,所以将其从链表中删除,这时链表状态如下图所示,当前位置变成地址为32768的空闲块。

在这里插入图片描述

从当前位置开始为第二个空间请求(大小为2560)遍历链表,按照上述原则寻找到满足条件的最小块为地址是80896的空闲块,其长度为3072,大于请求的空间大小,于是申请空间后该空闲块剩余的长度为512,当前位置为地址是80896的空闲块,链表状态如下图所示:

在这里插入图片描述

从当前位置开始为第三个空间请求(大小为10240)遍历链表,遍历一圈后发现找不到足够大的空闲块,则忽略该请求,当前位置不变。下面继续为第四个空间请求(大小为512)遍历链表,按照上述原则寻找到满足条件的最小块为当前位置的空闲块,其长度等于请求的空间大小,于是将该空闲块删除后,链表状态变为下图所示:

在这里插入图片描述

编写程序,模拟上述空闲空间申请。

输入形式

先从控制台读入一正整数,表示当前空闲块的个数(大于0且小于等于100)。

然后按照起始位置由小到大的顺序分行输入每个空闲块的起始位置和长度,位置和长度都用正整数表示,大小不超过int表示范围,两整数间以一个空格分隔。

最后在新的一行上依次输入申请空间的大小,以-1表示结束,各整数间以一个空格分隔,申请请求的个数不超过100个。

输出形式

按照上述原则模拟完空闲空间申请后,输出当前空闲空间链表状态,即从当前位置开始,遍历链表,分行输出剩余空闲块的起始位置和长度,位置和长度间以一个空格分隔。若申请完后,链表中没有空闲块,则什么都不输出。

样例输入

12

1024 2048

8192 512

16384 1024

32768 8192

65536 8192

77824 1024

80896 3072

86016 1024

91136 5120

99328 512

104448 1024

112640 3072

1024 2560 10240 512 1024 6400 512 -1

样例输出

104448 1024

112640 3072

1024 2048

8192 512

32768 1792

65536 8192

77824 1024

91136 5120

样例说明

样例输入了12个空闲块的信息,形成了如上述第一个图所示的空闲块链表;然后读取了7个空间申请请求,为前4个申请请求分配空间后,空闲块链表状态为上述最后一张图所示。满足第五个请求后,将删除地址为86016的空闲块;满足第六个请求后,地址为32768的空闲块剩余长度为1792;满足第七个请求后,将删除地址为99328的空闲块,这时链表中剩余8个空闲块,当前位置为地址是104448的空闲块,从该空闲块开始依次遍历输出所有剩余空闲块的起始位置和长度。

评分标准

该题要求编程模拟实现空闲空间的申请,提交程序名为memory.c。

问题分析

大模拟,按照题目要求一步一步实现就好,需注意的是,由于涉及到删除操作,所以更适合使用双向链表。

具体处理过程

首先定义空闲块结构:

#include <stdio.h>

typedef struct node{
    int addreas;
    int size;
    struct node* next;
} node;
typedef node* list;

int main()
{

    return 0;
}

然后在主函数中创建一个空链表(不带哑结点),然后读入数据:

    int n;
    scanf("%d",&n);
    list mem_list = malloc(sizeof(node));
    node *p = mem_list;//p指向读到的最后一个结点
    scanf("%d%d",&p->addreas,&p->size);//第一个先读入
    for(int i=1;i<n;i++){
        node *temp = malloc(sizeof(node));
        scanf("%d%d",&temp->addreas,&temp->size);
        temp->last = p;
        p->next = temp;
        p = p->next;
        if(i==n-1){//最后一个数据,把链表连接上
            p->next = mem_list;
            mem_list->last = p;
        }
    }

而后针对查找操作,写对应的函数:

node* find(node*begin,int target_size)
//begin为起始位置,target_size为要寻找的目标地址
{
    int flag = 0;
    node *p = begin;//该指针将遍历一圈
    node *target_node = NULL;//目标位置
    int min_size = 2147483647;//记录满足的最小内存大小
    while(p!=begin||flag==0){
        flag = 1;
        if(p->size==target_size){
            p->last->next = p->next;
            p->next->last = p->last;
            node* tmp = p;
            p = p->next;
            free(tmp);
            return p;
        }//内存刚好相等,删除该位置
        if(p->size < target_size){           
            p = p->next;
        }else {
            if(p->size < min_size){
                min_size = p->size;
                target_node = p;                
            }
            p = p->next;
        }      
    }
    if(min_size == 2147483647)return begin;
    else {
        target_node->size -= target_size;
        return target_node;
    }
}

再在主函数中循环调用,最后输出即可:

    int size;
    node*begin = mem_list;
    while(1){
        scanf("%d",&size);
        if(-1==size)break;
        begin = find(begin,size);
    }

    //输出剩余空块
    p = begin;
    int flag = 0;
    while(p!=begin||flag==0){
        flag = 1;
        printf("%d %d\n",p->addreas,p->size);
        p = p->next;
    }

但这样会出问题,如果此时只剩下一个结点,那么如果删除这个结点,双向链表中,当前结点的next和last域均指向其自己,赋值时会出bug;输出时若只有一个结点,也会卡循环。因此,我们有必要在find函数和最后输出时对这种情况特判,最终完整版代码如下:

完整代码

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

typedef struct node{
    int addreas;
    int size;
    struct node*next;
    struct node*last;
} node;
typedef node* list;

node* find(node*begin,int target_size);;

int n;

int main()
{
    scanf("%d",&n);
    list mem_list = malloc(sizeof(node));
    node *p = mem_list;//p指向读到的最后一个结点
    scanf("%d%d",&p->addreas,&p->size);//第一个先读入
    for(int i=1;i<n;i++){
        node *temp = malloc(sizeof(node));
        scanf("%d%d",&temp->addreas,&temp->size);
        temp->last = p;
        p->next = temp;
        p = p->next;
        if(i==n-1){//最后一个数据,把链表连接上
            p->next = mem_list;
            mem_list->last = p;
        }
    }
    
    int size;
    node*begin = mem_list;
    while(1){
        scanf("%d",&size);
        if(-1==size)break;
        begin = find(begin,size);
    }

    //输出剩余空块   
    if(n){
        if(1==n)printf("%d %d\n",begin->addreas,begin->size);
        else{
            p = begin;
            int flag = 0;
            while(p!=begin||flag==0){
                flag = 1;
                printf("%d %d\n",p->addreas,p->size);
                p = p->next;
            }
        }
    }
    return 0;
}


node* find(node*begin,int target_size)
//begin为起始位置,target_size为要寻找的目标地址
{
    if(1==n||0==n){
        if(begin->size==target_size){
            n=0;
            return NULL;    
        }else if(begin->size>target_size){
            begin->size -= target_size;
            return begin;
        }
    }
    int flag = 0;
    node *p = begin;//该指针将遍历一圈
    node *target_node = NULL;//目标位置
    int min_size = 2147483647;//记录满足的最小内存大小
    while(p!=begin||flag==0){       
        flag = 1;
        if(p->size==target_size){
            p->last->next = p->next;
            p->next->last = p->last;
            node* tmp = p;
            p = p->next;
            free(tmp);
            n--;
            return p;
        }//内存刚好相等,删除该位置
        if(p->size < target_size){           
            p = p->next;
        }else {
            if(p->size < min_size){               
                min_size = p->size;
                target_node = p;                
            }
            p = p->next;
        }      
    }
    if(min_size == 2147483647)return begin;
    else {
        target_node->size -= target_size;
        return target_node;
    }
}

3. 多项式相乘

问题

问题描述(建议用链表实现)

编写一个程序实现两个一元多项式相乘。

输入形式

首先输入第一个多项式中系数不为0的项的系数和指数,以一个空格分隔。且该多项式中各项的指数均为0或正整数,系数和最高幂次不会超过int类型的表示范围。对于多项式
a n x n + a n − 1 x n − 1 + . . . + a 1 x 1 + x 0 a_nx^n+a_{n-1}x^{n-1}+...+a_1x^1+x_0 anxn+an1xn1+...+a1x1+x0
的输入方法如下: a n a_n an n a n − 1 a_{n-1} an1 n-1 … a 1 a_1 a1 1 a 0 a_0 a0 0

即相邻两个整数分别表示表达式中一项的系数和指数。在输入中只出现系数不为0的项。最后一项的指数后没有空格,只有一个回车换行符。
按照上述方式再输入第二个多项式。

输出形式

将运算结果输出到屏幕。将系数不为0的项按指数从高到低的顺序输出,每次输出其系数和指数,均以一个空格分隔,最后一项的指数后也可以有一个空格。

样例输入

10 80000 2 6000 7 300 5 10 18 0
3 6000 5 20 8 10 6 0

样例输出

30 86000 50 80020 80 80010 60 80000 6 12000 21 6300 10 6020 31 6010 66 6000 35 320 56 310 42 300 25 30 130 20 174 10 108 0

样例说明

输入的两行分别代表如下表达式:
10 x 80000 + 2 x 6000 + 7 x 300 + 5 x 10 + 183 x 6000 + 5 x 20 + 8 x 10 + 6 10x^{80000}+2x^{6000}+7x^{300}+5x^{10}+18 3x^{6000}+5x^{20}+8x^{10}+6 10x80000+2x6000+7x300+5x10+183x6000+5x20+8x10+6
相乘结果为:
30 x 86000 + 50 x 80020 + 80 x 80010 + 6 x 12000 + 21 x 6300 + 10 x 6020 + 31 x 6010 + 35 x 320 + 56 x 310 + 42 x 300 + 25 x 30 + 130 x 20 + 174 x 10 + 108 30x^{86000}+50x^{80020}+80x^{80010}+6x^{12000}+21x^{6300}+10x^{6020}+31x^{6010}+35x^{320}+56x^{310}+42x^{300}+25x^{30}+130x^{20}+174x^{10}+108 30x86000+50x80020+80x80010+6x12000+21x6300+10x6020+31x6010+35x320+56x310+42x300+25x30+130x20+174x10+108
提示:利用链表存储多项式的系数和指数。

评分标准

该题要求输出相乘后多项式中系数不为0的系数和指数,共有5个测试点。上传C语言文件名为multi.c。

问题分析

多项式结构是用链表实现的经典结构,每一个结点包含其中一项的系数和指数,以及一个指针域,指向下一个结点,按照题目要求,我们按照指数递减的顺序存储各结点。利用链表,我们可以很轻松地完成两个多项式相加的操作。而对于乘法,我们可以把它拆分为两个步骤:

  1. 写一个函数实现一个项去乘一个多项式。
  2. 把其中一个多项式当做多项式,另一个多项式看成多个项相加,分别把这些项和第一个多项式相乘,再把得到的多个多项式相加。

具体实现过程

首先初始化,并定义多项式的链表。

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

typedef struct node{
    int coefficient;
    int index;
    struct node* next;
} node;
typedef node* polynome;

int main()
{


    return 0;
}

而后要写一个函数,实现创建一个多项式。

polynome creat_polynome()
{
    node* list = malloc(sizeof(node));
    list->next = NULL;
    return list;
}

接下里在主函数中,就可以读入相应的数据了,注意我们要处理何时把第一个多项式读完了(利用题目中说的最后一项的指数后没有空格,只有一个回车换行符。

    polynome polynome1 = creat_polynome();
    polynome polynome2 = creat_polynome();
    node*p1 = polynome1,*p2 = polynome2;
    //p1,p2指向链表的最后一个元素,方便读入
    int temp_index , temp_coefficient;
    while(1){ 
        scanf("%d%d",&temp_coefficient,&temp_index);      
        p1->next = malloc(sizeof(node));
        p1 = p1->next;
        p1->coefficient = temp_coefficient;
        p1->index = temp_index;
        p1->next = NULL;
        if(getchar()=='\n')break;
    }
    while(scanf("%d %d",&temp_coefficient,&temp_index)!=EOF){       
        p2->next = malloc(sizeof(node));
        p2 = p2->next;
        p2->coefficient = temp_coefficient;
        p2->index = temp_index;
        p2->next = NULL;
        if(getchar()=='\n')break;
    }

接下来我们可以写一个函数去打印这个多项式——一来最后要输出,二来我们可以检验此时写的代码有没有问题。

void print_polynome(polynome p)
{
    p = p->next;//第一个是哑结点,不输出
    while(p != NULL){
        printf("%d %d ",p->coefficient,p->index);
        p = p->next;
    }
}

然后我们要写一个实现实现两个多项式相加的函数:

polynome polynome_addition(polynome p1, polynome p2)
//计算两多项式之和,并返回
{
    node*p = p1;
    p1 = p1->next;
    p2 = p2->next;
    while(p1!=NULL&&p2!=NULL){
        if(p1->index>p2->index){
            p->next = p1;
            p1 = p1->next;
        }else if(p1->index<p2->index){
            p->next = p2;
            p2 = p2->next;
        }else {
            p1->coefficient+=p2->coefficient;
            p->next = p1;
            p1 = p1->next;
            p2 = p2->next;
        }
        p = p->next;        
    }
    if(p1==NULL)p->next = p2;
    else if(p2 == NULL)p->next = p1;
    else p->next = NULL;
    return result;   
}

接下来要实现一个多项式和另一个多项式中的某一项相乘,实现如下:

//计算一个多项式和一个项之积
polynome poly_muti(polynome p1, node*p2)
//p1为多项式,p2为一项
{
    polynome result = creat_polynome();
    node*final = result;
    node*p = p1->next;
    while(p1!=NULL){
        final->next = malloc(sizeof(node));
        final = final->next;
        final->coefficient = p1->coefficient *p2->coefficient;
        final->index = p1->index + p2->index;
        final->next = NULL;
    }   
    return result;
}

接下来只需要在主函数中将其中一个多项式的每一项都与另一个多项式相乘,得到多个多项式,然后在相加即可。

polynome ans = creat_polynome();
node*p = polynome2->next;
while(p!=NULL){
     polynome_addition(ans,poly_muti(polynome1,p));
     p = p->next;
}
print_polynome(ans);

完整代码

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

typedef struct node{
    int coefficient;
    int index;
    struct node* next;

} node;
typedef node* polynome;

polynome creat_polynome();
void print_polynome(polynome p);
void polynome_addition(polynome p1, polynome p2);
polynome poly_muti(polynome p1, node*p2);


int main()
{
    polynome polynome1 = creat_polynome();
    polynome polynome2 = creat_polynome();
    node*p1 = polynome1,*p2 = polynome2;
    //p1,p2指向链表的最后一个元素,方便读入
    int temp_index , temp_coefficient;
    while(1){ 
        scanf("%d%d",&temp_coefficient,&temp_index);      
        p1->next = malloc(sizeof(node));
        p1 = p1->next;
        p1->coefficient = temp_coefficient;
        p1->index = temp_index;
        p1->next = NULL;
        if(getchar()=='\n')break;
    }
    
    
    while(scanf("%d %d",&temp_coefficient,&temp_index)!=EOF){       
        p2->next = malloc(sizeof(node));
        p2 = p2->next;
        p2->coefficient = temp_coefficient;
        p2->index = temp_index;
        p2->next = NULL;
        if(getchar()=='\n')break;
    }
    
    polynome ans = creat_polynome();
    node*p = polynome2->next;
    while(p!=NULL){
        polynome_addition(ans,poly_muti(polynome1,p));
        p = p->next;
    }
    print_polynome(ans);

    return 0;
}


polynome creat_polynome()
{
    node* list = malloc(sizeof(node));
    list->next = NULL;
    return list;
}


void print_polynome(polynome p)
{
    p = p->next;//第一个是哑结点,不输出
    while(p != NULL){
        printf("%d %d ",p->coefficient,p->index);
        p = p->next;
    }
}


void polynome_addition(polynome p1, polynome p2)
//计算两多项式之和,以p1返回
{
    node*p = p1;
    p1 = p1->next;
    p2 = p2->next;
    while(p1!=NULL&&p2!=NULL){
        if(p1->index>p2->index){
            p->next = p1;
            p1 = p1->next;
        }else if(p1->index<p2->index){
            p->next = p2;
            p2 = p2->next;
        }else {
            p1->coefficient+=p2->coefficient;
            p->next = p1;
            p1 = p1->next;
            p2 = p2->next;
        }
        p = p->next;        
    }
    if(p1==NULL)p->next = p2;
    else if(p2 == NULL)p->next = p1;
    else p->next = NULL;  
}


//计算一个多项式和一个项之积
polynome poly_muti(polynome p1, node*p2)
//p1为多项式,p2为一项
{
    polynome result = creat_polynome();
    node*final = result;
    node*p = p1->next;
    while(p!=NULL){
        final->next = malloc(sizeof(node));
        final = final->next;
        final->coefficient = p->coefficient * p2->coefficient;
        final->index = p->index + p2->index;
        p = p->next;
        final->next = NULL;      
    }   
    return result;
}

4. 文件加密(环)

问题

问题描述(建议用链表实现

有一种文本文件加密方法,其方法如下:

1、密钥由所有ASCII码可见字符(ASCII码编码值32-126为可见字符)组成,密钥长度不超过32个字符

2、先将密钥中的重复字符去掉,即:只保留最先出现的字符,其后出现的相同字符都去掉;

3、将不含重复字符的密钥和**其它不在密钥中的可见字符(按字符升序)**连成一个由可见字符组成的环,密钥在前,密钥的头字符为环的起始位置

4、设原密钥的第一个字符(即环的起始位置)作为环的开始位置标识,先从环中删除第一个字符(位置标识则移至下一个字符),再沿着环从下一个字符开始顺时针第一个字符的ASCII码值移动该位置标识至某个字符,则该字符成为第一个字符的密文字符;然后从环中删除该字符,再从下一个字符开始顺时针以该字符的ASCII码值移动位置标识至某个字符,找到该字符的密文字符;依次按照同样方法找到其它字符的密文字符。当环中只剩一个字符时,则该剩下的最后一个字符的密文为原密钥的第一个字符

下面以可见字符集只由小写字母组成为例来说明对应密文字符集生成过程。如果密钥为:helloworld,将密钥中重复字符去掉后为:helowrd,将不在密钥中的小写字母按照升序添加在密钥后,即形成字符串:helowrdabcfgijkmnpqstuvxyz,该字符串形成的环如下图所示:

在这里插入图片描述

明码的第一个字母为h,h也是环的起始位置。h的ASCII码制为104,先把h从环中删除,再从下一个字母e开始顺时针沿着环按其ASCII值移动位置标识(即:在字母e为移动第1次,共移动位置标识104次)至字母w,则h的密文字符为w。w的ASCII码制为119,然后将w从环中删除,再从下一个字母r开始顺时针沿着环移动位置标识119次至字母为l,则w的密文字符为l。依次按照同样方法找到其它字母的密文字符。环中剩下的最后一个字母为x,则x的密文字符为明码的第一个字母h。按照这种方法形成的密文转换字符表为:

在这里插入图片描述

上方为原文字符,下方为对应的密文字符。由所有ASCII可见字符集组成的字符集密文字符生成方式与上例相同。

编写程序实现上述文件加密方法。密钥从标准输入读取,待加密文件为当前目录下的in.txt文件,该文件中的字符若是可见字符,则按照上述方法进行加密,否则原样输出(例如:回车换行符),加密后生成的密文文件为当前目录下的in_crpyt.txt

输入形式

密钥是从标准输入读取的一行字符串,可以包含任意ASCII码可见字符(ASCII码编码值32-126为可见字符),长度不超过32个字符。

输出形式

加密后生成的密文文件为当前目录下的in_crpyt.txt。

样例输入

C Programming(Second Edition)

假设in.txt文件内容为:

This book is meant to help the reader learn how to program in C. It is the definitive reference guide, now in a second edition. Although the first edition was written in 1978, it continues to be a worldwide best-seller. This second edition brings the classic original up to date to include the ANSI standard.

From the Preface:

样例输出

in_crpyt.txt文件内容为:

KgklW#33>WklWA^M8W83Wg\Z,W8g\WP^u\PWZ^PMWg3jW83W,P30PAWkMWX5W.8WklW8g\Wu\EkMk8kt\WP\E\P\MR\W0-ku+WM3jWkMWWl\R3MuW\uk8k3M5WIZ8g3-0gW8g\WEkPl8W\uk8k3MWjlWjPk88\MWkMW’71G+Wk8WR3M8kM-\lW83W#\WWj3PZujku\W#\l8Jl\ZZ\P5WKgklWl\R3MuW\uk8k3MW#PkM0lW8g\WRZllkRW3Pk0kMZW-,W83Wu8\W83WkMRZ-u\W8g\WIOY.Wl8Mu^Pu5

4P3AW8g\WdP\E^R(

样例说明

输入的密钥为C Programming(Second Edition),由该密钥生成的字符串环中字符依次为:

C Progamin(SecdEt)!"#$%&'*+,-./0123456789:;<=>?@ABDFGHIJKLMNOQRTUVWXYZ[]^_`bfhjklpqsuvwxyz{|}~

形成的字符转换字符表(第一行为原ASCII字符,第二行为对应的密文字符)为:

按照该密文转换字符表将in.txt中的内容转换成加密字符输出到in_crpyt.txt中。

评分标准

该题要求对指定的文件进行加密,提交的文件名为:encode.c。

问题分析

本题仍是大模拟,但是较为复杂,写代码前有必要规划一下我们实现的步骤:

  1. 读取秘钥,可以以字符串的形式存储;
  2. 将秘钥中的字符按照顺序,屏蔽掉相同字符,存储到链表中,并记录已经使用过的字符;
  3. 将剩余还没有使用过的可见字符按顺序加到链表末尾,并把链表首尾相接成环形链表;
  4. 按照题目要求,得到密文转换的字符表;
  5. 对文件中读取要加密的内容,按照密文转换字符表对其加密后输出到指定文件中;

具体实现过程

对前两步,代码实现如下:

    //读取秘钥
    char key[35] = {0};
    gets(key);

    int already_used[130] = {0};//记录已经使用过的字符
    node *first = malloc(sizeof(node));
    node *p = first;//记录当前链表末尾
    p->ch = key[0];
    already_used[(int)key[0]] = 1;
    for(int i=1;key[i];i++){
        if(already_used[(int)key[i]] == 0){//该字符还没有被使用过
            already_used[(int)key[i]] = 1;
            p->next = malloc(sizeof(node));
            p = p->next;
            p->ch = key[i];
        }
    }

第三步实现如下:

    for(int i=32;i<=126;i++){//所有字符都遍历一次
        if(already_used[i]==0){//还没有被用过
            p->next = malloc(sizeof(node));
            p = p->next;
            p->ch = i;            
        }
    }
    //最后将链表首尾连接,成为循环链表
    p->next = first;

接下来我们要实现单向链表的删除操作,则必须写一个函数findprevious来找到某一个结点的上一个结点(若为双向链表就不需要了):

node* findprevious(node*now)
{
    node* p = now;
    while(p->next!=now){
        p = p->next;
    }
    return p;
}

接下来实现第4步:

    char convertion_table[130] = {0};//密文转换表
    p = first;
    while(p->next->next!=p){//特判此时链表中是否只剩下两个结点,作为循环终止条件
        int org = (int)(p->ch);
        findprevious(p)->next = p->next;//将p所指结点的上一个结点与下一个结点相连
        node* tmp = p;
        p = p->next;
        free(tmp);
        for(int i=1;i<org;i++){
            p = p->next;
        }
        convertion_table[org] = p->ch;
    }

    //此时链表中还有两个结点
    int org = (int)(p->ch);
    convertion_table[org] = org%2 ? p->next->ch : p->ch;
    convertion_table[!org%2 ? (int)(p->next->ch) : (int)(p->ch)] = first ->ch;
    free(p ->next);
    free(p);

然后读取文件,逐行对其操作即可:

    FILE* in = fopen("in.txt", "r");
    FILE* out = fopen("in_crypt.txt","w");
    char text[1024];
    while(fgets(text,1024,in)!=NULL){
        for(int i=0;text[i];i++){
            if(text[i]>=32&&text[i]<=126){//是可见字符
                fprintf(out,"%c",convertion_table[(int)(text[i])]);
            }else fprintf(out,"%c",text[i]);
        }
    }

此时提交代码,发现有一个测试点没有过,经过“缜密”的检查,发现:若输入的秘钥为空字符串,则一切都毁了,所以要在最开始时对这种情况特判一下,2,3步的代码改为如下:

    //读取秘钥
    char key[35] = {0};
    gets(key);

    int already_used[130] = {0};//记录已经使用过的字符
    node *first = malloc(sizeof(node));
    node *p = first;//记录当前链表末尾
    if(key[0]=='\0'){//读入的是空字符串
        p->ch = 32;
        already_used[32] = 1;
    }else {
        p->ch = key[0];
        already_used[(int)key[0]] = 1;
        for(int i=1;key[i];i++){
            if(already_used[(int)key[i]] == 0){//该字符还没有被使用过
                already_used[(int)key[i]] = 1;
                p->next = malloc(sizeof(node));
                p = p->next;
                p->ch = key[i];
            }
        }
    }

    for(int i=32;i<=126;i++){//所有字符都遍历一次
        if(already_used[i]==0){//还没有被用过
            p->next = malloc(sizeof(node));
            p = p->next;
            p->ch = i;            
        }
    }
    //最后将链表首尾连接,成为循环链表
    p->next = first;

然而,发现还是没过。。。

于是,经过另一番“缜密”的分析,发现:按照我们最后用fgets读入再输出,若待加密的文件中有’\0’,则又毁了,所以换成fgetc,改成如下:

    FILE* in = fopen("in.txt", "r");
    FILE* out = fopen("in_crpyt.txt","w");
    int c;
    while((c = fgetc(in)) != EOF){
        if(c>=32&&c<=126)fprintf(out,"%c",convertion_table[c]);
        else fprintf(out,"%c",c);
    }

然后发现,还没过。。。

又经过了“缜密”地分析,发现:当我们刚进行第4步的时候,while循环中,第一次就把first结点free掉了,然而最后给剩下的那一个结点赋值时,用了first->ch——这显然不对,于是我们预先把first结点的值记录下来,最后靠这个来赋值:

    char convertion_table[130] = {0};//密文转换表
    p = first;
    char first_index = first->ch;
    while(p->next->next!=p){//特判此时链表中是否只剩下两个结点,作为循环终止条件
        int org = (int)(p->ch);
        findprevious(p)->next = p->next;//将p所指结点的上一个结点与下一个结点相连
        node* tmp = p;
        p = p->next;
        free(tmp);
        for(int i=1;i<org;i++){
            p = p->next;
        }
        convertion_table[org] = p->ch;
    }

    //此时链表中还有两个结点
    int org = (int)(p->ch);
    convertion_table[org] = org%2 ? p->next->ch : p->ch;
    convertion_table[!org%2 ? (int)(p->next->ch) : (int)(p->ch)] = first_index;
    free(p ->next);
    free(p);

然后,还是不过。。。但是我们已经发现了这里的许多bug了(这里改的这个是很关键的,之所以能过4个点可能是其中没有first结点所对应的那个字符)

再经过了最后一次检查,发现其实问题出现在这两行上:

    convertion_table[org] = org%2 ? p->next->ch : p->ch;
    convertion_table[!org%2 ? (int)(p->next->ch) : (int)(p->ch)] = first_index;

此时链表中只剩下两个结点,p指向其中一个,这里写的三目表达式的意思是判断p现在指向的结点字符对应的ASCII码值是奇数还是偶数,但这里就陷入了一个误区:题目中要求的是先删除结点,然后再沿着链表找,也就是说,无论奇偶,在密码替换表中,对应的都是下一个结点,而另一个结点对应的一定是最开始的first(已经被free)所对应的值,故修改如下:

    convertion_table[org] = p->next->ch;
    convertion_table[(int)(p->next->ch)] = first_index;

完整代码

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

typedef struct node{
    char ch;
    struct node *next;
} node;

node* findprevious(node*now);

int main()
{
    //读取秘钥
    char key[35] = {0};
    gets(key);

    int already_used[130] = {0};//记录已经使用过的字符
    node *first = malloc(sizeof(node));
    node *p = first;//记录当前链表末尾
    if(key[0]=='\0'){//读入的是空字符串
        p->ch = 32;
        already_used[32] = 1;
    }else {
        p->ch = key[0];
        already_used[(int)key[0]] = 1;
        for(int i=1;key[i];i++){
            if(already_used[(int)key[i]] == 0){//该字符还没有被使用过
                already_used[(int)key[i]] = 1;
                p->next = malloc(sizeof(node));
                p = p->next;
                p->ch = key[i];
            }
        }
    }

    for(int i=32;i<=126;i++){//所有字符都遍历一次
        if(already_used[i]==0){//还没有被用过
            p->next = malloc(sizeof(node));
            p = p->next;
            p->ch = i;            
        }
    }
    //最后将链表首尾连接,成为循环链表
    p->next = first;
    
    char convertion_table[130] = {0};//密文转换表
    p = first;
    char first_index = first->ch;
    while(p->next->next!=p){//特判此时链表中是否只剩下两个结点,作为循环终止条件
        int org = (int)(p->ch);
        findprevious(p)->next = p->next;//将p所指结点的上一个结点与下一个结点相连
        node* tmp = p;
        p = p->next;
        free(tmp);
        for(int i=1;i<org;i++){
            p = p->next;
        }
        convertion_table[org] = p->ch;
    }

    //此时链表中还有两个结点
    int org = (int)(p->ch);
    convertion_table[org] = p->next->ch;
    convertion_table[(int)(p->next->ch)] = first_index;
    free(p ->next);
    free(p);

    FILE* in = fopen("in.txt", "r");
    FILE* out = fopen("in_crpyt.txt","w");
    int c;
    while((c = fgetc(in)) != EOF){
        if(c>=32&&c<=126)fprintf(out,"%c",convertion_table[c]);
        else fprintf(out,"%c",c);
    }

    return 0;
}



node* findprevious(node*now)
{
    node* p = now;
    while(p->next!=now){
        p = p->next;
    }
    return p;
}

5. 词频统计(数组或链表实现)

问题

问题描述

编写程序统计一个英文文本文件中每个单词的出现次数(词频统计),并将统计结果按单词字典序输出到屏幕上。

注:在此单词为仅由字母组成的字符序列。包含大写字母的单词应将大写字母转换为小写字母后统计。

输入形式

打开当前目录下文件“article.txt”,从中读取英文单词进行词频统计。

输出形式

程序将单词统计结果按单词字典序输出到屏幕上,每行输出一个单词及其出现次数,单词和其出现次数间由一个空格分隔,出现次数后无空格,直接为回车。

样例输入

当前目录下文件article.txt内容如下:

“Do not take to heart every thing you hear.”

“Do not spend all that you have.”

“Do not sleep as long as you want;”

样例输出

all 1

as 2

do 3

every 1

have 1

hear 1

heart 1

long 1

not 3

sleep 1

spend 1

take 1

that 1

thing 1

to 1

want 1

you 3

样例说明

按单词字典序依次输出单词及其出现次数。

评分标准

通过所有测试点将得满分。

问题分析

本题的整体思路很清晰:从文件中将各个单词读出来存储,再对其按照字典序排序,输出时仍然可以参考第二次作业第4题的思路,以“队列”或“栈”的思想去输出。而读单词时,我们知道每个单词必定是连续的多个字母排列而成,所以可以采用相同的思想来处理。

具体实现过程

由于本题只有文件输入而没有控制台的输入,所以可以用freopen函数进行输入重定向:

#include <stdio.h>
int main()
{
    freopen("article.txt","r",stdin);//输入重定向
    
    return 0;
}

而后我们按照队列的思想,读取所有单词的小写模式,并存储到数组中:

    char gather[1000][15] = {{0}};
    char queue[15] = {0};//暂时存储单词
    int queue_len = 0;//记录当前队列中字符个数
    int num = 0;//记录单词总个数
    char ch = 0;
    while(ch = getchar() != EOF){
        if(isalpha(ch)){
            ch = belower(ch);
            queue[queue_len] = ch;
            queue_len++;
        }else {
            if(queue_len==0)
                continue;//在队列为空时读到了非字母字符,跳过
            else {
                //清空队列
                for(int i=0;i<queue_len;i++){
                    gather[num][i] = queue[i];
                }
                queue_len = 0;
            }
        }
    }
    //最后清空队列
    if(queue_len!=0){
        for(int i=0;i<queue_len;i++){
            gather[num][i] = queue[i];
        }
        num++;
    }

其中isalpha和belower函数实现如下:

int isalpha(char ch)
//判断一个字符是否是字符
{
    if(ch>='a' && ch<='z'||ch>='A'&&ch>='Z')return 1;
    return 0;
}

char belower(char ch)
//将输入的字母转化为小写形式
{
    if(ch >='A'&&ch<='Z')return ch +'a' - 'A';
    return ch;
}

而后设计cmp函数,以使用qsort排序:

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

在主函数中对读入的数据进行排序:

qsort(gather,num,sizeof(gather[0]),cmp);

接下来,对排好序的数组,同样采用队列的思想输出,只是这里并不需要对数据再进行存储了:我们只需要记录当前队列中的单词是什么,以及该单词当前已经连续出现多少个即可:

    char *cur_word;//当前的单词
    int cur_num = 0;//当前队列中元素的数量
    for(int i=0;i<num;i++){
        if(cur_num==0){
            cur_word = gather[i];
            cur_num++;
        }else {
            if(strcmp(cur_word,gather[i])==0){//这个单词和队列中单词相同
                cur_num++;
            }else {
                //输出
                printf("%s %d\n",cur_word,cur_num);
                //新单词入队
                cur_word = gather[i];
                cur_num = 1;
            }
        }
    }
    printf("%s %d\n",cur_word,cur_num);//最后清空队列

完整代码

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

int Isalpha(char ch);
char belower(char ch);
int cmp(const void *a, const void *b);


int main()
{
    freopen("article.txt","r",stdin);//输入重定向
    char gather[1000][15] = {{0}};
    char queue[15] = {0};//暂时存储单词
    int queue_len = 0;//记录当前队列中字符个数
    int num = 0;//记录单词总个数
    char ch = 0;
    while(scanf("%c",&ch)!=EOF){
        if(Isalpha(ch)){
            ch = belower(ch);
            queue[queue_len] = ch;
            queue_len++;
        }else {
            if(queue_len==0)
                continue;//在队列为空时读到了非字母字符,跳过
            else {
                //清空队列
                for(int i=0;i<queue_len;i++){
                    gather[num][i] = queue[i];
                }
                num++;
                queue_len = 0;
            }
        }
        
    }
    //最后清空队列
    if(queue_len!=0){
        for(int i=0;i<queue_len;i++){
            gather[num][i] = queue[i];
        }
        num++;
    }
    
    qsort(gather,num,sizeof(gather[0]),cmp);
    
    char *cur_word;//当前的单词
    int cur_num = 0;//当前队列中元素的数量
    for(int i=0;i<num;i++){
        if(cur_num==0){
            cur_word = gather[i];
            cur_num++;
        }else {
            if(strcmp(cur_word,gather[i])==0){//这个单词和队列中单词相同
                cur_num++;
            }else {
                //输出
                printf("%s %d\n",cur_word,cur_num);
                //新单词入队
                cur_word = gather[i];
                cur_num = 1;
            }
        }
    }
    printf("%s %d\n",cur_word,cur_num);//最后清空队列

    return 0;
}


int Isalpha(char ch)
//判断一个字符是否是字符
{
    if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z'))return 1;
    return 0;
}

char belower(char ch)
//将输入的字母转化为小写形式
{
    if(ch >='A'&&ch<='Z')return ch +'a' - 'A';
    return ch;
}


int cmp(const void *a, const void *b)
{
    return strcmp((char*)a, (char*)b);
}
  • 10
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值