BUAA数据结构第二次作业2023

BUAA数据结构第二次作业2023

1. 五子棋危险判断

题目

问题描述

已知两人分别执白棋和黑棋在一个围棋棋盘上下五子棋,若同一颜色的棋子在同一条横行、纵行或斜线上连成5个棋子,则执该颜色棋子的人获胜。编写程序读入某一时刻下棋的状态,并判断是否有人即将获胜,即:同一颜色的棋子在同一条横行、纵列或斜线上连成4个棋子,且该4个棋子的两端至少有一端为空位置。
输入的棋盘大小是19×19,用数字0表示空位置(即没有棋子),用数字1表示该位置下了一白色棋子,用数字2表示该位置下了一黑色棋子。假设同一颜色的棋子在同一条横行、纵列或斜线上连成的棋子个数不会超过4个,并且最多有一人连成线的棋子个数为4。

输入形式

从控制台输入用来表示棋盘状态的数字0、1或2;每行输入19个数字,各数字之间以一个空格分隔,每行最后一个数字后没有空格;共输入19行表示棋盘状态的数字。

输出形式

若有人即将获胜,则先输出即将获胜人的棋子颜色(1表示白色棋子,2表示黑色棋子),然后输出英文冒号:,最后输出连成4个棋子连线的起始位置(棋盘横行自上往下、纵列自左往右从1开始计数,横行最小的棋子在棋盘上的横行数和纵列数作为连线的起始位置,若在同一行上,则纵列数最小的棋子位置作为起始位置,两数字之间以一个英文逗号,作为分隔符)。
若没有人获胜,则输出英文字符串:No。
无论输出什么结果,最后都要有回车换行符。

输入样例1

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 0 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 2 1 1 1 1 2 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 2 1 2 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 1 0 2 2 0 0 0 0 0 0 0 0
0 0 0 0 0 2 0 1 0 0 2 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

输出样例1

1:9,8

输入样例2

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 2 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 2 2 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

输出样例2

No

样例说明

在输入的样例1中,执白棋(数字1表示)的人即将获胜,连成4个棋子且有一端为空的起始位置在第9行第8列,所以输出1:9,8。
在输入的样例2中,还没有同一颜色的棋子连成4个,所以无人即将获胜,直接输出No。

评分标准

该题要求判断五子棋的棋盘状态,提交程序文件名为chess.c。

问题分析

很容易想到要先开一个二维数组存储棋盘状态,接下来要对当前棋盘上的情况做检测。我们要检测的有两点:

  1. 棋盘上是否有某一颜色的棋子在横/竖/斜的方向上连了4颗。
  2. 在连了4颗的情况下,其“两边”是否被另一种颜色的棋子堵住了。

同时我们还要按照题目要求的规则记录下连成4个棋子连线的起始位置。很显然横、竖方向上的检测方法都很直观,斜方向上的情况会稍微复杂一点,然而无论第一点还是第二点,我们都要考虑我们目标的棋子是否超出了棋盘的范围。

具体处理过程

围棋盘是19X19方格,很自然地想到开一个20X20的数组去存——而且忽略掉下标为0的元素,以使我们存储的下标和实际的位置一一对应。

#include <stdio.h>

int main()
{
    int grid[20][20] = {{0}};
    for(int i = 1;i < 20;i++){
        for(int j = 1;j < 20;j++){
            scanf("%d",&grid[i][j]);   //从grid[1][1]存到grid[19][19]
        }
    }
}

接下来要对横、竖、斜三个方向做检测,最自然(暴力)的想法肯定是再把所有棋子都遍历一遍,针对每个棋子,分别在三个方向上检测,然而其实并不只有三个方向:横着的有左和右、竖着的有上和下、斜着的有左上,左下,右上,右下——如果这样去遍历,显然情况太多太复杂了。但仔细分析就能发现这样的规律:如果某一颗棋子和它左边的三颗棋子连在一起,那么针对这4颗中最左边的那颗棋子来说,它就和右边的三颗连在一起了。也就是说:针对横着的情况,我们只需要针对每一颗棋子去检测它是否和右边的三颗棋子连在一起了,就完全等价于把所有横着的情况都检测到了。所以我们只需要检测右,下,右下,左下这四个方向即可。

其实大多数人能靠直觉想到针对横着和竖着的情况,只要检测右方向和下方向就行,而对斜着的情况似乎大为头疼。事实上,经过分析,这和横竖的情况并没有什么不一样。

同时,我们显然不需要对当前没有棋子的位置去检测是否连成四个了。

综合以上分析,设计出下面这个函数的雏形——用于判断是否有某一色的棋子连成4个:

int isWin(int grid[][20],int *x, int *y)
//通过函数返回谁赢了,通过x,y返回“起始位置”
{
    for(int i=1;i<20;i++){
        for(int j=1;j<20;j++){   //从左至右,从上至下遍历
            if(!grid[i][j])continue;    //当前位置没有棋子,跳过本次检测

            /*竖向检测*/        //判断j+3<20是判断最下边的那颗棋子是否在棋盘上,防止越界
            if(grid[i][j]==grid[i][j+1]&&grid[i][j+1]==grid[i][j+2]&&grid[i][j+2]==grid[i][j+3]&&j+3<20){
                *x = i; *y = j;
                return grid[i][j];               
            }

            /*横向检测*/         //判断i+3<20是判断最右边的那颗棋子是否在棋盘上,防止越界
            if(grid[i][j]==grid[i+1][j]&&grid[i+1][j]==grid[i+2][j]&&grid[i+2][j]==grid[i+3][j]&&i+3<20){
                *x = i; *y = j;
                return grid[i][j];               
            }

            /*斜向检测*/
            /*右下方向*/                                                               
            if(grid[i][j]==grid[i+1][j+1]&&grid[i+1][j+1]==grid[i+2][j+2]&&grid[i+2][j+2]==grid[i+3][j+3]
                &&i+3<20&&j+3<20){     //此处i+3<20&&j+3<20同理
                *x = i; *y = j;
                return grid[i][j];                         
            }

            /*左下方向*/
            if(grid[i][j]==grid[i-1][j+1]&&grid[i-1][j+1]==grid[i-2][j+2]&&grid[i-2][j+2]==grid[i-3][j+3]
                &&i-3<20&&j+3<20){
                *x = i-3; *y = j+3;
                return grid[i][j];               
            }
        }
    }

}

但我们清楚,现在这个函数只判断了是否有某一色的棋子连成了四个,还没有检测其两边有没有棋子——这个只需要在每一种情况下面加上一个判断即可。(本题的四种情况检测可以合成为一个,这样的话就不是很直观,此处不做展示,读者可以自行完成)

函数最终版本:

int isWin(int grid[][20],int *x, int *y)
//通过函数返回谁赢了,通过x,y返回“起始位置”
{
    for(int i=1;i<20;i++){
        for(int j=1;j<20;j++){   //从左至右,从上至下遍历
            if(!grid[i][j])continue;    //当前位置没有棋子,跳过本次检测

            /*竖向检测*/              //判断j+3<20是判断最下边的那颗棋子是否在棋盘上,防止越界
            if(grid[i][j]==grid[i][j+1]&&grid[i][j+1]==grid[i][j+2]&&grid[i][j+2]==grid[i][j+3]&&j+3<20){
                if((j+4<20&&grid[i][j+4]==0)||(j-1>0&&grid[i][j-1]==0)){/*判断两边是否有空也要进行越界审查(棋子贴在棋盘边缘也被认为是“被堵住了”)*/
                    *x = i; *y = j;                                                                               
                    return grid[i][j];
                }                               
            }

            /*横向检测*/              //判断i+3<20是判断最右边的那颗棋子是否在棋盘上,防止越界
            if(grid[i][j]==grid[i+1][j]&&grid[i+1][j]==grid[i+2][j]&&grid[i+2][j]==grid[i+3][j]&&i+3<20){
                if((i+4<20&&grid[i+4][j]==0)||(i-1>0&&grid[i-1][j]==0)){
                    *x = i; *y = j;
                    return grid[i][j];
                }                             
            }

            /*斜向检测*/
            /*右下方向*/                                                               
            if(grid[i][j]==grid[i+1][j+1]&&grid[i+1][j+1]==grid[i+2][j+2]&&grid[i+2][j+2]==grid[i+3][j+3]
                &&i+3<20&&j+3<20){    //此处i+3<20&&j+3<20同理
                if((i+4<20&&j+4<20&&grid[i+4][j+4]==0)||(i-1>0&&j-1>0&&grid[i-1][j-1]==0)){
                    *x = i; *y = j;
                    return grid[i][j];
                }                                         
            }

            /*左下方向*/
            if(grid[i][j]==grid[i-1][j+1]&&grid[i-1][j+1]==grid[i-2][j+2]&&grid[i-2][j+2]==grid[i-3][j+3]
                &&i-3<20&&j+3<20){
                if((i-4>0&&j+4<20&&grid[i-4][j+4]==0)||(i+1<20&&j-1>0&&grid[i+1][j-1]==0)){
                    *x = i-3; *y = j+3;
                    return grid[i][j];
                }                               
            }
        }
    }
    return 0;//经过检测后没有返回,则没有获胜者。
}

接下来只需要在主程序中对函数调用结果进行判断,输出相应的内容即可。

完整代码

#include <stdio.h>
int isWin(int grid[][20],int *x, int *y);

int main()
{
    int grid[20][20] = {{0}};
    for(int i = 1;i < 20;i++){
        for(int j = 1;j < 20;j++){
            scanf("%d",&grid[i][j]);   //从grid[1][1]存到grid[19][19]
        }
    }
    int x,y;
    int winner = isWin(grid,&x,&y);
    if(winner==1)printf("%d:%d,%d\n",1,x,y);
    else if(winner==2)printf("%d:%d,%d\n",2,x,y);
    else printf("NO\n");
}


int isWin(int grid[][20],int *x, int *y)
//通过函数返回谁赢了,通过x,y返回“起始位置”
{
    for(int i=1;i<20;i++){
        for(int j=1;j<20;j++){   //从左至右,从上至下遍历
            if(!grid[i][j])continue;    //当前位置没有棋子,跳过本次检测

            /*竖向检测*/              //判断j+3<20是判断最下边的那颗棋子是否在棋盘上,防止越界
            if(grid[i][j]==grid[i][j+1]&&grid[i][j+1]==grid[i][j+2]&&grid[i][j+2]==grid[i][j+3]&&j+3<20){
                if((j+4<20&&grid[i][j+4]==0)||(j-1>0&&grid[i][j-1]==0)){/*判断两边是否有空也要进行越界审查(棋子贴在棋盘边缘也被认为是“被堵住了”)*/                  
                    *x = i; *y = j;                                                                                 
                    return grid[i][j];
                }                               
            }

            /*横向检测*/              //判断i+3<20是判断最右边的那颗棋子是否在棋盘上,防止越界
            if(grid[i][j]==grid[i+1][j]&&grid[i+1][j]==grid[i+2][j]&&grid[i+2][j]==grid[i+3][j]&&i+3<20){
                if((i+4<20&&grid[i+4][j]==0)||(i-1>0&&grid[i-1][j]==0)){
                    *x = i; *y = j;
                    return grid[i][j];
                }                             
            }

            /*斜向检测*/
            /*右下方向*/                                                               
            if(grid[i][j]==grid[i+1][j+1]&&grid[i+1][j+1]==grid[i+2][j+2]&&grid[i+2][j+2]==grid[i+3][j+3]
                &&i+3<20&&j+3<20){    //此处i+3<20&&j+3<20同理
                if((i+4<20&&j+4<20&&grid[i+4][j+4]==0)||(i-1>0&&j-1>0&&grid[i-1][j-1]==0)){
                    *x = i; *y = j;
                    return grid[i][j];
                }                                         
            }

            /*左下方向*/
            if(grid[i][j]==grid[i-1][j+1]&&grid[i-1][j+1]==grid[i-2][j+2]&&grid[i-2][j+2]==grid[i-3][j+3]
                &&i-3<20&&j+3<20){
                if((i-4>0&&j+4<20&&grid[i-4][j+4]==0)||(i+1<20&&j-1>0&&grid[i+1][j-1]==0)){
                    *x = i-3; *y = j+3;
                    return grid[i][j];
                }                               
            }
        }
    }
    return 0;//经过检测后没有返回,则没有获胜者。
}

思路二(改进)

上述的解法完全是暴力地去遍历,每个棋子都要被遍历一次,而且针对每一个棋子,我们还要判断它右边、下边、右下、左下方向上的相连的4颗棋子,这实际上造成了较大的时间浪费,比如当判断某一行时,棋子如下排列:

1112

当遍历到第一颗棋子时,它和它右边的3个棋子连成的序列是1 1 1 2 ,不满足四个相同的棋子连在一起。由于有这个2的限制,我们完全可以不用再去管后面的棋子是怎么样排列的,就可以知道从第二颗棋子开始的序列1 1 2 ? 不可能满足题意——也就是说再在横方向上遍历下一颗棋子是多余的。

同时,上述算法的代码可读性较差,对边界的审查逻辑混杂在判断四颗棋子相连的逻辑里,容易出漏。

事实上,本题看起来要在多个方向上判断,逻辑很复杂,但是如果把这几个方向隔离来看,问题就能大大简化。

我们来考虑只做横向检测的情况:显然一共有19行,意味着我们可以继续把问题拆分成19个相同的更简单的问题,针对每一行,我们可以设置一个计数器,以及一个记录棋子状态的变量,从第一颗棋子开始,遇到棋盘上为0的点就把计数器置0;计数器为0时记录下当前位置上棋子的状态并把计数器置1;遇到与当前记录的棋子状态相同的棋子(比如当前记录的状态是1,而当前这个棋子恰好也是1)就把计数器加一;遇到与当前记录的状态相反的棋子就把计数器置1并改变记录的状态。比如若有一行的棋子排列如下:

112022220

初始化计数器counter = 0, 记录状态的变量winner = 0.遇到第一颗棋子时,由于棋盘上的数非0,且当前计数器为0,故将winner赋值为1,counter赋值为1.继续遍历下一颗棋子时,棋盘上的棋子为1,与当前状态winner相同,故counter加一,变为2.而后到了第三颗,棋盘上的棋子为2,与当前状态(winner)不同,故把winner赋值为2,并把counter置1.接下来遇到了0,故把winner和counter均置0.而后的棋子为2,由于当前状态下counter为0,故把winner置为2,counter置1…

代码实现如下

int isWin(int grid[][20],int *x,int *y)
{//用x,y返回“初始位置”
    int counter,winner;
    //winner记录状态,counter为计数器

            /*横向检测*/
    for(int i = 1;i < 20;i++){//遍历19行
        counter = 0,winner = 0;//针对每次检测,初始化状态
        for(int j = 1;j < 20;j++){//遍历该行中每颗棋子
            if(0==grid[i][j]){
                winner = 0;
                counter = 0;
                continue;
            }
            if(0==counter){
                winner = grid[i][j];
                counter = 1;
                *x = i;
                *y = j;
                continue;
            }
            if(grid[i][j]==winner){
                counter++;
            }else {
                counter = 1;
                winner = grid[i][j];
                *x = i;
                *y = j;
            }                     
        }
        //@@
    }    
}

注意此处用了两个continue,方便理解,如果读者想纯用if else去写,一定要注意其中的逻辑顺序。

在此基础上,我们还要在每颗棋子遍历后去判断当前状态是否已经满足获胜条件,如果满足,把它返回。获胜的条件还是两点:

  • 四颗棋子连在一起(本函数中即counter==4)
  • 这四颗棋子两边没有被“堵上”

只需要在上述代码“//@@”处添加如下代码即可

if(4==counter&&((*y-1>0&&grid[i][*y-1]==0)||(*y+4>0&&grid[i][*y+4]==0))){
    return winner;
}

如此便完整地设计出了横向检测的函数,相比暴力遍历,此法的运行效率更高,代码可读性也更强。

竖向检测的道理相同,此处不作展示。更难的似乎是两个斜方向上的检测——似乎我们无法像横竖向检测这样,找到明确的起始点(每一行第一颗棋子)。其实,要是明白了起始点的含义其实就是该方向上的第一颗棋子,我们就很容易把斜方向的问题转化为熟悉的横竖方向的问题。以右下方向为例,我们原来看的棋盘是横平竖直的正方形,现在保持棋盘不动,我们把头逆时针旋转45°(或者把整个棋盘绕中心顺时针旋转45°),以对角线的方向去看,整个棋盘其实就是19条长度不等的“竖线”排列而成的。如此,我们就把斜方向的问题转化为了类似于竖向检测的问题,只是这19条竖线长度不等而已。

在我们判断完4个方向后若仍没有返回,则可以认为当前没有赢家,在函数最后返回0即可。思路讲解至此,具体实现的代码留给读者自行完成。

2. 字符串替换(新)

题目

问题描述

编写程序将一个指定文件中某一字符串替换为另一个字符串。要求:(1)被替换字符串若有多个,均要被替换;(2)指定的被替换字符串,大小写无关。

输入形式

给定文件名为filein.txt。从控制台输入两行字符串(不含空格,行末尾都有回车换行符),分别表示被替换的字符串和替换字符串。

输出形式

将替换后的结果输出到文件fileout.txt中。

样例输入

从控制台输入两行字符串:

in

out

文件filein.txt的内容为:

#include <stdio.h>

void main()

{

FILE * IN;

if((IN=fopen(“in.txt”,“r”))==NULL)

{

​ printf(“Can’t open in.txt!”);

​ return;

}

fclose(IN);

}

样例输出

文件fileout.txt的内容应为:

#outclude <stdio.h>

void maout()

{

FILE * out;

if((out=fopen(“out.txt”,“r”))==NULL)

{

​ prouttf(“Can’t open out.txt!”);

​ return;

}

fclose(out);

}

样例说明

输入的被替换字符串为in,替换字符串为out,即将文件filein.txt中的所有in字符串(包括iN、In、IN字符串)全部替换为out字符串,并输出保存到文件fileout.txt中。

评分标准

该题要求得到替换后的文件内容,共有5个测试点。上传C语言文件名为replace.c。

问题分析

文件的读入与写入有固定的格式,寻找匹配字符串也可以用内置函数strstr来实现,唯一的难点就是不区分原文件的大小写。我们可以先把原文件的字符与读入的要更改的字符都改成小写,但这样做的问题就是输出的时候不能输出全改成小写之后的字符。利用数组在空间中连续存储的特点,我们可以完美地解决这个问题,因为通过这个特性,我们可以将创建好的两个数组关联起来:比如有一个100位的数组,在内存中占据地址为1 2 3 4 … 100 的内存,另一个100位的数组占据地址为201 202 … 300的内存,我们只需要计算这两个数组首地址的差(201-1),即可将两个数组的元素一一对应地关联起来。

具体处理过程

首先初始化

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{    
    char org[512]={0};//存储要替换的字符
    char rep[512] = {0};//存储替换后的字符
    
    gets(org);   
    gets(rep);
    
    return 0;
}

显然我们要写一个函数,它使得输入字符串中所有大写字符都转化为小写字符。

void belower(char *org,char *lower)
//org为原字符,lower为返回的替换后的字符
{
    int i=0;
    while(org[i]!='\0'){
        if(org[i]>='A'&&org[i]<='Z'){
            lower[i] = org[i]+'a'-'A';
        }else lower[i] = org[i];
        i++;
    }
    lower[i] = '\0';//这一步容易忽略,不加这一步返回的可能就不是字符串了
}

于是,在主函数中,我们继续按流程操作:

belower(org,org);//将org转换为小写形式
char buffer[1024] = {0};//记录读入文件的每一行
char buf_lower[1024] = {0};//buffer转换为小写后的
int deviation = buffer - buf_lower;//两个字符串首地址差值
FILE *text = fopen("filein.txt", "r");//文件操作
FILE *out = fopen("fileout.txt", "w");
if(text == NULL)
        exit(1);

接下来我们还需要写一个函数,他接收原字符串的小写形式与要替换的字符的小写形式,输出替换后的字符串(含有大写字符):

void replace(char *buf_lower, char *org,char *rep,int deviation,FILE*out)
//buf_lower:原字符串小写形式
//org:要替换的字符串(转换为小写形式后的)
//rep:替换后的字符串
//deviation:原字符串与其小写形式字符串每一位地址的差
//out:输出的文件
{
    char *temp;
    char *begin = buf_lower;
    int org_len = strlen(org);

    while(strstr(begin,org) != NULL){
        temp = strstr(begin,org);
        *(temp+deviation) = '\0';//在原字符串对应位置截断,以便把前面的字符输出
        fputs(begin+deviation,out);
        fputs(rep,out);
        begin = temp+org_len;
    }
    fputs(begin+deviation,out);//最后还要把剩余部分输出
}

于是,主函数中,对文件的每一行调用replace函数即可

while(fgets(buffer,1024,text) != NULL){
        belower(buffer,buf_lower);       
        replace(buf_lower,org,rep,deviation,out);     
    }

完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void belower(char *org,char *lower);
void replace(char *buf_lower, char *org,char *rep,int deviation,FILE*out);

int main()
{    
    char org[512]={0};//存储要替换的字符
    char rep[512] = {0};//存储替换后的字符
    
    gets(org);   
    gets(rep);
    belower(org,org);//将org转换为小写形式
    
    char buffer[1024] = {0};//记录读入文件的每一行
	char buf_lower[1024] = {0};//buffer转换为小写后的
	int deviation = buffer - buf_lower;//两个字符串首地址差值
	FILE *text = fopen("filein.txt", "r");//文件操作
	FILE *out = fopen("fileout.txt", "w");
	if(text == NULL)
        exit(1);
    
    while(fgets(buffer,1024,text) != NULL){
        belower(buffer,buf_lower);       
        replace(buf_lower,org,rep,deviation,out);       
    }
    
    return 0;
}

void belower(char *org,char *lower)
//org为原字符,lower为返回的替换后的字符
{
    int i=0;
    while(org[i]!='\0'){
        if(org[i]>='A'&&org[i]<='Z'){
            lower[i] = org[i]+'a'-'A';
        }else lower[i] = org[i];
        i++;
    }
    lower[i] = '\0';//这一步容易忽略,不加这一步返回的可能就不是字符串了
}

void replace(char *buf_lower, char *org,char *rep,int deviation,FILE*out)
//buf_lower:原字符串小写形式
//org:要替换的字符串(转换为小写形式后的)
//rep:替换后的字符串
//deviation:原字符串与其小写形式字符串每一位地址的差
//out:输出的文件
{
    char *temp;
    char *begin = buf_lower;
    int org_len = strlen(org);

    while(strstr(begin,org) != NULL){
        temp = strstr(begin,org);
        *(temp+deviation) = '\0';//在原字符串对应位置截断,以便把前面的字符输出
        fputs(begin+deviation,out);
        fputs(rep,out);
        begin = temp+org_len;
    }
    fputs(begin+deviation,out);//最后还要把剩余部分输出
}

3. 加密文件

题目

问题描述

有一种加密方法为:其使用一个字母串(可以含重复字母,字母个数不超过50)作为密钥。假定密钥单词串为feather,则先去掉密钥单词中的重复字母得到单词串feathr,然后再将字母表中的其它字母以反序追加到feathr的后面:

feathrzyxwvusqponmlkjigdcb

加密字母的对应关系如下:

abcdefghijklmnopqrstuvwxyz
feathrzyxwvusqponmlkjigdcb

其中第一行为原始英文字母,第二行为对应加密字母。其它字符不进行加密。编写一个程序,用这种密码加密文件。假定要加密的文件名为encrypt.txt及加密后的文件名为output.txt,并假定输入文件中字母全为小写字母,并且输入密钥也全为小写字母。

输入形式

从标准输入中输入密钥串,并从文件encrypt.txt中读入要加密的内容。

输出形式

加密后结果输出到文件output.txt中。

样例输入

feather
和文件encrypt.txt中内容,例如被加密的文件encrypt.txt中内容为:
c language is wonderful.

样例输出

加密后output.txt文件中内容为:
a ufqzjfzh xl gpqthmrju.

样例说明

首先将给定的密钥单词去除重复字母,然后按照上面的加密对应表对encrypt.txt文件内容进行加密即可得到加密后的文件,其中只对英文字母进行加密对换,并且假设encrypt.txt中的英文字母全是小写字母。

评分标准

该题要求对文件进行加密,共有5个测试点。提交程序名为encrypt.c

问题分析

本题我们按照题目的要求,完成以下步骤即可:

  1. 读取秘钥字符串,去掉其中的相同字母
  2. 将剩下的字母按要求排列在秘钥后形成对应表(所以第一步中我们应当记录下出现过的字母)
  3. 读入要加密的字符串,对其加密输出

具体实现过程

前两步实现如下:

#include <stdio.h>
#include <string.h>
char secret_key [27];  // 加密对应表


int main()
{
    char org_word[55] = {0};  // 最开始读取的秘钥
    gets(org_word);
    

    int already_used[26] = {0};  // 记录哪些积木已经被用过了(字母1-26编号)
    int count = 0;
    for(int i=0;org_word[i];i++){
        if(!already_used[org_word[i] - 'a']){  // 没被用过
            already_used[org_word[i] - 'a'] = 1;
            secret_key[count++] = org_word[i];           
        }
    }   
    for(int i=25;i>=0;i--){
        if(!already_used[i]){
            
            secret_key [count++] = 'a' + i;
        }
    }
    return 0;
}

而后对文件中的字符串加密,注意我们的对应表利用了字母对应1-26数字,所以可以通过数组的索引来获取到加密后的字母。

    FILE *text = fopen("encrypt.txt", "r");
    FILE *out = fopen("output.txt", "w");
    
    char buffer[1024];
    while(fgets(buffer,1024,text) != NULL){
        encrypt(buffer,out);
        
    }
    return 0;

其中encrypt函数实现如下:

void encrypt(char *org,FILE*out)
{
    for(int i=0;org[i];i++){
        if(org[i]>='a'&&org[i]<='z'){
            fputc(secret_key[org[i]-'a'],out);
        }else fputc(org[i],out);
    }
}

完整代码

#include <stdio.h>
#include <string.h>
char secret_key [27];  // 加密对应表
void encrypt(char *org,FILE*out);

int main()
{
    char org_word[55] = {0};  // 最开始读取的秘钥
    gets(org_word);
    

    int already_used[26] = {0};  // 记录哪些积木已经被用过了(字母1-26编号)
    int count = 0;
    for(int i=0;org_word[i];i++){
        if(!already_used[org_word[i] - 'a']){  // 没被用过
            already_used[org_word[i] - 'a'] = 1;
            secret_key[count++] = org_word[i];           
        }
    }   
    for(int i=25;i>=0;i--){
        if(!already_used[i]){
            
            secret_key [count++] = 'a' + i;
        }
    }
    
    FILE *text = fopen("encrypt.txt", "r");
    FILE *out = fopen("output.txt", "w");
    
    char buffer[1024];
    while(fgets(buffer,1024,text) != NULL){
        encrypt(buffer,out);
        
    }
    return 0;
}

void encrypt(char *org,FILE*out)
{
    for(int i=0;org[i];i++){
        if(org[i]>='a'&&org[i]<='z'){
            fputc(secret_key[org[i]-'a'],out);
        }else fputc(org[i],out);
    }
}

4. 通讯录整理

题目

问题描述

读取一组电话号码簿(由姓名和手机号码组成),将重复出现的项删除(姓名和电话号码都相同的项为重复项,只保留第一次出现的项),并对姓名相同手机号码不同的项进行如下整理:首次出现的项不作处理,第一次重复的姓名后面加英文下划线字符_和数字1,第二次重复的姓名后面加英文下划线字符_和数字2,依次类推。号码簿中姓名相同的项数最多不超过10个。最后对整理后的电话号码簿按照姓名进行从小到大排序,并输出排序后的电话号码簿

输入形式

先从标准输入读取电话号码个数,然后分行输入姓名和电话号码,姓名由不超过20个英文小写字母组成,电话号码由11位数字字符组成,姓名和电话号码之间以一个空格分隔,输入的姓名和电话号码项不超过100个。

输出形式

按照姓名从小到大的顺序分行输出最终的排序结果,先输出姓名再输出电话号码,以一个空格分隔。

样例输入

15

liping 13512345678

zhaohong 13838929457

qiansan 13900223399

zhouhao 18578294857

anhai 13573948758

liping 13512345678

zhaohong 13588339922

liping 13833220099

boliang 15033778877

zhaohong 13838922222

tianyang 18987283746

sunnan 13599882764

zhaohong 13099228475

liushifeng 13874763899

caibiao 13923567890

样例输出

anhai 13573948758

boliang 15033778877

caibiao 13923567890

liping 13512345678

liping_1 13833220099

liushifeng 13874763899

qiansan 13900223399

sunnan 13599882764

tianyang 18987283746

zhaohong 13838929457

zhaohong_1 13588339922

zhaohong_2 13838922222

zhaohong_3 13099228475

zhouhao 18578294857

样例说明

输入了15个人名和电话号码。其中第一项和第六项完全相同,都是“liping 13512345678”,将第六项删除,第一项保留;

第八项和第一项人名相同,电话不同,则将第八项的人名整理为liping_1;同样,第二项、第七项、第十项、第十三项的人名都相同,将后面三项的人名分别整理为:zhaohong_1、zhaohong_2和zhaohong_3。

最后将整理后的电话簿按照姓名进行从小到大排序,分行输出排序结果。

评分标准

该题要求编程实现通讯录的整理与排序,提交程序文件名为sort.c。

问题分析

本题的目标很清晰,可以分为以下几步:

  1. 用合适的方式读入,存储数据
  2. 用恰当的方式对存储的这些数据排序
  3. 将排序后的数据经过一定处理输出

具体处理过程

对第一点,很容易想到用这样一个结构体存储数据

typedef struct _data{
        char name[20];
        char tel[20];
    } data;

而后设计主函数,将数据读入存储

int main()
{
    data gather[105];
    int num;
    scanf("%d", &num);
    for(int i=0;i<num;i++){
        scanf(" %s %s",gather[i].name,gather[i].tel); 
    }
}

此时当然可以冒泡排序:若前一个数据的名字的字典序大于后一个,则交换顺序,否则不换——这样我们才能做到题目中所说“相同的人名不同的手机号的数据,按照输入顺序输出”。然而,我们肯定更喜欢用qsort——不仅运行时间短,而且代码写起来方便,只需要写一个cmp函数即可,不少人想到这,应该会这么去设计cmp函数:

int cmp(const void *e1, const void *e2)
{
	return strcmp(((data*)e1)->name,((data*)e2)->name);    
}

然而这样排序存在一个严重的问题:在qsort函数设计时,对其中的cmp函数,叙述是这样的:

当 qsort 函 数想要确定某两个元素的排列顺序时,会将这两个元素的指针 e1 和 e2 传入 cmp 函数进行元素值的比较:

  • 如果 cmp 函数返回值小于 0,则 e1 所指向元素会被排在 e2 所指向元素的前面
  • 如果 cmp 函数返回值等于 0,则 e1 所指向元素与 e2 所指向元素的顺序不确定(因为在某种意义上两个相同 的元素谁在前谁在后都无伤大雅)
  • 如果 cmp 函数返回值大于 0,则 e1 所指向元素会被排在 e2 所指向元素的后面

然而,对于本题来说,当返回值为0(即两个数据的name相同时),我们要求先输入的数据排在前面,这样才能实现题目所说的“首次出现的项不作处理,第一次重复的姓名后面加英文下划线字符_和数字1,第二次重复的姓名后面加英文下划线字符_和数字2,依次类推。”

比如:在给出的样例中,zhaohong一共有4个不同的号码,数据输入的顺序是

zhaohong 13838929457

zhaohong 13588339922

zhaohong 13838922222

zhaohong 13099228475

而若用上述的cmp函数进行qsort排序,打印排序后的结果,会发现顺序变成了

zhaohong 13838929457

zhaohong 13099228475

zhaohong 13838922222

zhaohong 13588339922

这显然没有完成题目的要求。

如果我们还想继续用qsort函数对这组数据排序的话,就必须得给这些读入的数据增加一些“属性”,以使得任取其中两个数据的地址,我们就能知道它们哪个是先读入的,哪个是后读入的。

我最开始想到的一个方法是在读入每组数据(名字和电话号码)时,针对名字的字符串,在其末尾增加一个字符,比如输入的第一个数据是liping 13512345678 ,我们就把它存储为name:liping1 ;tel:13512345678

这样做的好处是,永远都不会有字典序相同的两个名字了——即使他们的名字完全相同,但由于我们给它加了一个“小尾巴”,所以在计算机看来他们并不同。但问题是由于这里的数字编号其实是“字符”,所以我们只能编号到1-9,解决方法是用ASCII编码的值来编号,这样的话就可以处理128组以内的数据了,题目中说到输入的数据不超过100组,刚好在这个范围之内。

这样做的坏处是:

  • 太麻烦,读入数据时要先把读入的名字字符串末尾加上一个字符,但由于这个字符占据了原来的‘\0’的位置,使得这不是字符串了,所以正确的做法是先读入,然后找到字符串末尾的‘\0’,先把它后面的那个元素改为’\0’,然后再把原字符串末尾的’\0’改成对应的编号,代码如下

    int main()
    {
        data gather[105];
        char *temp[105];
        int num;
        scanf("%d", &num);
        for(int i=0;i<num;i++){
            scanf(" %s %s",temp,gather[i].tel); 
            temp[strlen(temp)+1] = '\0';
            temp[strlen(temp)] = i +1;
            gather[i].name = temp;
        }
    }
    

    而排完序后,我们还需要把每一个数据的这个末尾字符剔除掉。

  • 处理数据能力太少,本题限制在100以内可行,对大于128组的数据的情况就行不通了。

循其本,我们分析题目后知道其实任务就是“给这些读入的数据增加一些“属性”,以使得任取其中两个数据的地址,我们就能知道它们哪个是先读入的,哪个是后读入的。”,那何不在定义data的时候就增加一个属性呢——存储着这是第几个读入的数据。

于是,我们修改原来的代码,重新定义data结构与cmp函数

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

typedef struct _data{
       char name[20];//存储名字
       char tel[20];//存储电话号码
       int number;//存储这是第几个读入的数据
    } data;
int cmp(const void *e1, const void *e2);

int main()
{
    data gather[105];
    int num;
    data tmp;
    scanf("%d", &num);

    for(int i=0;i<num;i++){
        scanf(" %s %s",gather[i].name,gather[i].tel); 
        gather[i].number = i + 1;           
    }
    qsort(gather,num,sizeof(data),cmp);
}

int cmp(const void *e1, const void *e2)
{
    int flag = strcmp(((data*)e1)->name,((data*)e2)->name);//字典序大小
    if(flag<0)return -1;//前者字典序小,位置不变
    if(flag==0)return ((data*)e1)->number-((data*)e2)->number;//两组数据的名字相同,比价“编号”
    return 1;//前者字典序大,交换位置    
}

此时可以把排序后的数据打印出来,发现确实如我们所愿。

接下来要对处理后的数据进行输出,由于针对名字相同的数据我们还要进行一些处理(舍弃或编号输出),所以我们可以把名字相同的这些数据当做一个输出单元,实际上这里用到了“队列”的想法,但并没有什么高级的,看着代码和注释完全可以理解:

data queue[105];//队列,可以理解为先把名字相同的数据按顺序存储到这里,然后集中输出
int queue_num = 0;//表示当前队列的元素个数
    for(int i=0;i<num;i++){//遍历排序后的数组中的元素
        if(!queue_num){//当前队列为空,将这个元素放到栈内
            queue[0] = gather[i];          
            queue_num = 1;
            continue;
        }
        
        if(strcmp(gather[i].name,queue[0].name)!=0){//这个元素的名字和队列内的名字不同
            if(queue_num==1)printf("%s %s\n",queue[0].name,queue[0].tel);//队列内只有一个元素,直接输出
            else {//队列内有多个元素,按题目要求对其编号输出
                printf("%s %s\n",queue[0].name,queue[0].tel);//首个元素没有编号
                for(int j=1;j<queue_num;j++){
                    printf("%s_%d %s\n",queue[j].name,j,queue[j].tel);
                }
            }
            queue[0] = gather[i];//队列内的首个元素变成当前这个元素
            queue_num = 1;//队列内元素个数置1
            continue;
        }
        if(strcmp(gather[i].name,queue[0].name)==0){//该数据和队列内数据的名字相同   
            int flag = 0;//记录该数据的电话号是否和队列内已有数据相同       
            for(int j=0;j<queue_num;j++){
                if(strcmp(queue[j].tel,gather[i].tel)==0){
                    flag = 1;//找到完全相同的数据
                    break;
                }
            }
            if(!flag){
                queue[shed_num++] = gather[i];//没有与之相同的数据,将其入队,并将记录队列内元素的变量加1          
            }
        }
    } 
	//全都遍历后,最后将队内剩余元素全部输出
    if(queue_num==1)printf("%s %s\n",queue[0].name,queue[0].tel);
        else {
            printf("%s %s\n",queue[0].name,queue[0].tel);
            for(int j=1;j<queue_num;j++){
                printf("%s_%d %s\n",queue[j].name,j,queue[j].tel);
            }
        }

最后还需要再对队内的元素输出一遍,这一点容易忽略,如果发现输出结果不对,可以看看打印出的结果少了什么,依此去分析出现的问题。

完整代码

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

typedef struct _data{
       char name[20];//存储名字
       char tel[20];//存储电话号码
       int number;//存储这是第几个读入的数据
    } data;
int cmp(const void *e1, const void *e2);

int main()
{
    data gather[105];
    int num;
    data tmp;
    scanf("%d", &num);

    for(int i=0;i<num;i++){
        scanf(" %s %s",gather[i].name,gather[i].tel); 
        gather[i].number = i + 1;           
    }
    qsort(gather,num,sizeof(data),cmp);
    
   data queue[105];//队列,可以理解为先把名字相同的数据按顺序存储到这里,然后集中输出
int queue_num = 0;//表示当前队列的元素个数
    for(int i=0;i<num;i++){//遍历排序后的数组中的元素
        if(!queue_num){//当前队列为空,将这个元素放到栈内
            queue[0] = gather[i];          
            queue_num = 1;
            continue;
        }
        
        if(strcmp(gather[i].name,queue[0].name)!=0){//这个元素的名字和队列内的名字不同
            if(queue_num==1)printf("%s %s\n",queue[0].name,queue[0].tel);//队列内只有一个元素,直接输出
            else {//队列内有多个元素,按题目要求对其编号输出
                printf("%s %s\n",queue[0].name,queue[0].tel);//首个元素没有编号
                for(int j=1;j<queue_num;j++){
                    printf("%s_%d %s\n",queue[j].name,j,queue[j].tel);
                }
            }
            queue[0] = gather[i];//队列内的首个元素变成当前这个元素
            queue_num = 1;//队列内元素个数置1
            continue;
        }
        if(strcmp(gather[i].name,queue[0].name)==0){//该数据和队列内数据的名字相同   
            int flag = 0;//记录该数据的电话号是否和队列内已有数据相同       
            for(int j=0;j<queue_num;j++){
                if(strcmp(queue[j].tel,gather[i].tel)==0){
                    flag = 1;//找到完全相同的数据
                    break;
                }
            }
            if(!flag){
                queue[shed_num++] = gather[i];//没有与之相同的数据,将其入队,并将记录队列内元素的变量加1          
            }
        }
    } 
	//全都遍历后,最后将队内剩余元素全部输出
    if(queue_num==1)printf("%s %s\n",queue[0].name,queue[0].tel);
        else {
            printf("%s %s\n",queue[0].name,queue[0].tel);
            for(int j=1;j<queue_num;j++){
                printf("%s_%d %s\n",queue[j].name,j,queue[j].tel);
            }
        }
}

int cmp(const void *e1, const void *e2)
{
    int flag = strcmp(((data*)e1)->name,((data*)e2)->name);//字典序大小
    if(flag<0)return -1;//前者字典序小,位置不变
    if(flag==0)return ((data*)e1)->number-((data*)e2)->number;//两组数据的名字相同,比价“编号”
    return 1;//前者字典序大,交换位置    
}

5. 小型图书管理系统

题目

问题描述

小明同学特别喜欢买书看书。由于书较多,摆放杂乱,找起来非常麻烦。这学期小明同学上了数据结构与程序设计课后,决定改变这种状况:用C开发一个小型图书管理系统。系统中包含的图书信息有:书名、作者、出版社、出版日期等。首先,图书管理系统对已有的书(原始书库,存放在一个文本文件中)按书名字典序进行(按书名中各字符的ASCII码值由小到大排序)摆放(即将原始无序的图书信息文件生成一个有序的文件,即新书库),以便查找。该管理系统可以对新书库中图书条目进行如下操作:
1.录入。新增书录入到书库中(即从输入中读入一条图书信息插入到已排序好的图按书文件相关位置处)
2.查找。按书名或书名中关键字信息在书库中查找相关图书信息,若有多本书,按字典序输出。
3.删除。输入书名或书名中关键字信息,从书库中查找到相关书并将其删除,并更新书库。

输入形式

原始的图书信息(原始书库)保存在当前目录下的books.txt中。
用户操作从控制台读入,首先输入操作功能序号(1代表录入操作,2代表查找操作,3代表删除操作,0代表将已更新的图书信息保存到书库中并退出程序),然后在下一行输入相应的操作信息(录入操作后要输入一条图书信息,查找和删除操作后只要输入书名或书名中部分信息)。程序执行过程中可以进行多次操作,直到退出(输入操作0)程序。
要求:
1、原始文件中的图书信息与录入的图书信息格式相同,每条图书信息都在一行上,包括书名(不超过50个字符)、作者(不超过20个字符)、出版社(不超过30个字符)和出版日期(不超过10个字符),只由英文字母和下划线组成,用一个空格分隔。图书信息总条数不会超过500.
2、下划线字符参加排序。
3、图书不会重名。

输出形式

进行录入和删除操作,系统会更新图书信息,但不会在控制台窗口显示任何信息。
进行查找操作后,将在控制台按书名字典序分行输出查找到的图书信息,书名占50个字符宽度,作者占20个字符宽度,出版社占30个字符宽度,出版日期占10个字符宽度,都靠左对齐输出。
最终按字典排序的图书信息保存在当前目录下的ordered.txt中,每条图书信息占一行,格式与查找输出的图书信息相同。

样例输入

假设books.txt中保存的原始图书信息为:
The_C_programming_language Kernighan Prentice_Hall 1988
Programming_in_C Yin_Bao_Lin China_Machine_Press 2013
Data_structures_and_Algorithm_Analysis_in_C Mark_Allen_Weiss Addison_Wesley 1997
ANSI_and_ISO_Standard_c Plauger Microsoft_Press 1992
Data_structures_and_program_design_in_C Robert_Kruse Pearson_Education 1997
Computer_network_architectures Anton_Meijer Computer_Science_Press 1983
C_programming_guidelines Thomas_Plum Prentice_Hall 1984
Data_structures_using_C Tenenbaum Prentice_Hall 1990
Operating_system_concepts Peterson Addison_Wesley 1983
Computer_networks_and_internets Douglas_E_Come Electronic_Industry 2017
用户控制台输入信息为:
1
Data_structures_and_C_programs Christopher Addison_Wesley 1988
2
structure
1
The_C_programming_tutor Leon_A_Wortman R_J_Brady 1984
2
rogram
3
rogramming
0

样例输出

用户输入“2 structure”后,控制台输出:

在这里插入图片描述

用户输入“2 rogram”后,控制台输出:

在这里插入图片描述

ordered.txt文件内容为:

在这里插入图片描述

样例说明

先读入books.txt中的10条图书信息,按照书名进行字典序排序;用户进行了五次操作,然后退出:第一次操作是插入了一条图书信息,这时有11条图书信息,按书名字典序排序为:
ANSI_and_ISO_Standard_c Plauger Microsoft_Press 1992
C_programming_guidelines Thomas_Plum Prentice_Hall 1984
Computer_network_architectures Anton_Meijer Computer_Science_Press 1983
Computer_networks_and_internets Douglas_E_Come Electronic_Industry 2017
Data_structures_and_Algorithm_Analysis_in_C Mark_Allen_Weiss Addison_Wesley 1997
Data_structures_and_C_programs Christopher Addison_Wesley 1988
Data_structures_and_program_design_in_C Robert_Kruse Pearson_Education 1997
Data_structures_using_C Tenenbaum Prentice_Hall 1990
Operating_system_concepts Peterson Addison_Wesley 1983
Programming_in_C Yin_Bao_Lin China_Machine_Press 2013
The_C_programming_language Kernighan Prentice_Hall 1988
第二次操作是查找书名包含structure的图书,有4本图书信息按照格式要求输出到屏幕;第三次操作又插入了一条图书信息,这时有12条图书信息;第四次操作查找书名包含rogram的图书,有6本图书信息按照格式要求输出到屏幕;第五次操作是删除书名包含rogramming的图书信息,有四条图书信息要删除,剩下八条图书信息;最后退出程序前将剩余的八条图书信息按照格式要求存储在ordered.txt文件中。

评分标准

该程序要求编写图书管理系统。提交程序文件名为books.c。

问题分析

本题看着挺唬人,实际上就是纸老虎,甚至没有前面几道题难度大。本题是大模拟,也没有什么需要特别注意的地方,只要顺着题目的要求来写就好。

完整代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int cmp(const void *a, const void *b);

typedef struct book{
    char book_name[55];
    char author[25];
    char press[35];
    char pubilsh_date[15];
} book;


int main()
{
    FILE *in = fopen("books.txt", "r");
    FILE *out = fopen("ordered.txt", "w");

    book lib[600];  // 图书结构数组

    int num = 0;  // 图书总数
    while(fscanf(in,"%s %s %s %s\n",lib[num].book_name,lib[num].author,lib[num].press,lib[num].pubilsh_date)!=EOF){       
        num++;
    }

    qsort(lib, num,sizeof(book),cmp);  // 先对图书顺序排序

    int op;
    char tofind[200];

    while(scanf("%d",&op)!=EOF){
        if(0==op)break;       
        else if(1==op){
            //新书读到最后一位,然后重新对数组排序
            scanf(" %s %s %s %s",lib[num].book_name,lib[num].author,lib[num].press,lib[num].pubilsh_date);
            num++;
            qsort(lib, num,sizeof(book),cmp);  
            // 这里也可以按照顺序读取插入,虽然效率可能高一点,但那样代码写的比较啰嗦,所以我们借用已经写好的qsort函数
        }else {
            scanf(" %s",tofind);
            if(2==op){
                for(int j=0;j<num;j++){
                    if(strstr(lib[j].book_name,tofind)!=NULL){  // 找到了
                        printf("%s %s %s %s\n",lib[j].book_name,lib[j].author,lib[j].press,lib[j].pubilsh_date);
                    }
                }
            }else {
                //  删除,我们对其置0,最后排序的时候这些会到最前面,我们不输出即可
                for(int j=0;j<num;j++){
                    if(strstr(lib[j].book_name,tofind)!=NULL){
                        memset(lib[j].book_name,0,sizeof(lib[j].book_name));                          
                    }
                }
            }
        }
    }

    for(int j=0;j<num;j++){
        if(lib[j].book_name[0]!=0)  // 该书没有被删除过
            fprintf(out,"%-50s%-20s%-30s%-10s\n",lib[j].book_name,lib[j].author,lib[j].press,lib[j].pubilsh_date);
    }
    fclose(in);
    fclose(out);
    return 0;   
}
int cmp(const void *a, const void *b)
{
    return strcmp(((book*)a)->book_name,((book*)b)->book_name);
}
  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值