精华内容
下载资源
问答
  • C/C++趣味编程经典100例详解

    万次阅读 多人点赞 2017-05-03 09:02:19
    C/C++语言经典、实用、趣味程序设计编程精解 1.绘制余弦曲线 在屏幕上用“*”显示0~360度的余弦函数cos(x)曲线 *问题分析与算法设计 如果在程序中使用数组的话,这个问题十分简单。但若规定不能使用数组,...

     

     

    更新记录  
    2019-08-27 28题 重写
       

    C/C++语言经典、实用、趣味程序设计编程百例精解 

    1.绘制余弦曲线 

    在屏幕上用“*”显示0~360度的余弦函数cos(x)曲线 
    *问题分析与算法设计
    如果在程序中使用数组的话,这个问题十分简单。但若规定不能使用数组,问题就变得不容易了。
    关键在于余弦曲线在0~360度的区间内,一行中要显示两个点,而对一般的显示器来说,只能按行输出,即:输出第一行信息后,只能向下一行输出,不能再返回到上一行。为了获得本文要求的图形就必须在一行中一次输出两个“*”。
    为了同时得到余弦函数cos(x)图形在一行上的两个点,考虑利用cos(x)的左右对称性。将屏幕的行方向定义为x,列方向定义为y,则0~180度的图形与180~360度的图形是左右对称的,若定义图形的总宽度为62列,计算出x行0~180度时y点的坐标m,那么在同一行与之对称的180~360度的y点的坐标就 应为62-m。程序中利用反余弦函数acos计算坐标(x,y)的对应关系。
    使用这种方法编出的程序短小精炼,体现了一定的技巧。 
    *程序说明与注释

    #include<stdio.h>
    #include<math.h>
    int main()
    {
    double y;
    int x,m;
    for(y=1;y>=-1;y-=0.1) /*y为列方向,值从1到-1,步长为0.1*/
    {
    m=acos(y)*10; /*计算出y对应的弧度m,乘以10为图形放大倍数*/
    for(x=1;x<m;x++) printf(" ");
    printf("*"); /*控制打印左侧的 * 号*/
    for(;x<62-m;x++)printf(" ");
    printf("*\n"); /*控制打印同一行中对称的右侧*号*/
    } 
    return 0; 
    }

    *思考题
    如何实现用“*”显示0~360度的sin(x)曲线。 
    在屏幕上显示0~360度的cos(x)曲线与直线f(x)=45*(y-1)+31的迭加图形。其中cos(x)图形用“*”表示,f(x)用“+”表示,在两个图形相交的点上则用f(x)图形的符号。 

     

     

    2.绘制余弦曲线和直线 

    *问题分析与算法设计
    本题可以在上题的基础上进行修改。图形迭加的关键是要在分别计算出同一行中两个图形的列方向点坐标后,正确判断相互的位置关系。为此,可以先判断图形的交点,再分别控制打印两个不同的图形。 
    *程序注释与说明

     

    #include<stdio.h>
    #include<math.h> 
    int main()
    {
    double y;
    int x,m,n,yy;
    for(yy=0;yy<=20;yy++) /*对于第一个y坐标进行计算并在一行中打印图形*/
    {
    y=0.1*yy; /*y:屏幕行方向坐标*/
    m=acos(1-y)*10; /*m: cos(x)曲线上y点对应的屏幕列坐标*/
    n=45*(y-1)+31; /*n: 直线上y点对应的列坐标*/
    for(x=0;x<=62;x++) /*x: 屏幕列方向坐标*/
    if(x==m&&x==n) printf("+"); /*直线与cos(x)相交时打印“+”*/
    else if(x==n) printf("+"); /*打印不相交时的直线图形*/
    else if(x==m||x==62-m) printf("*"); /*打印不相交时的cos(x)图形*/
    else printf(" "); /*其它情况打印空格*/
    printf("\n");
    } 
    return 0; 
    }



    *思考题
    如何实现sin(x)曲线与cos(x)曲线图形的同时显示。

     

    3.绘制圆

    在屏幕上用“*”画一个空心的圆 
    *问题分析与算法设计
    打印圆可利用图形的左右对称性。根据圆的方程:
    R*R=X*X+Y*Y
    可以算出圆上每一点行和列的对应关系。 
    *程序说明与注释

     

    #include<stdio.h>
    #include<math.h>
    int main()
    {
    double y;
    int x,m;
    for(y=10;y>=-10;y--)
    {
    m=2.5*sqrt(100-y*y); /*计算行y对应的列坐标m,2.5是屏幕纵横比调节系数因为屏幕的
    行距大于列距,不进行调节显示出来的将是椭圆*/
    for(x=1;x<30-m;x++) printf(" "); /*图形左侧空白控制*/
    printf("*"); /*圆的左侧*/
    for(;x<30+m;x++) printf(" "); /*图形的空心部分控制*/
    printf("*\n"); /*圆的右侧*/ 
    }
    return 0; 
    }

    *思考题
    实现函数y=x2的图形与圆的图形叠加显示 

     

    4.歌星大奖赛

    在歌星大奖赛中,有10个评委为参赛的选手打分,分数为1~100分。选手最后得分为:去掉一个最高分和一个最低分后其余8个分数的平均值。请编写一个程序实现。
    *问题分析与算法设计
    这个问题的算法十分简单,但是要注意在程序中判断最大、最小值的变量是如何赋值的。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int integer,i,max,min,sum;
    max=-32768; /*先假设当前的最大值max为C语言整型数的最小值*/
    min=32767; /*先假设当前的最小值min为C语言整型数的最大值*/
    sum=0; /*将求累加和变量的初值置为0*/
    for(i=1;i<=10;i++)
    {
    printf("Input number %d=",i);
    scanf("%d",&integer); /*输入评委的评分*/
    sum+=integer; /*计算总分*/
    if(integer>max)max=integer; /*通过比较筛选出其中的最高分*/
    if(integer<min)min=integer; /*通过比较筛选出其中的最低分*/
    }
    printf("Canceled max score:%d\nCanceled min score:%d\n",max,min);
    printf("Average score:%d\n",(sum-max-min)/8); /*输出结果*/
    } 



    *运行结果
    Input number1=90
    Input number2=91
    Input number3=93
    Input number4=94
    Input number5=90
    Input number6=99
    Input number7=97
    Input number8=92
    Input number9=91
    Input number10=95
    Canceled max score:99
    Canceled min score:90
    Average score:92 
    *思考题
    题目条件不变,但考虑同时对评委评分进行裁判,即在10个评委中找出最公平(即评分最接返平均分)和最不公平(即与平均分的差距最大)的评委,程序应该怎样实现?

     

    5.求最大数

    问555555的约数中最大的三位数是多少?
    *问题分析与算法设计
    根据约数的定义,对于一个整数N,除去1和它自身外,凡能整除N的数即为N的约数。因此,最简单的方法是用2到N-1之间的所有数去除N,即可求出N的全部约数。本题只要求取约数中最大的三位数,则其取值范围可限制在100到999之间。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    long i;
    int j;
    printf("Please input number:");
    scanf("%ld",&i);
    for(j=999;j>=100;j–)
    if(i%j==0)
    {
    printf("The max factor with 3 digits in %ld is:%d,\n",i,j);
    break;
    }
    }



    *运行结果
    输入:555555
    输出:The max factor with 3 digits in 555555 is:777 

     

    6.高次方数的尾数

    求13的13次方的最后三位数
    *问题分析与算法设计
    解本题最直接的方法是:将13累乘13次方截取最后三位即可。
    但是由于计算机所能表示的整数范围有限,用这种“正确”的算法不可能得到正确的结果。事实上,题目仅要求最后三位的值,完全没有必要求13的13次方的完整结果。
    研究乘法的规律发现:乘积的最后三位的值只与乘数和被乘数的后三位有关,与乘数和被乘数的高位无关。利用这一规律,可以大大简化程序。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int i,x,y,last=1; /*变量last保存求X的Y次方过程中的部分乘积的后三位*/
    printf("Input X and Y(X**Y):");
    scanf("%d**%d",&x,&y);
    for(i=1;i<=y;i++) /*X自乘Y次*/
    last=last*x%1000; /*将last乘X后对1000取模,即求积的后三位*/
    printf("The last 3 digits of %d**%d is:%d\n",x,y,last%1000); /*打印结果*/
    }



    *运行结果
    Input X and Y(X**Y):13**13
    The last 3 digits of 13**13 is:253
    Input X and Y(X**Y):13**20
    The last 3 digits of 13**20 is:801

     

    7.阶乘尾数零的个数

    100!的尾数有多少个零? 
     
    *问题分析与算法设计
      可以设想:先求出100!的值,然后数一下末尾有多少个零。事实上,与上题一样,由于计算机所能表示的整数范围有限,这是不可能的。
       为了解决这个问题,必须首先从数学上分析在100!结果值的末尾产生零的条件。不难看出:一个整数若含有一个因子5,则必然会在求100!时产生一个零。因此问题转化为求1到100这100个整数中包含了多少个因子5。若整数N能被25整除,则N包含2个因子5;若整数N能被5整除,则N包含1个因子5。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int a,count =0;
    for(a=5;a<=100;a+=5) //循环从5开始,以5的倍数为步长,考察整数
    {
    ++count; //若为5的倍数,计数器加1
    if(!(a%25)) ++count; //若为25的倍数,计数器再加1
    }
    printf("The number of 0 in the end of 100! is: %d.\n",count); //打印结果
    return 0; 
    }



    *运行结果
    The number of 0 in the end of 100! is: 24. 
    *问题进一步讨论 
    本题的求解程序是正确的,但是存在明显的缺点。程序中判断整数N包含多少个因子5的方法是与程序中的100有关的,若题目中的100改为1000,则就要修改程序中求因子5的数目的算法了。 
    *思考题 
    修改程序中求因子5的数目的算法,使程序可以求出任意N!的末尾有多少个零。 

     

    8.借书方案知多少

    小明有五本新书,要借给A,B,C三位小朋友,若每人每次只能借一本,则可以有多少种不同的借法?
    *问题分析与算法设计
    本问题实际上是一个排列问题,即求从5个中取3个进行排列的方法的总数。首先对五本书从1至5进行编号,然后使用穷举的方法。假设三个人分别借这五本书中的一本,当三个人所借的书的编号都不相同时,就是满足题意的一种借阅方法。
    *程序说明与注释

     

    int main()
    {
    int a,b,c,count=0;
    printf("There are diffrent methods for XM to distribute books to 3 readers:\n");
    for(a=1;a<=5;a++) /*穷举第一个人借5本书中的1本的全部情况*/
    for(b=1;b<=5;b++) /*穷举第二个人借5本书中的一本的全部情况*/
    for(c=1;a!=b&&c<=5;c++) /*当前两个人借不同的书时,穷举第三个人借5本书
    中的1本的全部情况*/
    if(c!=a&&c!=b) /*判断第三人与前两个人借的书是否不同*/
    printf(count%8?"%2d:%d,%d,%d ":"%2d:%d,%d,%d\n ",++count,a,b,c);
    /*打印可能的借阅方法*/
    }



    *运行结果
    There are diffrent methods for XM to distribute books to 3 readers:
    1: 1,2,3 2: 1,2,4 3: 1,2,5 4: 1,3,2 5: 1,3,4
    6: 1,3,5 7: 1,4,2 8: 1,4,3 9: 1,4,5 10:1,5,2
    11:1,5,3 12:1,5,4 13:2,1,3 14:2,1,4 15:2,1,5
    16:2,3,1 17:2,3,4 18:2,3,5 19:2,4,1 20:2,4,3
    21:2,4,5 22:2,5,1 23:2,5,3 24:2,5,4 25:3,1,2
    26:3,1,4 27:3,1,5 28:3,2,1 29:3,2,4 30:3,2,5
    31:3,4,1 32:3,4,2 33:3,4,5 34:3,5,1 35:3,5,2
    36:3,5,4 37:4,1,2 38:4,1,3 39:4,1,5 40:4,2,1
    41:4,2,3 42:4,2,5 43:4,3,1 44:4,3,2 45:4,3,5
    46:4,5,1 47:4,5,2 48:4,5,3 49:5,1,2 50:5,1,3
    51:5,1,4 52:5,2,1 53:5,2,3 54:5,2,4 55:5,3,1
    56:5,3,2 57:5,3,4 58:5,4,1 59:5,4,2 60:5,4,3

     

    9.杨辉三角形

    在屏幕上显示杨辉三角形
                1
              1 1
            1 2 1
           1 3 3 1
          1 4 6 4 1
       1 5 10 10 5 1
    ………………………………..
    *问题分析与算法设计
    杨辉三角形中的数,正是(x+y)的N次方幂展开式各项的系数。本题作为程序设计中具有代表性的题目,求解的方法很多,这里仅给出一种。
    从杨辉三角形的特点出发,可以总结出:
    1)第N行有N+1个值(设起始行为第0行)
    2)对于第N行的第J个值:(N>=2)
    当J=1或J=N+1时:其值为1
    J!=1且J!=N+1时:其值为第N-1行的第J-1个值与第N-1行第J个值
    之和
    将这些特点提炼成数学公式可表示为:
    1 x=1或x=N+1
    c(x,y)= 
    c(x-1,y-1)+c(x-1,y) 其它
    本程序应是根据以上递归的数学表达式编制的。
    *程序说明与注释

     

    #include<stdio.h>
    int c(int x, int y) /*求杨辉三角形中第x行第y列的值*/
    {
    	int z;
    	if ((y == 1) || (y == x + 1)) return 1; /*若为x行的第1或第x+1列,则输出1*/
    	z = c(x - 1, y - 1) + c(x - 1, y); /*否则,其值为前一行中第y-1列与第y列值之和*/
    	return z;
    }
    int main()
    {
    	int i, j, n = 13;
    	printf("N=");
    	while (n>12)
    		scanf("%d", &n); /*控制输入正确的值以保证屏幕显示的图形正确*/
    	for (i = 0; i <= n; i++) /*控制输出N行*/
    	{
    		for (j = 0; j<24 - 2 * i; j++) printf(" "); /*控制输出第i行前面的空格*/
    		for (j = 1; j<i + 2; j++) printf("%4d", c(i, j)); /*输出第i行的第j个值*/
    		printf("\n");
    	}
    }



    *思考题
    自行设计一种实现杨辉三角形的方法

     

    10.数制转换

    将任一整数转换为二进制形式
    *问题分析与算法设计
    将十进制整数转换为二进制的方法很多,这里介绍的实现方法利用了C语言能够对位进行操作的特点。对于C语言来说,一个整数在计算机内就是以二进制的形式存储的,所以没有必要再将一个整数经过一系列的运算转换为二进制形式,只要将整数在内存中的二进制表示输出即可。
    *程序说明与注释

     

    #include<stdio.h>
    void printb(int,int);
    int main()
    {
    int x;printf("Input number:");
    scanf("%d",&x);
    printf("number of decimal form:%d\n",x);
    printf(" it's binary form:");
    printb(x,sizeof(int)*8); /*x:整数 sizeof(int):int型在内存中所占的字节数
    sizeof(int)*8:int型对应的位数*/
    putchar('\n');
    }
    void printb(int x,int n)
    {
    if(n>0)
    {
    putchar('0'+((unsigned)(x&(1<<(n-1)))>>(n-1))); /*输出第n位*/
    printb(x,n-1); /*归调用,输出x的后n-1位*/
    }
    }



    *运行结果
    输入:8
    输出:
    number of decimal form:8
    it's bunary form:0000000000001000
    输入:-8
    输出:number of decimal form:-8
    it's binary form:1111111111111000
    输入:32767
    输出:number of decimal form:32767
    it's binary form:0111111111111111
    输入:-32768
    输出:number of decimal form:-32768
    it's binary form:1000000000000000
    输入:128
    输出:number of decimal form:128
    it's binary form:0000000010000000 
    *问题的进一步讨论
    充分利用C语言可以对位进行操作的特点,可以编写许多其它高级语言不便于编写甚至根本无法编写的程序。位操作是C语言的一大特点,在深入学习C语言的过程中应力求很好掌握。
    程序中使用的位运算方法不是最佳的,也可以不用递归操作,大家可以自行对程序进行优化。
    *思考题
    将任意正整数转换为四进制或八进制数


    C/C++语言经典、实用、趣味程序设计编程百例精解(2) 

     

    11.打鱼还是晒网 

    中国有句俗语叫“三天打鱼两天晒网”。某人从1990年1月1日起开始“三天打鱼两天晒网”,问这个人在以后的某一天中是“打鱼”还是“晒网”。
    *问题分析与算法设计
    根据题意可以将解题过程分为三步:
    1)计算从1990年1月1日开始至指定日期共有多少天;
    2)由于“打鱼”和“晒网”的周期为5天,所以将计算出的天数用5去除;
    3)根据余数判断他是在“打鱼”还是在“晒网”;
    若 余数为1,2,3,则他是在“打鱼”
    否则 是在“晒网”
    在这三步中,关键是第一步。求从1990年1月1日至指定日期有多少天,要判断经历年份中是否有闰年,二月为29天,平年为28天。闰年的方法可以用伪语句描述如下:
    如果 ((年能被4除尽 且 不能被100除尽)或 能被400除尽)
    则 该年是闰年;
    否则 不是闰年。
    C语言中判断能否整除可以使用求余运算(即求模)
    *程序说明与注释

     

    #include<stdio.h>
    int days(struct date day);
    struct date{
    int year;
    int month;
    int day;
    };
    int main()
    {
    struct date today,term;
    int yearday,year,day;
    printf("Enter year/month/day:");
    scanf("%d%d%d",&today.year,&today.month,&today.day); /*输入日期*/
    term.month=12; /*设置变量的初始值:月*/
    term.day=31; /*设置变量的初始值:日*/
    for(yearday=0,year=1990;year<today.year;year++)
    {
    term.year=year;
    yearday+=days(term); /*计算从1990年至指定年的前一年共有多少天*/
    }
    yearday+=days(today); /*加上指定年中到指定日期的天数*/
    day=yearday%5; /*求余数*/
    if(day>0&&day<4) printf("he was fishing at that day.\n"); /*打印结果*/
    else printf("He was sleeping at that day.\n");
    }
    int days(struct date day)
    {
    static int day_tab[2][13]=
    {{0,31,28,31,30,31,30,31,31,30,31,30,31,}, /*平均每月的天数*/
    {0,31,29,31,30,31,30,31,31,30,31,30,31,},
    };
    int i,lp;
    lp=day.year%4==0&&day.year%100!=0||day.year%400==0;
    /*判定year为闰年还是平年,lp=0为平年,非0为闰年*/
    for(i=1;i<day.month;i++) /*计算本年中自1月1日起的天数*/
    day.day+=day_tab[lp][i];
    return day.day;
    }

    *运行结果
    Enter year/month/day:1991 10 25
    He was fishing at day.
    Enter year/month/day:1992 10 25
    He was sleeping at day.
    Enter year/month/day:1993 10 25
    He was sleeping at day.
    *思考题
    请打印出任意年份的日历

     

    12.抓交通肇事犯

    一辆卡车违反交通规则,撞人后逃跑。现场有三人目击事件,但都没有记住车号,只记下车号的一些特征。甲说:牌照的前两位数字是相同的;乙说:牌照的后两位数字是相同的,但与前两位不同; 丙是数学家,他说:四位的车号刚好是一个整数的平方。请根据以上线索求出车号。
    *问题分析与算法设计
    按照题目的要求造出一个前两位数相同、后两位数相同且相互间又不同的整数,然后判断该整数是否是另一个整数的平方。
    *程序说明与注释

     

    #include<stdio.h>
    #include<math.h>
    int main()
    {
    int i,j,k,c;
    for(i=1;i<=9;i++) /*i:车号前二位的取值*/
    for(j=0;j<=9;j++) /*j:车号后二位的取值*/
    if(i!=j) /*判断二位数字是否相异*/
    {
    k=i*1000+i*100+j*10+j; /*计算出可能的整数*/
    for(c=31;c*c<k;c++); /*判断该数是否为另一整数的平方*/
    if(c*c==k) printf("Lorry–No. is %d.\n",k); /*若是,打印结果*/
    }
    }



    *运行结果
    Lorry _No.is 7744

     

    13.该存多少钱

    假设银行一年整存零取的月息为0.63%。现在某人手中有一笔钱,他打算在今后的五年中的年底取出1000元,到第五年时刚好取完,请算出他存钱时应存入多少。
    *问题分析与算法设计
    分析存钱和取钱的过程,可以采用倒推的方法。若第五年年底连本带息要取1000元,则要先求出第五年年初银行存款的钱数:
    第五年初存款=1000/(1+12*0.0063)
    依次类推可以求出第四年、第三年……的年初银行存款的钱数:
    第四年年初存款=(第五年年初存款+1000)/(1+12*0.0063)
    第三年年初存款=(第四年年初存款+1000)/(1+12*0.0063)
    第二年年初存款=(第三年年初存款+1000)/(1+12*0.0063)
    第一年年初存款=(第二年年初存款+1000)/(1+12*0.0063)
    通过以上过程就可以很容易地求出第一年年初要存入多少钱。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int i;
    float total=0;
    for(i=0;i<5;i++) /*i 为年数,取值为0~4年*/
    total=(total+1000)/(1+0.0063*12); /*累计算出年初存款数额,第五次的计算
    结果即为题解*/
    printf("He must save %.2f at first.\n",total);
    }



    *运行结果
    He must save 4039.44 at first 

     

    14.怎样存钱利最大

    假设银行整存整取存款不同期限的月息利率分别为:
    0.63% 期限=1年
    0.66% 期限=2年
    0.69% 期限=3年
    0.75% 期限=5年
    0.84% 期限=8年
    利息=本金*月息利率*12*存款年限。
    现在某人手中有2000元钱,请通过计算选择一种存钱方案,使得钱存入银行20年后得到的利息最多(假定银行对超过存款期限的那一部分时间不付利息)。
    *问题分析与算法设计
    为了得到最多的利息,存入银行的钱应在到期时马上取出来,然后立刻将原来的本金和利息加起来再作为新的本金存入银行,这样不断地滚动直到满20年为止,由于存款的利率不同,所以不同的存款方法(年限)存20年得到的利息是不一样的。
    分析题意,设2000元存20年,其中1年存i1次,2年存i2次,3年存i3次,5年存i5次,8年存i8次,则到期时存款人应得到的本利合计为:
    2000*(1+rate1)i1*(1+rate2)i2*(1+rate3)i3*(1+rate5)i5*(1+rate8)i8
    其中rateN为对应存款年限的利率。根据题意还可得到以下限制条件:
    0<=i8<=2
    0<=i5<=(20-8*i8)/5
    0<=i3<=(20-8*i8-5*i5)/3
    0<=i2<=(20-8*i8-5*i5-3*i3)/2
    0<=i1=20-8*i8-5*i5-3*i3-2*i2
    可以用穷举法穷举所有的i8、i5、i3、i2和i1的组合,代入求本利的公式计算出最大值,就是最佳存款方案。
    *程序说明与注释

     

    #include<stdio.h>
    #include<math.h>
    int main()
    {
    int i8,i5,i3,i2,i1,n8,n5,n3,n2,n1;
    float max=0,term;
    for(i8=0;i8<3;i8++) /*穷举所有可能的存款方式*/
    for(i5=0;i5<=(20-8*i8)/5;i5++)
    for(i3=0;i3<=(20-8*i8-5*i5)/3;i3++)
    for(i2=0;i2<=(20-8*i8-5*i5-3*i3)/2;i2++)
    {
    i1=20-8*i8-5*i5-3*i3-2*i2;
    term=2000.0*pow((double)(1+0.0063*12),(double)i1)
    *pow((double)(1+2*0.0063*12),(double)i2)
    *pow((double)(1+3*0.0069*12),(double)i3)
    *pow((double)(1+5*0.0075*12),(double)i5)
    *pow((double)(1+8*0.0084*12),(double)i8);
    /*计算到期时的本利合计*/
    if(term>max)
    {
    max=term;n1=i1;n2=i2;n3=i3;n5=i5;n8=i8;
    }
    }
    printf("For maxinum profit,he should so save his money in a bank:\n");
    printf(" made fixed deposit for 8 year: %d times\n",n8);
    printf(" made fixed deposit for 5 year: %d times\n",n5);
    printf(" made fixed deposit for 3 year: %d times\n",n3);
    printf(" made fixed deposit for 2 year: %d times\n",n2);
    printf(" made fixed deposit for 1 year: %d times\n",n1);
    printf(" Toal: %.2f\n",max);
    /*输出存款方式*/
    }



    *运行结果
    For maxinum profit,he should so save his money in a bank:
    made fixed deposit for 8 year: 0times
    made fixed deposit for 5 year: 4times
    made fixed deposit for 3 year: 0times
    made fixed deposit for 2 year: 0times
    made fixed deposit for 1 year: 0times
    Total:8841.01
    可见最佳的存款方案为连续四次存5年期。
    *思考题
    某单位对职工出售住房,每套为2万元。买房付款的方法是:
    一次交清,优惠20%
    从第一年开始,每年年初分期付款:
    5年交清,优惠50%;
    10年交清,优惠10%;
    20年交清,没有优惠。
    现在有人手中正好有2万元,若假定在今后20年中物价和银行利率均保持不变,问他应当选择哪种付款方式可以使应付的钱最少?

     

    15.捕鱼和分鱼

    A、B、C、D、E五个人在某天夜里合伙去捕鱼,到第二天凌晨时都疲惫不堪,于是各自找地方睡觉。日上三杆,A第一个醒来,他将鱼分为五份,把多余的一条鱼扔掉,拿走自己的一份。B第二个醒来,也将鱼分为五份,把多余的一条鱼扔掉,保持走自己的一份。C、D、E依次醒来,也按同样的方法拿走鱼。问他们合伙至少捕了多少条鱼?
    *问题分析与算法设计
    根据题意,总计将所有的鱼进行了五次平均分配,每次分配时的策略是相同的,即扔掉一条鱼后剩下的鱼正好分成五份,然后拿走自己的一份,余下其它的四份。
    假定鱼的总数为X,则X可以按照题目的要求进行五次分配:X-1后可被5整除,余下的鱼为4*(X-1)、5。若X满足上述要求,则X就是题目的解。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int n,i,x,flag=1; /*flag:控制标记*/
    for(n=6;flag;n++) /*采用试探的方法。令试探值n逐步加大*/
    {
    for(x=n,i=1&&flag;i<=5;i++)
    if((x-1)%5==0) x=4*(x-1)/5;
    else flag=0; /*若不能分配则置标记falg=0退出分配过程*/
    if(flag) break; /*若分配过程正常结束则找到结果退出试探的过程*/
    else flag=1; /*否则继续试探下一个数*/
    }
    printf("Total number of fish catched=%d\n",n); /*输出结果*/
    }



    *运行结果
    Total number of fish catched = 3121
    *问题的进一步讨论
    程序采用试探法,试探的初值为6,每次试探的步长为1。这是过分保守的做法。可以在进一步分析题目的基础上修改此值,增大试探的步长值,以减少试探次数。
    *思考题
    请使用其它的方法求解本题。 

     

    16.出售金鱼

     
    买卖提将养的一缸金鱼分五次出售系统上一次卖出全部的一半加二分之一条;第二次卖出余下的三分之一加三分之一条;第三次卖出余下的四分之一加四分之一条;第四次卖出余下的五分之一加五分之一条;最后卖出余下的11条。问原来的鱼缸中共有几条金鱼?
    *问题分析与算法设计
    题目中所有的鱼是分五次出售的,每次卖出的策略相同;第j次卖剩下的(j+1)分之一再加1/(j+1)条。第五次将第四次余下的11条全卖了。
    假定第j次鱼的总数为X,则第j次留下:
    x-(x+1)/(j+1)
    当第四次出售完毕时,应该剩下11条。若X满足上述要求,则X就是题目的解。
    应当注意的是:"(x+1)/(j+1)"应满足整除条件。试探X的初值可以从23开始,试探的步长为2,因为X的值一定为奇数。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int i,j,n=0,x; /*n为标志变量*/
    for(i=23;n==0;i+=2) /*控制试探的步长和过程*/
    {
    for(j=1,x=i;j<=4&&x>=11;j++) /*完成出售四次的操作*/
    if((x+1)%(j+1)==0) /*若满足整除条件则进行实际的出售操作*/
    x-=(x+1)/(j+1);
    else {x=0;break;} /*否则停止计算过程*/
    if(j==5&&x==11) /*若第四次余下11条则满足题意*/
    {
    printf("There are %d fishes at first.\n",i); /*输出结果*/
    n=1; /*控制退出试探过程*/
    }
    }
    }



    *运行结果
    There are 59 fishes at first.
    *思考题
    日本著名数学游戏专家中村义作教授提出这样一个问题:父亲将2520个桔子分给六个儿子。分完后父亲说:“老大将分给你的桔子的1/8给老二;老二拿到后连同原先的桔子分1/7给老三;老三拿到后连同原先的桔子分1/6给老四;老四拿到后连同原先的桔子分1/5给老五;老五拿到后连同原先的桔子分1/4给老六;老六拿到后连同原先的桔子分1/3给老大”。结果大家手中的桔子正好一样多。问六兄弟原来手中各有多少桔子?

     

    17.平分七筐鱼

    甲、乙、丙三位鱼夫出海打鱼,他们随船带了21只箩筐。当晚返航时,他们发现有七筐装满了鱼,还有七筐装了半筐鱼,另外七筐则是空的,由于他们没有秤,只好通过目测认为七个满筐鱼的重量是相等的,7个半筐鱼的重量是相等的。在不将鱼倒出来的前提下,怎样将鱼和筐平分为三份?
    *问题分析与算法设计
    根据题意可以知道:每个人应分得七个箩筐,其中有3.5筐鱼。采用一个3*3的数组a来表示三个人分到的东西。其中每个人对应数组a的一行,数组的第0列放分到的鱼的整筐数,数组的第1列放分到的半筐数,数组的第2列放分到的空筐数。由题目可以推出:
    。数组的每行或每列的元素之和都为7;
    。对数组的行来说,满筐数加半筐数=3.5;
    。每个人所得的满筐数不能超过3筐;
    。每个人都必须至少有1 个半筐,且半筐数一定为奇数
    对于找到的某种分鱼方案,三个人谁拿哪一份都是相同的,为了避免出现重复的分配方案,可以规定:第二个人的满筐数等于第一个人的满筐数;第二个人的半筐数大于等于第一个人的半筐数。
    *程序说明与注释

     

    #include<stdio.h>
    int a[3][3],count;
    int main()
    {
    int i,j,k,m,n,flag;
    printf("It exists possible distribtion plans:\n");
    for(i=0;i<=3;i++) /*试探第一个人满筐a[0][0]的值,满筐数不能>3*/
    {
    a[0][0]=i;
    for(j=i;j<=7-i&&j<=3;j++) /*试探第二个人满筐a[1][0]的值,满筐数不能>3*/
    {
    a[1][0]=j;
    if((a[2][0]=7-j-a[0][0])>3)continue; /*第三个人满筐数不能>3*/
    if(a[2][0]<a[1][0])break; /*要求后一个人分的满筐数>=前一个人,以排除重复情况*/
    for(k=1;k<=5;k+=2) /*试探半筐a[0][1]的值,半筐数为奇数*/
    {
    a[0][1]=k;
    for(m=1;m<7-k;m+=2) /*试探 半筐a[1][1]的值,半筐数为奇数*/
    {
    a[1][1]=m;
    a[2][1]=7-k-m;
    for(flag=1,n=0;flag&&n<3;n++)
    /*判断每个人分到的鱼是 3.5筐,flag为满足题意的标记变量*/
    if(a[n][0]+a[n][1]<7&&a[n][0]*2+a[n][1]==7)
    a[n][2]=7-a[n][0]-a[n][1]; /*计算应得到的空筐数量*/
    else flag=0; /*不符合题意则置标记为0*/
    if(flag)
    {
    printf("No.%d Full basket Semi–basket Empty\n",++count);
    for(n=0;n<3;n++)
    printf(" fisher %c: %d %d %d\n",
    'A'+n,a[n][0],a[n][1],a[n][2]);
    }
    }
    }
    }
    }
    }



    * 运行结果 
    It exists possible distribution plans:
    No.1 Full basket Semi–basket Empty
    fisher A: 1 5 1
    fisher B: 3 1 3
    fisher C: 3 1 3
    No.2 Full basket Semi–basket Empty
    fisher A: 2 3 2
    fisher B: 2 3 2
    fisher C: 3 1 3
    *思考题
    晏会上数学家出了一道难题:假定桌子上有三瓶啤酒,癣瓶子中的酒分给几个人喝,但喝各瓶酒的人数是不一样的。不过其中有一个人喝了每一瓶中的酒,且加起来刚好是一瓶,请问喝这三瓶酒的各有多少人?
    (答案:喝三瓶酒的人数分别是2人、3人和6人) 
     

     

    18.有限5位数

    个位数为6且能被3整除的五位数共有多少?
    *题目分析与算法设计
    根据题意可知,满足条件的五位数的选择范围是10006、10016。。。99996。可设基础数i=1000,通过计算i*10+6即可得到欲选的数(i的变化范围是1000~999),再判断该数能否被3整除。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    long int i;
    int count=0; /*count:统计满足条件的五位数的个数*/
    for(i=1000;i<9999;i++)
    if(!((i*10+6)%3)) /*判断所选的数能否被3整除*/
    count++; /*若满足条件则计数*/
    printf("count=%d\n",count);
    }



    *运行结果
    count=2999
     
    *思考题
    求100到1000之间有多少个其数字之和为5的整数。
    (答案:104,113,122,131,140,203,212,221,230,302,311,320,401,410,500) 
    19.8除不尽的自然数
    一个自然数被8除余1,所得的商被8除也余1,再将第二次的商被8除后余7,最后得到一个商为a。又知这个自然数被17除余4,所得的商被17除余15,最后得到一个商是a的2倍。求这个自然数。
    *问题分析与算法设计
    根据题意,可设最后的商为i(i从0开始取值),用逆推法可以列出关系式:
    (((i*8+7)*8)+1)*8+1=((2*i*17)+15)*18+4
    再用试探法求出商i的值。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int i;
    for(i=0;;i++) /*试探商的值*/
    if(((i*8+7)*8+1)*8+1==(34*i+15)*17+4)
    { /*逆推判断所取得的当前i值是否满足关系式*/
    /*若满足则输出结果*/
    printf("The required number is: %d\n",(34*i+15)*17+4);
    break; /*退出循环*/
    }
    }



    *运行结果
    The required number is:1993 

     

    20.一个奇异的三位数

     
    一个自然数的七进制表达式是一个三位数,而这个自然数的九进制表示也是一个三位数,且这两个三位数的数码正好相反,求这个三位数。
    *问题分析与算法设计
    根据题意可知,七进制和九进制表示的这全自然数的每一位一定小于7,可设其七进制数形式为kji(i、j、k的取值分别为1~6),然后设其九进制表示形式为ijk。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int i,j,k;
    for(i=1;i<7;i++)
    for(j=0;j<7;j++)
    for(k=1;k<7;k++)
    if(i*9*9+j*9+k==i+j*7+k*7*7)
    {
    printf("The special number with 3 digits is:");
    printf("%d%d%d(7)=%d%d%d(9)=%d(10)\n",k,j,i,i,j,k,i*9*9+j*9+k);
    }
    }



    *运行结果
    The special number with 3 digits is:503(7)=305(9)=248(10)


    C/C++语言经典、实用、趣味程序设计编程百例精解(3) 


     

    21.位反序数

    设N是一个四位数,它的9倍恰好是其反序数,求N。反序数就是将整数的数字倒过来形成的整数。例如:1234的反序数是4321。
    *问题分析与算法设计
    可设整数N的千、百、十、个位为i、j、k、l,其取值均为0~9,则满足关系式:
    (i*103+j*102+10*k+l)*9=(l*103+k*102+10*j+i)
    的i、j、k、l即构成N。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int i;
    for(i=1002;i<1111;i++) /*穷举四位数可能的值*/
    if(i%10*1000+i/10%10*100+i/100%10*10+i/1000==i*9)
    /*判断反序数是否是原整数的9倍*/
    printf("The number satisfied stats condition is: %d\n",i);
    /*若是则输出*/
    }



    *运行结果
    The number satisfied states condition is:1089 

     

    22.求车速

    一辆以固定速度行驶的汽车,司机在上午10点看到里程表上的读数是一个对称数(即这个数从左向右读和从右向左读是完全一样的),为95859。两小时后里程表上出现了一个新的对称数。问该车的速度是多少?新的对称数是多少?
    *问题分析与算法设计
    根据题意,设所求对称数为i,其初值为95589,对其依次递增取值,将i值的每一位分解后与其对称位置上的数进行比较,若每个对称位置上的数皆相等,则可判定i即为所求的对称数。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int t,a[5]; /*数组a存放分解的数字位*/
    long int k,i;
    for(i=95860;;i++) /*以95860为初值,循环试探*/
    {
    for(t=0,k=100000;k>=10;t++) /*从高到低分解所取i值的每位数*/
    { /* 字,依次存放于a[0]~a[5]中*/
    a[t]=(i%k)/(k/10); 
    k/=10;
    }
    if((a[0]==a[4])&&(a[1]==a[3]))
    {
    printf("The new symmetrical number kelometers is:%d%d%d%d%d\n",
    a[0],a[1],a[2],a[3],a[4]);
    printf("The velocity of the car is: %.2f\n",(i-95859)/2.0);
    break;
    }
    }
    }



    *运行结果
    The new symmetrical number kelometers is:95959.
    The velocity of the car is:50.00
    *思考题
    将一个数的数码倒过来所得到的新数叫原数的反序数。如果一个数等于它的反序数,则称它为对称数。求不超过1993的最大的二进制的对称数。
     

     

    23.由两个平方三位数获得三个平方二位数

     
    已知两个平方三位数abc和xyz,其中a、b、c、x、y、z未必是不同的;而ax、by、cz是三个平方二位数。请编程求三位数abc和xyz。
    *问题分析与算法设计
    任取两个平方三位数n和n1,将n从高向低分解为a、b、c,将n1从高到低分解为x、y、z。判断ax、by、cz是否均为完全平方数。
    *程序说明与注释

     

    #include<stdio.h>
    #include<math.h>
    void f(int n,float* s);
    int main()
    {
    int i,t;
    float a[3],b[3];
    print("The possible perfect squares combinations are:\n");
    for(i=11;i<=31;++i) //穷举平方三位数的取值范围
    for(t=11;t<=31;++t)
    {
    f(i*i,a); //分解平方三位数的各位,每位数字分别存入数组中
    f(t*t,b);
    if(sqrt(a[0]*10+b[0]) == (int)sqrt(a[0]*10+b[0]) 
    && sqrt(a[1]*10+b[1]) == (int)sqrt(a[1]*10+b[1])
    && sqrt(a[2]*10+b[2]) == (int)sqrt(a[2]*10+b[2]) )
    {
    printf("%d and %d\n,i*i,t*t"); //若三个新的数均是完全平方数,则输出
    }
    }
    } 
     
    /* ———————————————-
    分解三位数n的各位数字,将各个数字从高到低依次存入指针s所指向的数组中
    ————————————————*/
    void f(int n,float* s)
    {
    int k;
    for(k=1000;k>=10;++s)
    {
    *s = (n%k) /(k/10);
    k /=10;
    }
    }



    *运行结果
    The possible perfect squares combinations are:
    400 and 900
    841 and 196
    *思考题
    求这样一个三位数,该三位数等于其每位数字的阶乘之和。
    即 abc = a! + b! + c!
    (正确结果:145 = 1! + 4! +5!) 
     

     

    24.阿姆斯特朗数

     
    如果一个正整数等于其各个数字的立方和,则称该数为阿姆斯特朗数(亦称为自恋性数)。
    如 407=43+03+73就是一个阿姆斯特朗数。试编程求1000以内的所有阿姆斯特朗数。
    *问题分析与算法设计
    可采用穷举法,依次取1000以内的各数(设为i),将i的各位数字分解后,据阿姆斯特朗数的性质进行计算和判断。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int i,t,k,a[3];
    printf("There are follwing Armstrong number smaller than 1000:\n");
    for(i=2;i<1000;i++) /*穷举要判定的数i的取值范围2~1000*/
    {
    for(t=0,k=1000;k>=10;t++) /*截取整数i的各位(从高向低位)*/
    {
    a[t]=(i%k)/(k/10); /*分别赋于a[0]~a[2}*/
    k/=10;
    }
    if(a[0]*a[0]*a[0]+a[1]*a[1]*a[1]+a[2]*a[2]*a[2]==i)
    /*判断i是否为阿姆斯特朗数*/
    printf("%5d",i); /*若满足条件,则输出*/
    }
    printf("\n");
    }



    *运行结果
    There are following Armstrong number smaller than 1000:
    153 370 371 407 
    25.完全数
     
    如果一个数恰好等于它的因子之和,则称该数为“完全数”。
    *问题分析与算法设计
    根据完全数的定义,先计算所选取的整数a(a的取值1~1000)的因子,将各因子累加于m,若m等于a,则可确认a为完全数。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int a,i,m;
    printf("There are following perfect numbers smaller than 1000:\n");
    for(a=1;a<1000;a++) /*循环控制选取1~1000中的各数进行判断*/
    {
    for(m=0,i=1;i<=a/2;i++) /*计算a的因子,并将各因子之和m=a,则a是完全数输出*/
    if(!(a%i))m+=i;
    if(m==a)
    printf("%4d ",a);
    }
    printf("\n");
    }



    *运行结果
    TThere are following perfect numbers smaller than 1000:
    6 28 496 
     

     

    26.亲密数

    如果整数A的全部因子(包括1,不包括A本身)之和等于B;且整数B的全部因子(包括1,不包括B本身)之和等于A,则将整数A和B称为亲密数。求3000以内的全部亲密数。
    *问题分析与算法设计
    按照亲密数定义,要判断数a是否有亲密数,只要计算出a的全部因子的累加和为b,再计算b的全部因子的累加和为n,若n等于a则可判定a和b是亲密数。计算数a的各因子的算法:
    用a依次对i(i=1~a/2)进行模运算,若模运算结果等于0,则i为a的一个因子;否则i就不是a的因子。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    int a,i,b,n;
    printf("There are following friendly–numbers pair smaller than 3000:\n");
    for(a=1;a<3000;a++) /*穷举1000以内的全部整数*/
    { 
    for(b=0,i=1;i<=a/2;i++) /*计算数a的各因子,各因子之和存放于b*/
    if(!(a%i))b+=i; /*计算b的各因子,各因子之和存于n*/
    for(n=0,i=1;i<=b/2;i++)
    if(!(b%i))n+=i;
    if(n==a&&a<b)
    printf("%4d..%4d ",a,b); /*若n=a,则a和b是一对亲密数,输出*/
    }
    }



    *运行结果
    There are following friendly–numbers pair smaller than 3000:
    220.. 284 1184.. 1210 2620.. 2924 

     

    27.自守数

    自守数是指一个数的平方的尾数等于该数自身的自然数。例如:
    252=625 762=5776 93762=87909376
    请求出200000以内的自守数
    *问题分析与算法设计
    若采用“求出一个数的平方后再截取最后相应位数”的方法显然是不可取的,因为计算机无法表示过大的整数。
    分析手工方式下整数平方(乘法)的计算过程,以376为例:
    376 被乘数
    X 376 乘数
    ———-
    2256 第一个部分积=被乘数*乘数的倒数第一位
    2632 第二个部分积=被乘数*乘数的倒数第二位
    1128 第三个部分积=被乘数*乘数的倒数第三位
    ———-
    141376 积
    本问题所关心的是积的最后三位。分析产生积的后三位的过程,可以看出,在每一次的部分积中,并不是它的每一位都会对积的后三位产生影响。总结规律可以得到:在三位数乘法中,对积的后三位产生影响的部分积分别为:
    第一个部分积中:被乘数最后三位*乘数的倒数第一位
    第二个部分积中:被乘数最后二位*乘数的倒数第二位
    第三个部分积中:被乘数最后一位*乘数的倒数第三位
    将以上的部分积的后三位求和后截取后三位就是三位数乘积的后三位。这样的规律可以推广到同样问题的不同位数乘积。
    按照手工计算的过程可以设计算法编写程序。
    *程序说明与注释

     

    #include<stdio.h>
    int main()
    {
    long mul,number,k,ll,kk;
    printf("It exists following automorphic nmbers small than 200000:\n");
    for(number=0;number<200000;number++)
    {
    for(mul=number,k=1;(mul/=10)>0;k*=10);
    /*由number的位数确定截取数字进行乘法时的系数k*/
    kk=k*10; /*kk为截取部分积时的系数*/
    mul=0; /*积的最后n位*/
    ll=10; /*ll为截取乘数相应位时的系数*/
    while(k>0)
    {
    mul=(mul+(number%(k*10))*(number%ll-number%(ll/10)))%kk;
    /*(部分积+截取被乘数的后N位*截取乘数的第M位),%kk再截取部分积*/
    k/=10; /*k为截取被乘数时的系数*/
    ll*=10;
    }
    if(number==mul) /*判断若为自守数则输出*/
    printf("%ld ",number);
    }
    }



    *运行结果
    It exsts following automorphic numbners smaller than 200000:
    0 1 5 6 25 76 376 625 9376 90625 109376 

     

    28.回文数

    打印所有不超过n(取n<256) 的其平方具有对称性质的数(也称回文数)。
    *问题分析与算法设计
    对于要判断的数n,计算出其平方后(存于a),将a的每一位进行分解,再按a的从低到高的顺序将其恢复成一个数k(如n=13,则a=169且k=961),若a等于k则可判定n为回亠数。
    *程序说明与注释

    #include<stdio.h>
    
    void GetDigit(int number, int(&arr)[16], int& length)
    {
    	for (length = 0; number != 0; ++length) /*从低到高分解数a的每一位存于数组m[0]~m[16]*/
    	{
    		arr[length] = number % 10;//这个是取得a的个位,整个循环合起来就可以取得各个位
    		number /= 10;
    	}
    }
    bool IsCircle(int(&arr)[16], int length)
    {
    	for (int i = 0, j = length-1; i <= j; ++i, --j)//因为n的平方的各个位都存在数组中了,下面判断是不是对称
    	{
    		if (arr[j] != arr[i])
    		{
    			return false;
    		}//只要有一位不是对称,那就说明不是对称,就可以退出了
    	}
    	return true;
    }
    int main(void)
    {
    	int digitArray[16] = {0};
    	printf("No. number it's square(palindrome)\n");
    	for (int n = 1; n < 1000; n++) /*穷举n的取值范围*/
    	{
    		auto a = n * n; /*计算n的平方*/
    		int length = 0;
    		GetDigit(a, digitArray,  length);
    		if (IsCircle(digitArray, length))
    		{
    			printf("%10d %10d\n", n, a);
    		}
    	}
    	return 0;
    }


    29.求具有abcd=(ab+cd)2性质的四位数
    3025这个数具有一种独特的性质:将它平分为二段,即30和25,使之相加后求平方,即(30+25)2,恰好等于3025本身。请求出具有这样性质的全部四位数。
    *问题分析与算法设计
    具有这种性质的四位数没有分布规律,可以采用穷举法,对所有四位数进行判断,从而筛选出符合这种性质的四位数。具体算法实现,可任取一个四位数,将其截为两部分,前两位为a,后两位为b,然后套用公式计算并判断。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int n,a,b;
    printf("There are following number with 4 digits satisfied condition\n");
    for(n=1000;n<10000;n++) /*四位数N的取值范围1000~9999*/
    {
    a=n/100; /*截取N的前两位数存于a*/
    b=n%100; /*截取N的后两位存于b*/
    if((a+b)*(a+b)==n) /*判断N是否为符合题目所规定的性质的四位数*/
    printf("%d ",n);
    }
    }
    *运行结果
    There are following numbers with 4 digits satisfied condition:
    2025 3025 9801 

     

    30.求素数

    求素数表中1~1000之间的所有素数
    *问题分析与算法设计
    素数就是仅能衩1和它自身整除的整数。判定一个整数n是否为素数就是要判定整数n能否被除1和它自身之外的任意整数整除,若都不能整除,则n为素数。
    程序设计时i可以从2开始,到该整数n的1/2为止,用i依次去除需要判定的整数,只要存在可以整除该数的情况,即可确定要判断的整数不是素数,否则是素数。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int n1,nm,i,j,flag,count=0;
    do{
    printf("Input START and END=?");
    scanf("%d%d",&n1,&nm); /*输入求素数的范围*/
    }while(!(n1>0&&n1<nm)); /*输入正确的范围*/
    printf("………..PRIME TABLE(%d–%d)…………\n",n1,nm);
    if(n1==1||n1==2) /*处理素数2*/
    {
    printf("%4d",2);
    n1=3;count++;
    }
    for(i=n1;i<=nm;i++) /*判定指定范围内的整数是否为素数*/
    {
    if(!(i%2))continue;
    for(flag=1,j=3;flag&&j<i/2;j+=2)
    /*判定能否被从3到整数的一半中的某一数所整除*/
    if(!(i%j))flag=0; /*若能整除则不是素数*/
    if(flag) printf(++count%15?"%4d":"%4d\n",i);
    }
    }
    *思考题
    请找出十个最小的连续自然数,它们个个都是合数(非素数) 


    C/C++语言经典、实用、趣味程序设计编程百例精解(4)

     

    31.歌德巴赫猜想

     
    验证:2000以内的正偶数都能够分解为两个素数之和(即验证歌德巴赫猜想对2000以内的正偶数成立)。
    *问题分析与算法设计
    为了验证歌德巴赫猜想对2000以内的正偶数都是成立的,要将整数分解为两部分,然后判断出分解出的两个整数是否均为素数。若是,则满足题意;否则重新进行分解和判断。
    程序中对判断是否为素数的算法进行了改进,对整数判断“用从2开始到该整数的一半”改为“2开始到该整数的平方根”。原因何在请自行分析。
    *程序说明与注释
    #include<stdio.h>
    #include<math.h>
    int fflag(int n);
    int main()
    {
    int i,n;
    for(i=4;i<=2000;i+=2)
    {
    for(n=2;n<i;n++) /*将偶数i分解为两个整数*/
    if(fflag(n)) /*分别判断两个整数是否均为素数*/
    if(fflag(i-n))
    {
    printf("%14d=%d+%d\n",i,n,i-n); /*若均是素数则输出*/
    break;
    }
    if(n==i) printf("error %d\n",i);
    }
    }
    int fflag(int i) /*判断是否为素数*/
    {
    int j;
    if(i<=1)return 0;
    if(i==2)return 1;
    if(!(i%2))return 0; /*if no,return 0*/
    for(j=3;j<=(int)(sqrt((double)i)+1);j+=2)
    if(!(i%j))return 0;
    return 1; /*if yes,return 1*/

     

    32.可逆素数 

    求四位的可逆素数。可逆素数指:一个素数将其各位数字的顺序倒过来构成的反序数也是素数。
    *问题分析与算法设计
      本题的重点不是判断素数的方法,而是求一个整数的反序数。求反序数的方法是从整数的末尾依次截取最后一位数字,每截取一次后整数缩小10倍,将截取的数字作为新的整数的最后一位(新的整数扩大10倍后加上被截取的数字)。这样原来的整数的数字从低到高被不断地截取,依次作为新的整数从高到低的各位数字。 
    *程序说明与注释
    #include<stdio.h>
    #include<math.h>
    int num(int number);
    int ok(int number);
    int main()
    {
    int i,count;
    printf("There are invertable primes with 4 digits: \n");
    for(count=0,i=1001;i<9999;i+=2) //穷举全部的奇数
    {
    if(num(i)) //若是可逆素数,则输出
    printf(count%9 ? "%3d:%d" : "%3d:%d\n",++count,i);
    }
    return 0;

    int num(int number)
    {
    int i,j;
    if(!ok(number))return 0; //判断是否为素数
    for(i=number,j=0;i>0;i/=10) //按位将整数倒过来,产生反序数
    {
    j=j*10 + i%10;
    }
    if(number<j) //若原数小于反序数
    {
    if(!ok(i)) //判断对应的反序数是否为可逆素数
    {
    return 0;
    }
    else
    {
    return 1; //若是可逆数素数,则返回1
    }
    }
    else
    {
    return 0; 
    }
    getchar();
    return 0;
    }
    int ok(int number)
    {
    int i,j;
    if(number%2 ==0) //判断是否为素数
    return 0;
    j= sqrt((double)number) +1 ; //取整数的平方根为判断的上限
    for(i=3;i<j;i+=2)
    {
    if(number %i ==0) //若为素数则返回1,否则返回0
    return 0;
    }
    return 1;
    }
    *思考题
    求1000以内的孪生素数。孪生素数是指:若a为素数,且a+2也是素数,则素数a和a+2称为孪生素数。

     

    33.回文素数

    求不超过1000的回文素数。
    *问题分析与算法设计
      所谓回文素数是指,对一个整数n从左向右和从由向左读其结果值相同且是素数,即称n为回文素数。所以本题的重点不是判断素数的方法,而是求回文整数。构造回文数的方法很多,这里仅介绍一种最简单的算法。实现思路是先求出一个整数的回文数,再判断是否为素数。
      不超过1000的回文数包括二位和三位的回文数,我们采用穷举法来构造一个整数并求与其对应的反序数,若整数与其反序数相等,则该整数是回文数。
    *程序说明与注释
    #include<stdio.h> 
    int a(int n)
    int main()
    {
    int i,j,t,k,s;
    printf("Following are palindrome primes not greater than 1000:\n");
    for(i=0;i<=9;++i) //穷举第一位
    for(j=0;j<=9;++j) //穷举第二位
    for(k=0;k<=9;++k) //穷举第三位
    {
    s=i*100 + j*10 + k; //计算组成的整数
    t=ik*100 + j*10 + i; //计算对应的反序数
    if(i == 0 && j==0) //处理整数的前两位为0的情况
    {
    t/100;
    }
    else if(i ==0) //处理整数的第一位为0的情况
    {
    t/10;
    }
    if(s.10 && s==t && a(s)) //若大于10且为回文素数,则输出
    {
    printf("%d\t",s);
    }
    }
    return 0;
    }
    //判断参数n是否为素数
    int a(int n)
    {
    int i;
    for(i=2;i<(n-1)/2;+=i)
    {
    if(n%i == 0)
    return 0;
    }
    return 1;
    }
    *运行结果
    Following are palindrome primes not greater than 1000:
    11 101 131 151 181 191 313 353
    373 383 727 787 797 919 929
    *思考题
    优化生成回文数的算法。

     

    34.要发就发

     
    “1898–要发就发”。请将不超过1993的所有素数从小到大排成第一行,第二行上的每个素数都等于它右肩上的素数之差。编程求出:第二行数中是否存在这样的若干个连续的整数,它们的和恰好是1898?假好存在的话,又有几种这样的情况?
    第一行:2 3 5 7 11 13 17……1979 1987 1993
    第二行:1 2 2 4 2 4…… 8 6
    *问题分析与算法设计
    首先从数学上分析该问题:
    假设第一行中的素数为n[1]、n[2]、n[3]….n[i]、…第二行中的差值为m[1]、m[2]、m[3]…m[j]…。其中m[j]为:
    m[j]=n[j+1]-n[j]。
    则第二行连续N个数的和为:
    SUM=m[1]+m[2]+m[3]+…+m[j]
    =(n[2]-n[1])+(n[3]-n[2])+(n[4]-n[3])+…+(n[j+1]-n[j])
    =n[j+1]-n[1]
    由此题目就变成了:在不超过1993的所有素数中是否存在这样两个素数,它们的差恰好是1898。若存在,则第二行中必有所需整数序列,其和恰为1898,。
    对等价问题的求解是比较简单的。
    由分析可知,在素数序列中不必包含2,因为任意素数与2的差一定为奇数,所以不必考虑。
    *程序与程序注释:
    #include<stdio.h>
    #include<math.h>
    #define NUM 320
    int number[NUM]; /*存放不超过1993的全部奇数*/
    int fflag(int i);
    int main()
    {
    int i,j,count=0;
    printf("there are follwing primes sequences in first row:\n");
    for(j=0,i=3;i<=1993;i+=2) /*求出不超过1993的全部奇数*/
    if(fflag(i)) number[j++]=i;
    for(j–;number[j]>1898;j–) /*从最大的素数开始向1898搜索*/
    {
    for(i=0;number[j]-number[i]>1898;i++); /*循环查找满足条件的素数*/
    if(number[j]-number[i]==1898) /*若两个素数的差为1898,则输出*/
    printf("(%d).%3d,…..,%d\n",++count,number[i],number[j]);
    }
    }
    int fflag(int i)
    {
    int j;
    if(i<=1) return 0; /*判断是否为素数*/
    if(i==2) return 1;
    if(!(i%2)) return 0; /*if no, return 0*/
    for(j=3;j<=(int)(sqrt((double)i)+1);j+=2)
    if(!(i%j)) return 0;
    return 1;
    }
    *运行结果
    There are follwing primes sequences in first row:
    (1).89,……,1987
    (2).53,……,1951
    (3). 3,……,1901
    *思考题
    将1,2,3,。。。,20这20个连续的自然数排成一圈,使任意两个相邻的自然数之和均为素数。

     

    35.素数幻方

    求四阶的素数幻方。即在一个4X4 的矩阵中,每一个格填 入一个数字,使每一行、每一列和两条对角线上的4 个数字所组成的四位数,均为可逆素数。
    *问题分析与算法设计
    有了前面的基础,本题应当说是不困难的。
    最简单的算法是:采用穷举法,设定4X4矩阵中每一个元素的值后,判断每一行、每一列和两条对角线上的4个数字组成的四位数是否都是可逆素数,若是则求出了满足题意的一个解。
    这种算法在原理是对的,也一定可以求出满足题意的全部解。但是,按照这一思路编出的程序效率很低,在微机上几个小时也不会运行结束。这一算法致命的缺陷是:要穷举和判断的情况过多。
    充分利用题目中的“每一个四位数都是可逆素数”这一条件,可以放弃对矩阵中每个元素进行的穷举的算法,先求出全部的四位可逆素数(204个),以矩阵的行为单位,在四位可逆素数的范围内进行穷举,然后将穷举的四位整数分解为数字后,再进行列和对角线方向的条件判断,改进的算法与最初的算法相比,大大地减少了穷举的次数。
    考虑矩阵的第一行和最后一行数字,它们分别是列方向四位数的第一个数字和最后一个数字,由于这些四位数也必须是可逆素数,所以矩阵的每一行和最后一行中的各个数字都不能为偶数或5。这样穷举矩阵的第一行和最后一行时,它们的取值范围是:所有位的数字均不是偶数或5的四位可逆数。由于符合这一条件的四位可逆素数很少,所以这一范围限制又一次减少了穷举的次数。
    对算法的进一步研究会发现:当设定了第一和第二行的值后,就已经可以判断出当前的这种组合是否一定是错误的(尚不能肯定该组合一定是正确的)。若按列方向上的四个两位数与四位可逆数的前两位矛盾(不是其中的一种组合),则第一、二行的取值一定是错误的。同理在设定了前三行数据后,可以立刻判断出当前的这种组合是否一定是错误的,若判断出矛盾情况,则可以立刻设置新的一组数据。这样就可以避免将四个数据全部设定好以后再进行判断所造成的低效。
    根据以上分析,可以用伪语言描述以上改进的算法:
    开始
    找出全部四位的可逆素数;
    确定全部出现在第一和最后一行的四位可逆素数;
    在指定范围 内穷举第一行
    在指定范围内穷举第二行
    若第一、第二、三行已出现矛盾,则继续穷举下一个数;
    在指定范围内穷举第四行
    判断列和对角方向是否符合题意
    若符合题意,则输出矩阵;
    否则继续穷举下一个数;
    结束
    在实际编程中,采用了很多程序设计技巧,假如设置若干辅助数组,其目的就是要最大限度的提高程序的执行效率,缩短运行时间。下面的程序运行效率是比较高的。
    *程序说明与注释
    #include<stdio.h>
    #include<math.h>
    int number[210][5]; /*存放可逆素数及素数分解后的各位数字*/
    int select[110]; /*可以放在矩阵第一行和最后一行的素数的下标*/ 
    int array[4][5]; /*4X4的矩阵,每行0号元素存可逆素数对应的数组下标*/
    int count; /*可逆素数的数目*/
    int selecount; /*可以放在矩阵第一行和最后一行的可逆素数的数目*/
    int larray[2][200]; /*存放素数前二、三位数的临时数组所对应的数量计数器*/
    int lcount[2];
    int num(int number);
    int ok(int number);
    void process(int i);
    void copy_num(int i);
    int comp_num(int n);
    int find1(int i);
    int find2(void);
    int find0(int num);
    void p_array(void);
    int main()
    {
    int i,k,flag,cc=0,i1,i4;
    printf("there are magic squares with invertable primes as follw:\n");
    for(i=1001;i<9999;i+=2) /*求满足条件的可逆素数*/
    {
    k=i/1000;
    if(k%2!=0&&k!=5&&num(i)) /*若可逆素数的第一位不是偶数或5*/
    {
    number[count][0]=i; /*存入数组*/
    process(count++); /*分解素数的各位数字*/
    if(number[count-1][2]%2!=0&& /*若可逆素数满足放在矩阵第一行*/
    number[count-1][3]%2!=0&& /*和最后一行的条件,记录可逆素数的*/
    number[count-1][2]!=5&& /*下标,计数器加1*/
    number[count-1][3]!=5)
    select[selecount++]=count-1;
    }
    }
    larray[0][lcount[0]++]=number[0][0]/100; /*临时数组的第一行存前二位*/
    larray[1][lcount[1]++]=number[0][0]/10; /*临时数组的第二行存前三位*/
    for(i=1;i<count;i++) /*将素数不重复的前二、三位存入临时数组中*/
    {
    if(larray[0][lcount[0]-1]!=number[i][0]/100)
    larray[0][lcount[0]++]=number[i][0]/100;
    if(larray[1][lcount[1]-1]!=number[i][0]/10)
    larray[1][lcount[1]++]=number[i][0]/10;
    }
    for(i1=0;i1<selecount;i1++) /*在第一行允许的汇聚围内穷举*/
    {
    array[0][0]=select[i1]; /*取对应的素数下标*/
    copy_num(0); /*复制分解的素数*/
    for(array[1][0]=0;array[1][0]<count;array[1][0]++) /*穷举第二行*/
    {
    copy_num(1); /*复制分解的数字*/
    if(!comp_num(2))
    continue; /*若每列的前两位的组成与素数相矛盾,则试探下一个数*/
    for(array[2][0]=0;array[2][0]<count;array[2][0]++) /*穷举第三行*/
    {
    copy_num(2); /*复制分解的数字*/
    if(!comp_num(3))
    continue; /*若每列的前三位的组成与素数相矛盾,则试探下一个数*/
    for(i4=0;i4<selecount;i4++) /*在最后一行允许的范围内穷举*/
    {
    array[3][0]=select[i4];
    copy_num(3); /*复制分解的数字*/
    for(flag=1,i=1;flag&&i<=4;i++) /*判断每列是否可逆素数*/
    if(!find1(i))flag=0;
    if(flag&&find2()) /*判断对角线是否为可逆素数*/
    { printf("No.%d\n",++cc); p_array(); } /*输出幻方矩阵*/
    }
    }
    }
    }
    }
    int num(int number) /*判断是否可逆素数*/
    {
    int j;
    if(!ok(number)) return 0;
    for(j=0;number>0;number/=10) /*将素数变为反序数*/
    j=j*10+number%10;
    if(!ok(j)) return 0; /*判断反序数是否为素数*/
    return 1;
    }
    int ok(int number) /*判断是否为素数*/
    {
    int i,j;
    if(number%2==0) return 0;
    j=sqrt((double)number)+1;
    for(i=3;i<=j;i+=2)
    if(number%i==0) return 0;
    return 1;
    }
    void process(int i) /*将第i个整数分解为数字并存入数组*/
    {
    int j,num;
    num=number[i][0];
    for(j=4;j>=1;j–,num/=10)
    number[i][j]=num%10;
    }
    void copy_num(int i) /*将array[i][0]指向的素数的各位数字复制到array[i]中*/
    {
    int j;
    for(j=1;j<=4;j++)
    array[i][j]=number[array[i][0>[j];
    }
    int comp_num(int n) /*判断array中每列的前n位是否与可逆素数允许的前n位矛盾*/
    {
    static int ii; /*用内部静态变量保存前一次查找到的元素下标*/
    static int jj; /*ii:前一次查找前二位的下标,jj:前一次查找前三位的下标*/
    int i,num,k,*p; /*p:指向对应的要使用的前一次下标ii或jj*/
    int *pcount; /*pcount:指向要使用的临时数组数量的计数器*/
    switch(n){ /*根据n的值选择对应的一组控制变量*/
    case 2:pcount=&lcount[0];p=&ii;break;
    case 3:pcount=&lcount[1];p=&jj;break;
    default:return 0;
    }
    for(i=1;i<=4;i++) /*对四列分别进行处理*/
    {
    for(num=0,k=0;k<n;k++) /*计算前n位数字代表的数值*/
    num=num*10+array[k][i];
    if(num<=larray[n-2][*p]) /*与前一次最后查找到的元素进行比较*/
    for(;*p>=0&&num<larray[n-2][*p];(*p)–);/*若前次查找到的元素大,则向前找*/
    else
    for(;p<pcount&&num>larray[n-2][*p];(*p)++); /*否则向后找*/
    if(*p<0||*p>=*pcount)
    {
    *p=0; return 0;
    }
    if(num!=larray[n-2][*p])
    return 0; /*前n位不是可逆素数允许的值则返回0*/
    }
    return 1;
    }
    int find1(int i) /*判断列方向是否是可逆素数*/
    {
    int num,j;
    for(num=0,j=0;j<4;j++)
    num=num*10+array[j][i];
    return find0(num);
    }
    int find2(void) /*判断对角线方向是否是可逆素数*/
    {
    int num1,num2,i,j;
    for(num1=0,j=0;j<4;j++)
    num1=num1*10+array[j][j+1];
    for(num2=0,j=0,i=4;j<4;j++,i–)
    num2=num2*10+array[j][i];
    if(find0(num1)) return(find0(num2));
    else return 0;
    }
    int find0(int num) /*查找是否为满足要求的可逆素数*/
    {
    static int j;
    if(num<=number[j][0])for(;j>=0&&num<number[j][0];j–);
    else for(;j<count&&num>number[j][0];j++);
    if(j<0||j>=count){ j=0;return 0; }
    if(num==number[j][0]) return 1;
    else return 0;
    }
    void p_array(void) /*输出矩阵*/
    {
    int i,j;
    for(i=0;i<4;i++)
    {
    for(j=1;j<=4;j++) printf("%d ",array[i][j]);
    printf("\n");
    }
    }
    *问题的进一步讨论
    程序中大量技巧是用于尽早发现矛盾,减少循环次数,缩短运行时间。从实际效果看是相当不错的。但目前的程序仍然可以进一步优化。
    当第四行设定了前三行后,尚未设定的行就没必要再使用穷举的方法,因为列方向设定好的三位数字已经限制了最后一个数字可能的取值,在可逆数中找出前三位数字与设定好的三位数字相同的素数。这些素数就是在这一列前面已设定好的三位数字的限制条件下可能的取值。此时每一列上只有不超过四个可能的取值。找出全部各列可能的取值(可能的四位可逆素数),求出它们的交集。若交集为空,即没有共同的可能取值,则列间数据相互矛盾否满足则将交集中的数据填 入矩阵中就是题目的一个解。
    算法可再进一步优化。先穷举一、二和四列的数据,然后用上面的算法来确定第三行的值,这样可进一步缩小穷举的范围,提高运行效率。
    分析输出的结果。可以看出本题的基本解只有17种,每个解可通过旋转与反射获得同构的其它7个解,可以进一步改进程序,只输出17个基本解。
     
    *思考题
    用1到16构成一个四阶幻方,要求任意相邻两个方格中的数字之和均为素数。

     

    36.百钱百鸡问题

    中国古代数学家张丘建在他的《算经》中提出了著名的“百钱买百鸡问题”:鸡翁一,值钱五,鸡母一,值钱三,鸡雏三,值钱一,百钱买百鸡,问翁、母、雏各几何?
    *问题分析与算法设计
    设鸡翁、鸡母、鸡雏的个数分别为x,y,z,题意给定共100钱要买百鸡,若全买公鸡最多买20只,显然x的值在0~20之间;同理,y的取值范围在0~33之间,可得到下面的不定方程:
    5x+3y+z/3=100
    x+y+z=100
    所以此问题可归结为求这个不定方程的整数解。
    由程序设计实现不定方程的求解与手工计算不同。在分析确定方程中未知数变化范围的前提下,可通过对未知数可变范围的穷举,验证方程在什么情况下成立,从而得到相应的解。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int x,y,z,j=0;
    printf("Folleing are possible plans to buy 100 fowls with 100 Yuan.\n");
    for(x=0;x<=20;x++) /*外层循环控制鸡翁数*/
    for(y=0;y<=33;y++) /*内层循环控制鸡母数y在0~33变化*/
    {
    z=100-x-y; /*内外层循环控制下,鸡雏数z的值受x,y的值的制约*/
    if(z%3==0&&5*x+3*y+z/3==100)
    /*验证取z值的合理性及得到一组解的合理性*/
    printf("%2d:cock=%2d hen=%2d chicken=%2d\n",++j,x,y,z);
    }
    }
    *运行结果
    Follwing are possible plans to buy 100 fowls with 100 Yuan.
    1:cock=0 hen=25 chicken=75
    2:cock=4 hen=18 chicken=78
    3:cock=8 hen=11 chicken=81
    4:cock=12 hen=4 chicken=84
    *问题的进一步讨论
    这类求解不定方程总理的实现,各层循环的控制变量直接与方程未知数有关,且采用对未知数的取值范上穷举和组合的方法来复盖可能得到的全部各组解。能否根据题意更合理的设置循环控制条件来减少这种穷举和组合的次数,提高程序的执行效率,请读者考虑
    37.爱因斯坦的数学题
    爱因斯坦出了一道这样的数学题:有一条长阶梯,若每步跨2阶,则最最后剩一阶,若每步跨3 阶,则最后剩2阶,若每步跨5阶,则最后剩4阶,若每步跨6阶则最后剩5阶。只有每次跨7阶,最后才正好一阶不剩。请问这条阶梯共有多少阶?
    *问题分析与算法设计
    根据题意,阶梯数满足下面一组同余式:
    x≡1 (mod2)
    x≡2 (mod3)
    x≡4 (mod5)
    x≡5 (mod6)
    x≡0 (mod7)
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int i=1; /*i为所设的阶梯数*/
    while(!((i%2==1)&&(i%3==2)&&(i%5==4)&&(i%6==5)&&(i%7==0)))
    ++i; /*满足一组同余式的判别*/
    printf("Staris_number=%d\n",i);
    }
    *运行结果
    Staris_number=119
    *问题的进一步讨论
    此题算法还可考虑求1、2、4、5的最小公倍数n,然后判t(t为n-1)≡0(mod7)是否成立,若不成立则t=t+n,再进行判别,直至选出满足条件的t值。请自行编写程序实现 
     38.换分币
    用一元人民币兑换成1分、2分和5分硬币,共有多少种不同的兑换方法。
    *问题分析与算法设计
    根据题意设i,j,k分别为兑换的1分、2分、5分硬币所具有的钱数(分),则i,j,k的值应满足:
    i+j+k=100
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int i,j,k,count=1;
    printf("There are follwing small exchange plans for 1 Yuan note:\n");
    for(i=0;i<=100;i++) /*i为1分硬币钱数,可取值0,1,2…,100*/
    for(j=0;j<=100-i;j+=2) /*j为2分硬币钱数,可取0值,2,4,…,100*/
    for(k=0;k<=100-i-2*j;k+=5) /*k为5分硬币钱数*/
    if(i+j+k==100)
    printf(count%4?"%d:1*%d+2*%d+5*%d\t":"%d:1*%d+2*%d+5*%d\n",count++,i,j/2,k/5);

     

    39.年龄几何

    张三、李四、王五、刘六的年龄成一等差数列,他们四人的年龄相加是26,相乘是880,求以他们的年龄为前4项的等差数列的前20项。
    *问题分析与算法设计
    设数列的首项为a,则前4项之和为"4*n+6*a",前4 项之积为"n*(n+a)*(n+a+a)*(n+a+a+a)"。同时"1<=a<=4","1<=n<=6"。可采用穷举法求出此数列。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int n,a,i;
    printf("The series with equal difference are:\n");
    for(n=1;n<=6;n++) /*公差n取值为1~6*/
    for(a=1;a<=4;a++) /*首项a取值为1~4*/
    if(4*n+6*a==26&&n*(n+a)*(n+a+a)*(n+a+a+a)==880) /*判断结果*/
    for(i=0;i<20;i++)
    printf("%d ",n+i*a); /*输出前20项*/
    }
    *运行结果
    The series with equal difference are:
    2 5 8 11 14 17 20 23 26 29 32 35 38 41 44 47 50 53 56 59 
    40.三色球问题
    若一个口袋中放有12个球,其中有3个红的。3个白的和6个黒的,问从中任取8个共有多少种不同的颜色搭配?
    *问题分析与算法设计
    设任取的红球个数为i,白球个数为j,则黒球个数为8-i-j,根据题意红球和白球个数的取值范围是0~3,在红球和白球个数确定的条件下,黒球个数取值应为8-i-j<=6。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int i,j,count=0;
    printf(" RED BALL WHITE BALL BLACKBALL\n");
    printf("…………………………………………..\n");
    for(i=0;i<=3;i++) /*循环控制变量i控制任取红球个数0 ̄3*/
    for(j=0;j<=3;j++) /*循环控制变量j控制任取白球个数0 ̄3*/
    if((8-i-j)<=6)
    printf(" %2d: %d %d %d\n",++count,i,j,8-i-j);

    C/C++语言经典、实用、趣味程序设计编程百例精解(5) 
    41.马克思手稿中的数学题
    马克思手稿中有一道趣味数学问题:有30个人,其中有男人、女人和小孩,在一家饭馆吃饭花了50先令;每个男人花3先令,每个女人花2先令,每个小孩花1先令;问男人、女人和小孩各有几人?
    *问题分析与算法设计
    设x,y,z分别代表男人、女人和小孩。按题目的要求,可得到下面的方程:
    x+y+z=30 (1)
    3x+2y+z=50 (2)
    用方程程序求此不定方程的非负整数解,可先通过(2)-(1)式得:
    2x+y=20 (3)
    由(3)式可知,x变化范围是0~10
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int x,y,z,count=0;
    printf(" Men Women Children\n");
    printf("………………………………….\n");
    for(x=0;x<=10;x++)
    {
    y=20-2*x; /*x定值据(3)式求y*/
    z=30-x-y; /*由(1)式求z*/
    if(3*x+2*y+z==50) /*当前得到的一组解是否满足式(2)*/
    printf(" %2d: %d %d %d\n",++count,x,y,z);
    }

     

     42.最大公约数和最小公倍数

    求任意两个正整数的最大公约数和(GCD)和最小公倍数(LCM)
    *问题分析与算法设计
    手工方式求两个正整数的蝚大公约数的方法是用辗转相除法,在程序中可以模拟这种方式。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a,b,num1,num2,temp;
    printf("Input a & b:");
    scanf("%d%d",&num1,&num2);
    if(num1>num2) /*找出两个数中的较大值*/
    {
    temp=num1; num1=num2; num2=temp; /*交换两个整数*/
    }
    a=num1; b=num2;
    while(b!=0) /*采用辗转相除法求最大公约数*/
    {
    temp=a%b;
    a=b;
    b=temp;
    }
    printf("The GCD of %d and %d is: %d\n",num1,num2,a); /*输出最大公约数*/
    printf("The LCM of them is: %d\n",num1*num2/a); /*输出最小公倍数*/
    }
    *运行结果
    1.Input a & b: 20 55
    The GCD of 20 and 55 is: 5
    The LCM of them is: 220
    2.Input a & b: 17 71
    The GCD of 17 and 71 is: 1
    The LCM of them is: 1207
    3.Input a & b: 24 88
    The GCD of 24 and 88 is: 8
    The LCM of them is: 264
    4.Input a & b: 35 85
    The GCD of 35 and 85 is: 5
    The LCM of them is: 595
    *思考题
    求一个最小的正整数,这个正整数被任意n(2<=n<=10)除都是除不尽的,而且余数总是(n-1)。例如:被9除时的余数为8。要求设计一个算法,不允许枚举与除2、除3、….、除9、除10有关的命令,求出这个正整数。 

     

    43.分数比较

    比较两个分数的大小。
    *问题分析与算法设计
    人工方式下比较分数大小最常用的方法是:进行分数的通分后比较分子的大小。可以编程模拟手式方式。
    *程序说明与注释
    #include<stdio.h>
    int zxgb(int a,int b);
    int main()
    {
    int i,j,k,l,m,n;
    printf("Input two FENSHU:\n");
    scanf("%d/%d,%d/%d",&i,&j,&k,&l); /*输入两个分数*/
    m=zxgb(j,l)/j*i; /*求出第一个分数通分后的分子*/
    n=zxgb(j,l)/l*k; /*求出第二个分数通分后的分子*/
    if(m>n) printf("%d/%d>%d/%d\n",i,j,k,l); /*比较分子的大小*/
    else if(m==n) printf("%d/%d=%d/%d\n",i,j,k,l); /*输出比较的结果*/
    else printf("%d/%d<%d/%d\n",i,j,k,l);
    }
    int zxgb(int a,int b)
    {
    long int c;
    int d;
    if(a<b) c=a,a=b,b=c; /*若a<b,则交换两变量的值*/
    for(c=a*b;b!=0;)
    {
    d=b; b=a%b; a=d;
    }
    return (int)c/a;
    }
    *运行结果
    输入: 4/5,6/7 输出: 4/5<6/7
    输入: 8/4,16/32 输出: 8/4>16/32
    输入:16/32,4/8 输出: 16/32=4/8 
    44.分数之和
    求这样的四个自然数p,q,r,s(p<=q<=r<=s),使得以下等式成立: 
    1/p+1/q+1/r+1/s=1
    *问题分析与算法设计
    若规定p<=q<=r<=s,将原式通分、化简并整理后得到:
    2<=p<5 p<=q<7 q<r<13
    采用最简单的穷举方法可以很方便的求解。
    程序与程序注释:
    #include<stdio.h>
    int main()
    {
    int p,q,r,s,count=0;
    printf("The 4 fractions which sum is equal 1 are:\n");
    for(p=2;p<5;p++) /*穷举分母*/
    for(q=p;q<7;q++)
    for(r=q;r<13;r++)
    if(p*q*r-q*r-p*r-p*q!=0)
    {
    s=(p*q*r)/(p*q*r-q*r-p*r-p*q); /*求出s的值*/
    if(!((p*q*r)%(p*q*r-q*r-p*r-p*q))&&s>=r)
    printf("[%2d] 1/%d+1/%d+1/%d+1/%d=1\n",++count,p,q,r,s);
    /*输出结果*/
    }
    }
    *运行结果
    *思考题
    将1、2、3、4、5、6、7、8、9九个数字分成以下三种分数形式之一,每个数字只能用一次,使得该分数刚好等于一个整数。
    求所有满足条件的表示形式。
    (参考答案:某些自然数没有这种表示形式,如:1、2、3、4、15、18等。此外整数100有11种满足条件的表示形式;89的表示形式最多,共有36种;三种形式中,最大可表示的整数为794。) 

     

    45.将真分数分解为埃及分数

    分子为1 的分数称为埃及分数,现输入一个真分数,请将该分数分解为埃及分数。
    如:8/11=1/2+1/5+1/55+1/110。
    *问题分析与算法设计
    若真分数的分子a能整除分母b,则真分数经过化简就可以得到埃及分数,若真分数的分子不能整除分母,则可以从原来的分数中分解出一个分母为b/a+1的埃及分数。用这种方法将剩余部分反复分解,最后可得到结果。
    *程序说明与注释
    /*安安注:对源程序作稍许修改,主要是添加了一个外循环,可以直接计算多个真分数的埃及分数,按Ctrl-C退出。具体的算法我没有认真看,有问题请提出,谢谢*/
    #include<stdio.h>
    int main(void)
    {
    long int a,b,c;
    while(true)
    {
    printf("Please enter a optional fraction(a/b):");
    scanf("%ld/%ld",&a,&b); /*输入分子a和分母b*/
    printf("It can be decomposed to:");
    while(true)
    {
    if(b%a) /*若分子不能整除分母*/
    c=b/a+1; /*则分解出一个分母为b/a+1的埃及分数*/
    else{ c=b/a; a=1;} /*否则,输出化简后的真分数(埃及分数)*/
    if(a==1)
    {
    printf("1/%ld\n",c);
    break; /*a为1标志结束*/
    }
    else
    printf("1/%ld + ",c);
    a=a*c-b; /*求出余数的分子*/
    b=b*c; /*求出余数的分母*/
    if(a==3) /*若余数为3,输出最后两个埃及分数*/
    { printf("1/%ld + 1/%ld\n",b/2,b); break;}
    }
    }
    return 0;
    }
     
    *运行结果
    Please enter a optional fraction (a/b): 1/6
    It can be decomposed to: 1/6
    Please enter a optional fraction (a/b): 20/33
    It can be decomposed to: 1/2+1/10+1/165
    Please enter a optional fraction (a/b): 10/89
    It can be decomposed to: 1/9+1/801
    Please enter a optional fraction (a/b): 19/99
    It can be decomposed to: 1/6+1/40+1/3960
    Please enter a optional fraction (a/b): 8/87
    It can be decomposed to: 1/11+1/957
    ……(按ctrl-c退出)

     

    46.列出真分数序列

     
    按递增顺序依次列出所有分母为40,分子小于40的最简分数。
    *问题分析与算法设计
    对分子采用穷举法,利用最大公约数的方法,判断分子与40是否构成真分数。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int i,num1,num2,temp;
    printf("The fraction serials with demominator 40 is:\n");
    for(i=1;i<=40;i++) /*穷举40以内的全部分子*/
    {
    num1=40;
    num2=i;
    while(num2!=0) /*采用辗转相除法求出最大公约数*/
    {
    temp=num1%num2;
    num1=num2;
    num2=temp;
    }
    if(num1==1) /*若最大公约数为1,则为最简真分数*/
    printf("%d/40 ",i);
    }
    }
    *运行结果
    The fraction serials with demominator 40 is:
    1/40 3/40 7/40 9/40 11/40 13/40 17/40 19/40
    21/40 23/40 27/40 29/40 31/40 33/40 37/40 39/40
    *思考题
    按递增顺序依次列出所有分母小于等于40的最简真分数 

     

    47.计算分数的精确值

    使用数组精确计算M/N(0<M<N<=100)的值。如果M/N是无限循环小数,则计算并输出它的第一循环节,同时要求输出 循环节的起止位置(小数位的序号)
    *问题分析与算法设计
    由于计算机字长的限制,常规的浮点运算都有精度限制,为了得到高精度的计算结果,就必须自行设计实现方法。
    为了实现高精度的计算,可将商存放在一维数组中,数组的每个元素存放一位十进制数,即商的第一位存放在第一个元素中,商的第二位存放在第二个元素中….,依次类推。这样就可以使用数组不表示一个高精度的计算结果。
    进行除法运算时可以模拟人的手工操作,即每次求出商的第一位后,将余数乘以10,再计算商的下一位,重复以上过程,当某次计算后的余数为0 时,表示M/N为有限不循环小数某次计算后的余数与前面的某个余数相同时,则M/N为无限循环小数,从该余数第一次出现之后所求得的各位数就是小数的循环节。
    程序具体实现时,采用了数组和其它一些技巧来保存除法运算所得到的余数和商的各位数。
    *程序说明与注释
    #include<stdio.h>
    int remainder[101],quotient[101]; /*remainder:存放除法的余数; quotient:依次存放商的每一位*/
    int main()
    {
    int m,n,i,j;
    printf("Please input a fraction(m/n)(<0<m<n<=100):");
    scanf("%d/%d",&m,&n); /*输入被除数和除数*/
    printf("%d/%d it's accuracy value is:0.",m,n);
    for(i=1;i<=100;i++) /*i: 商的位数*/
    {
    remainder[m]=i; /*m:除的余数 remainder[m]:该余数对应的商的位数*/
    m*=10; /*余数扩大10位*/
    quotient[i]=m/n; /*商*/
    m=m%n; /*求余数*/
    if(m==0) /*余数为0 则表示是有限小数*/
    {
    for(j=1;j<=1;j++) printf("%d",quotient[j]); /*输出商*/
    break; /*退出循环*/
    }
    if(remainder[m]!=0) /*若该余数对应的位在前面已经出现过*/
    {
    for(j=1;j<=i;j++) printf("%d",quotient[j]); /*则输出循环小数*/
    printf("\n\tand it is a infinite cyclic fraction from %d\n",remainder[m]);
    printf("\tdigit to %d digit after decimal point.\n",i);
    /*输出循环节的位置*/
    break; /*退出*/
    }
    }
    }
    *思考题
    使用数组实现计算MXN的精确值

     

    48.新娘和新郞

    三对情侣参加婚礼,三个新郞为A、B、C,三个新娘为X、Y、Z。有人不知道谁和谁结婚,于是询问了六位新人中的三位,但听到的回答是这样的:A说他将和X结婚;X说她的未婚夫是C;C说他将和Z结婚。这人听后知道他们在开玩笑,全是假话。请编程找出谁将和谁结婚。
    *问题分析与算法设计
    将A、B、C三人用1,2,3表示,将X和A结婚表示为“X=1”,将Y不与A结婚表示为“Y!=1”。按照题目中的叙述可以写出表达式:
    x!=1 A不与X结婚
    x!=3 X的未婚夫不是C
    z!=3 C不与Z结婚
    题意还隐含着X、Y、Z三个新娘不能结为配偶,则有:
    x!=y且x!=z且y!=z
    穷举以上所有可能的情况,代入上述表达式中进行推理运算,若假设的情况使上述表达式的结果均为真,则假设情况就是正确的结果。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int x,y,z;
    for(x=1;x<=3;x++) /*穷举x的全部可能配偶*/
    for(y=1;y<=3;y++) /*穷举y的全部可能配偶*/
    for(z=1;z<=3;z++) /*穷举z的全部可能配偶*/
    if(x!=1&&x!=3&&z!=3&&x!=y&&x!=z&&y!=z) /*判断配偶是否满足题意*/
    {
    printf("X will marry to %c.\n",'A'+x-1); /*打印判断结果*/
    printf("Y will marry to %c.\n",'A'+y-1);
    printf("Z will marry to %c.\n",'A'+z-1);
    }
    }
    *运行结果
    X will marry to B. (X与B结婚)
    Y will marry to C. (Y与C结婚)
    Z will marry to A. (Z与A结婚) 

     

    49.委派任务

    某侦察队接到一项紧急任务,要求在A、B、C、D、E、F六个队员中尽可能多地挑若干人,但有以下限制条件:
    1)A和B两人中至少去一人;
    2)A和D不能一起去;
    3)A、E和F三人中要派两人去;
    4)B和C都去或都不去;
    5)C和D两人中去一个;
    6)若D不去,则E也不去。
    问应当让哪几个人去?
    *问题分析与算法设计
    用A、B、C、D、E、F六个变量表示六个人是否去执行任务的状态,变量的值为1,则表示该人去;变量的值为0,则表示该人不参加执行任务,根据题意可写出表达式:
    a+b>1 A和B两人中至少去一人;
    a+d!=2 A和D不能一起去;
    a+e+f==2 A、E、F三人中要派两人去;
    b+c==0或b+c==2 B和C都去或都不去;
    c+d==1 C和D两人中去一个;
    d+e==0或d==1 若D不去,则E也不去(都不去;或D去E随便)。
    上述各表达式之间的关系为“与”关系。穷举每个人去或不去的各种可能情况,代入上述表达式中进行推理运算,使上述表达式均为“真”的情况就是正确的结果。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a,b,c,d,e,f;
    for(a=1;a>=0;a–) /*穷举每个人是否去的所有情况*/
    for(b=1;b>=0;b–) /*1:去 0:不去*/
    for(c=1;c>=0;c–)
    for(d=1;d>=0;d–)
    for(e=1;e>=0;e–)
    for(f=1;f>=0;f–)
    if(a+b>=1&&a+d!=2&&a+e+f==2
    &&(b+c==0||b+c==2)&&c+d==1
    &&(d+e==0||d==1))
    {
    printf("A will%s be assigned. \n",a?"":"not");
    printf("B will%s be assigned. \n",b?"":"not");
    printf("C will%s be assigned. \n",c?"":"not");
    printf("D will%s be assigned. \n",d?"":"not");
    printf("E will%s be assigned. \n",e?"":"not");
    printf("F will%s be assigned. \n",f?"":"not");
    }
    }
    *运行结果
    A will be assigned. (去)
    B will be assigned. (去)
    C will be assigned. (去)
    D will not be assigned. (不去)
    E will not be assigned. (不去)
    F will be assigned. (去)
    *思考题
    某参观团按以下条件限制从A、B、C、D、E五个地方中选若干参观点:
    1)如去A,则必须去B;
    2)D、E两地只能去一地;
    3)B、C两地只能去一地;
    4)C、D两地都去或都不去;
    5)若去E地,A、D也必去。
    问该团最多能去哪几个地方?

     

    50.谁在说谎

    张三说李四在说谎,李四说王五在说谎,王五说张三和李四都在说谎。现在问:这三人中到底谁说的是真话,谁说的是假话?
    *问题分析与算法设计
    分析题目,每个人都有可能说的是真话,也有可能说的是假话,这样就需要对每个人所说的话进行分别判断。假设三个人所说的话的真假用变量A、B、C表示,等于1表示该人说的是真话; 表示这个人说的是假话。由题目可以得到:
    *张三说李四在说谎 张三说的是真话:a==1&&b==0
    或 张三说的是假话:a==0&&b==1
    *李四说王五在说谎 李四说的是真话:b==1&&c==0
    或 李四说的是假话:b==0&&c==1
    *王五说张三和李四都在说谎 王五说的是真话:c==1&&a+b==0
    或 王五说的是假话:c==0&&a+b!=0
    上述三个条件之间是“与”的关系。将表达式进行整理就可得到C语言的表达式:
    (a&&!b||!a&&b)&&(b&&!c||!b&&c)&&(c&&a+b==0||!c&&a+b!=0)
    穷举每个人说真话或说假话的各种可能情况,代入上述表达式中进行推理运算,使上述表达式均为“真”的情况就是正确的结果。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a,b,c;
    for(a=0;a<=1;a++)
    for(b=0;b<=1;b++)
    for(c=0;c<=1;c++)
    if((a&&!b||!a&&b)&&(b&&!c||!b&&c)&&(c&&a+b==0||!c&&a+b!=0))
    {
    printf("Zhangsan told a %s.\n",a?"truth":"lie");
    printf("Lisi told a %s.\n",b?"truch":"lie");
    printf("Wangwu told a %s.\n",c?"truch":"lie");
    }
    }
    *运行结果
    Zhangsan told a lie (张三说假话)
    Lisi told a truch. (李四说真话)
    Wangwu told a lie. (王五说假话) 


    C/C++语言经典、实用、趣味程序设计编程百例精解(6)

     

    51.谁是窃贼

    公安人员审问四名窃贼嫌疑犯。已知,这四人当中仅有一名是窃贼,还知道这四人中每人要么是诚实的,要么总是说谎的。在回答公安人员的问题中:
    甲说:“乙没有偷,是丁偷的。”
    乙说:“我没有偷,是丙便的。”
    丙说:“甲没有偷,是乙偷的。”
    丁说:“我没有偷。”
    请根据这四人的答话判断谁是盗窃者。
    *问题分析与算法设计
    假设A、B、C、D分别代表四个人,变量的值为1代表该人是窃贼。
    由题目已知:四人中仅有一名是窃贼,且这四个人中的每个人要么说真话,要么说假话,而由于甲、乙、丙三人都说了两句话:“X没偷,X偷了”,故不论该人是否说谎,他提到的两人中必有一人是小偷。故在列条件表达式时,可以不关心谁说谎,谁说实话。这样,可以列出下列条件表达式:
    甲说:”乙没有偷,是丁偷的。” B+D=1
    乙说:“我没有偷,是丙偷有。” B+C=1
    丙说:“甲没有偷,是乙偷的。” A+B=1
    丁说:“我没有偷。” A+B+C+D=1
    其中丁只说了一句话,无法判定其真假,表达式反映了四人中仅有一名是窃贼的条件。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int i,j,a[4];
    for(i=0;i<4;i++) /*假定只有第i个人为窃贼*/
    {
    for(j=0;j<4;j++) /*将第i个人设置为1表示窃贼,其余为0*/
    if(j==i)a[j]=1;
    else a[j]=0;
    if(a[3]+a[1]==1&&a[1]+a[2]==1&&a[0]+a[1]==1) /*判断条件是否成立*/
    {
    printf("The thief is "); /*成立*/
    for(j=0;j<=3;j++) /*输出计算结果*/
    if(a[j])printf("%c.",j+'A');
    printf("\n");
    }
    }
    }
    *运行结果
    The thief is B. (乙为窃贼。) 

     

     52.黑与白

    有A、B、C、D、E五人,每人额头上都帖了一张黑或白的纸。五人对坐,每人都可以看到其它人额头上的纸的颜色。五人相互观察后,
    A说:“我看见有三人额头上帖的是白纸,一人额头上帖的是黑纸。”
    B说:“我看见其它四人额头上帖的都是黑纸。”
    C说:“我看见一人额头上帖的是白纸,其它三人额头上帖的是黑纸。”
    D说:“我看见四人额头上帖的都是白纸。”
    E什么也没说。
    现在已知额头上帖黑纸的人说的都是谎话,额头帖白纸的人说的都是实话。问这五人谁的额头是帖白纸,谁的额头是帖黑纸?
    *问题分析与算法设计
    假如变量A、B、C、D、E表示每个人额头上所帖纸的颜色,0 代表是黑色,1 代表是白色。根据题目中A、B、C、D四人所说的话可以总结出下列关系:
    A说: a&&b+c+d+e==3||!a&&b+c+d+e!=3
    B说: b&&a+c+d+e==0||!b&&a+c+d+e!=0
    C说: c&&a+b+d+e==1||!c&&a+b+d+e!=1
    D说: d&&a+b+c+e==4||!d&&a+b+c+e!=4
    穷举每个人额头所帖纸的颜色的所有可能的情况,代入上述表达式中进行推理运算,使上述表达式为“真”的情况就是正确的结果。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a,b,c,d,e;
    for(a=0;a<=1;a++) /*黑色:0 白色:1*/
    for(b=0;b<=1;b++) /*穷举五个人额头帖纸的全部可能*/
    for(c=0;c<=1;c++)
    for(d=0;d<=1;d++)
    for(e=0;e<=1;e++)
    if((a&&b+c+d+e==3||!a&&b+c+d+e!=3)
    &&(b&&a+c+d+e==0||!b&&a+c+d+e!=0)
    &&(c&&a+b+d+e==1||!c&&a+b+d+e!=1)
    &&(d&&a+b+c+e==4||!d&&a+b+c+e!=4))
    {
    printf("A is pasted a piece of %s paper on his forehead.\n",
    a?"white":"black");
    printf("B is pasted a piece of %s paper on his forehead.\n",
    b?"white":"black");
    printf("C is pasted a piece of %s paper on his forehead.\n",
    c?"white":"black");
    printf("D is pasted a piece of %s paper on his forehead.\n",
    d?"white":"black");
    printf("E is pasted a piece of %s paper on his forehead.\n",
    e?"white":"black");
    }
    }
    *运行结果
    A is pasted a paper of black paper on his forehead. (黑)
    B is pasted a paper of black paper on his forehead. (黑)
    C is pasted a paper of white paper on his forehead. (白)
    D is pasted a paper of black paper on his forehead. (黑)
    E is pasted a paper of white paper on his forehead. (白)
    53.迷语博士的难题(1)
    诚实族和说谎族是来自两个荒岛的不同民族,诚实族的人永远说真话,而说谎族的人永远说假话。迷语博士是个聪明的人,他要来判断所遇到的人是来自哪个民族的。
    迷语博士遇到三个人,知道他们可能是来自诚实族或说谎族的。为了调查这三个人是什么族的,博士分别问了他们的问题,这是他们的对话:
    问第一个人:“你们是什么族?”,答:“我们之中有两个来自诚实族。”第二个人说:“不要胡说,我们三个人中只有一个是诚实族的。”第三个人听了第二个人的话后说:“对,就是只有一个诚实族的。”
    请根据他的回答判断他们分别是哪个族的。
    *问题分析与算法设计
    假设这三个人分别为A、B、C,若说谎其值为0,若诚实,其值为1。根据题目中三个人的话可分别列出:
    第一个人: a&&a+b+c==2||!a&&a+b+c!=2
    第二个人: b&&a+b+c==1||!b&&a+b+c!=1
    第三个人: c&&a+b+c==1||!c&&a+b+c!=1
    利用穷举法,可以很容易地推出结果。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a,b,c;
    for(a=0;a<=1;a++) /*穷举每个人是说谎还是诚实的全部情况*/
    for(b=0;b<=1;b++) /*说谎:0 诚实:1*/
    for(c=0;c<=1;c++)
    if((a&&a+b+c==2||!a&&a+b+c!=2) /*判断是否满足题意*/
    &&(b&&a+b+c==1||!b&&a+b+c!=1)
    &&(c&&a+b+c==1||!c&&a+b+c!=1))
    {
    printf("A is a %s.\n",a?"honest":"lier"); /*输出判断结果*/
    printf("B is a %s.\n",b?"honest":"lier");
    printf("C is a %s.\n",c?"honest":"lier");
    }
    }
    *运行结果
    A is a lier (说谎族)
    B is a lier (说谎族)
    C is a lier (说谎族)
    *思考题
    迷语博士遇到四个人,知道他们可能是来自诚实族和说谎族的。为了调查这四个人是什么族的,博士照例进行询问:”你们是什么族的?“
    第一人说:”我们四人全都是说谎族的。“
    第二人说:”我们之中只有一人是说谎族的。“
    第三人说:”我们四人中有两个是说谎族的。“
    第四人说:”我是诚实族的。“
    问自称是“诚实族”的第四个人是否真是诚实族的?
    (答案:第四个人是诚实族的。) 
    54.迷语博士的难题(2)
    两面族是荒岛上的一个新民族,他们的特点是说话真一句假一句且真假交替。如果第一句为真,则第二句是假的;如果第一句为假的,则第二句就是真的,但是第一句是真是假没有规律。
    迷语博士遇到三个人,知道他们分别来自三个不同的民族:诚实族、说谎族和两面族。三人并肩站在博士前面。
    博士问左边的人:“中间的人是什么族的?”,左边的人回答:“诚实族的”。
    博士问中间的人:“你是什么族的?”,中间的人回答:“两面族的”。
    博士问右边的人:“中间的人究竟是什么族的?”,右边的人回答:“说谎族的”。
    请问:这三个人都是哪个民族的?
    *问题分析与算法设计
    这个问题是两面族问题中最基本的问题,它比前面只有诚实族和说谎族的问题要复杂。解题时要使用变量将这三个民族分别表示出来。
    令:变量A=1表示:左边的人是诚实族的(用C语言表示为A);
    变量B=1表示:中间的人是诚实族的(用C语言表示为B);
    变量C=1表示:右边的人是诚实族的(用C语言表示为C);
    变量AA=1表示:左边的人是两面族的(用C语言表示为AA);
    变量BB=1表示:中间的人是两面族的(用C语言表示为BB);
    变量CC=1表示:右边的人是两面族的(用C语言表示为CC);
    则左边的人是说谎族可以表示为:A!=1且AA!=1 (不是诚实族和两面族的人)
    用C语言表示为:!A&&!AA
    中间的人是说谎族可以表示为:B!=1且BB!=1
    用C语言表示为:!B&&!BB
    右边的人是说谎族可以表示为:C!=0且CC!=1
    用C语言表示为:!C&&!CC
    根据题目中“三人来自三个民族”的条件,可以列出:
    a+aa!=2&&b+bb!=2&&c+cc!=2 且 a+b+c==1&&aa+bb+cc==1
    根据左边人的回答可以推出:若他们是诚实族,则中间的人也是诚实族;若他不是诚实族,则中间的人也不是诚实族。以上条件可以表示为:
    c&&!b&&!bb||(!c&&!cc)&&(b||bb)||!c&&cc
    将全部逻辑条件联合在一起,利用穷举的方法求解,凡是使上述条件同时成立的变量取值就是题目的答案。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a,b,c,aa,bb,cc;
    for(a=0;a<=1;a++) /*穷举全部情况*/
    for(b=0;b<=1;b++)
    for(c=0;c<=1;c++)
    for(aa=0;aa<=1;aa++)
    for(bb=0;bb<=1;bb++)
    for(cc=0;cc<=1;cc++)
    if(a+aa!=2&&b+bb!=2&&c+cc!=2&& /*判断逻辑条件*/
    a+b+c==1&&aa+bb+cc==1 &&
    (a&&!aa&&b&&!bb||!a&&!b)&&
    !b &&
    (c&&!b&&!bb||(!c&&!cc)&&(b||bb)||!c&cc))
    {
    printf("The man stand on left is a %s.\n",
    aa?"double–dealer":(a?"honest":"lier"));
    printf("The man stand on left is a %s.\n",
    bb?"double–dealer":(b?"honest":"lier"));
    printf("The man stand on left is a %s.\n",
    cc?"double–dealer":(c?"honest":"lier")); 
    /*输出最终的推理结果*/
    }
    }
    *运行结果
    The man stand on left is a double–dealer. (左边的人是两面族的)
    The man stand on center is a lier. (中间的人是说谎族的)
    The man stand on right is a honest. (右边的人是诚实族的)
    *思考题
    迷语博士遇到三个人,便问第一个人:“你是什么族的?”,回答:“诚实族的。”问第二个人:“你是什么族的?”,答:“说谎族的。”博士又问第二个人:“第一个人真的是诚实族的吗?”,答:“是的。”问第三个人:“你是什么族的?”,答:“诚实族的。”博士又问第三个人:“第一个人是什么族的?”,答:“两面族的。”
    请判断这个人到底是哪个民族的?
    (答案:第一个人是诚实族的,第二个人是两面族的,第三人是说谎族。) 
    55.哪个大夫哪天值班
    医院有A、B、C、D、E、F、G七位大夫,在一星期内(星期一至星期天)每人要轮流值班一天。现在已知:
    A大夫比C大夫晚一天值班;
    D大夫比E大夫晚二天值班;
    B大夫比G大夫早三天值班;
    F大夫的值班日在B和C大夫的中间,且是星期四;
    请确定每天究竟是哪位大夫值班?
    *问题分析与算法设计
    由题目可推出如下已知条件:
    *F是星期四值班;
    *B值班的日期在星期一至星期三,且三天后是G值班;
    *C值班的日期在星期五至星期六,且一天后是A值班;
    *E两天后是D值班;E值班的日期只能在星期一至星期三;
    在编程时用数组元素的下标1到7表示星期一到星期天,用数组元素的值分别表示A~F七位大夫。
    *程序说明与注释
    #include<stdio.h>
    #include<stdlib.h>
    int a[8];
    char *day[]={"","MONDAY","TUESDAY","WEDNESDAY","THURSDAYT",
    "FRIDAY","SATUDAY","SUNDAY"}; /*建 立星期表*/
    int main()
    {
    int i,j,t;
    a[4]=6; /*星期四是F值班*/ 
    for(i=1;i<=3;i++)
    {
    a[i]=2; /*假设B值班的日期*/
    if(!a[i+3]) a[i+3]=7; /*若三天后无人值班则安排G值班*/
    else{ a[i]=0;continue;} /*否则B值班的日期不断对*/
    for(t=1;t<=3;t++) /*假设E值班的时间*/
    {
    if(!a[t]) a[t]=5; /*若当天无人值班则安排E值班*/
    else continue;
    if(!a[t+2]) a[t+2]=4; /*若E值班两天后无人值班则应为D*/
    else{ a[t]=0;continue;} /*否则E值班的日期不对*/
    for(j=5;j<7;j++)
    {
    if(!a[j]) a[j]=3; /*若当天无人值班,则安排C值班*/
    else continue;
    if(!a[j+1]) a[j+1]=1; /*C之后一天无人值班则应当是A值班*/
    else{ a[j]=0;continue;} /*否则A值班日期不对*/
    for(i=1;i<=7;i++) /*安排完毕,输出结果*/
    printf("Doctor %c is on duty %s.\n",'A'+a[i]-1,day[i]);
    exit(0);
    }
    }
    }
    }
    *运行结果
    Doctor E is on duty MONDAY. (星期一:E)
    Doctor B is on duty TUESDAY. (星期二:B)
    Doctor D is on duty WEDNESDAY. (星期三:D)
    Doctor F is on duty THUESDAY. (星期四:F)
    Doctor G is on duty FRIDAY. (星期五:G)
    Doctor C is on duty SATURDAY. (星期六:C)
    Doctor A is on duty SUNDAY. (星期日:A)
    *思考题
    在本题的求解过程中,我们只考虑了一星期之内的情况,没有考虑跨周的情况。对于“B大夫比G大夫早三天值班的”条件只是简单的认为是在同一周内早三天。若考虑跨周的情况就可能出现:B大夫星期一值班,而G大夫是上周的星期五。同样,对“F大夫的值班日在B和C大夫的中间”这个条件,也可以扩展为:“只要F大夫的值班日在B和C大夫的中间就可以”。
    请考虑允许跨周的情况下,可能的时间安排表。
     
    56.区分旅客国籍
    在一个旅馆中住着六个不同国籍的人,他们分别来自美国、德国、英国、法国、俄罗斯和意大利。他们的名字叫A、B、C、D、E和F。名字的顺序与上面的国籍不一定是相互对应的。现在已知:
    1)A美国人是医生。
    2)E和俄罗斯人是技师。
    3)C和德国人是技师。
    4)B和F曾经当过兵,而德国人从未参过军。
    5)法国人比A年龄大;意大利人比C年龄大。
    6)B同美国人下周要去西安旅行,而C同法国人下周要去杭州度假。
    试问由上述已知条件,A、B、C、D、E和F各是哪国人?
    *问题分析与算法设计
    首先进行题目分析,尽可能利用已知条件,确定谁不是哪国人。
    由:1) 2) 3)可知:A不是美国人,E不是俄罗斯人,C不是德国人。另外因为A与德国人的职业不同,E与美、德人的职业不同,C与美、俄人的职业不同,故A不是俄罗斯人或德国人,E不是美国人或德国人,C不是美国人或俄罗斯人。
    由4)和5)可知B和F不是德国人,A不是法国人,C不是意大利人。
    由6)可知B不是美国人,也不是法国人(因B与法国人下周的旅行地点不同);C不是法国人。
    将以上结果汇总可以得到下列条件矩阵:
    . 美(医生) 英 法 德(技师) 意大利 俄(教师) 
    A(医生) X . X X . X 
    B X . X X . . 
    C(技师) X . X X X X 
    D . . . . . . 
    E(教师) X . . X . X 
    F . . . X . . 
    根据此表使用消元法进行求解,可以方便地得到问题的答案。
    将条件矩阵输入计算机,用程序实现消去算法是很容易的。
    *程序说明与注释
    #include<stdio.h>
    char *m[7]={" ","U.S","U.K","FRANCE","GER","ITALI","EUSSIAN"}; /*国名*/
    int main()
    {
    int a[7][7],i,j,t,e,x,y;
    for(i=0;i<7;i++) /*初始化条件矩阵*/
    for(j=0;j<7;j++) /*行为人,列为国家,元素的值表示某人是该国人*/
    a[i][j]=j;
    for(i=1;i<7;i++) /*条件矩阵每一列的第0号元素作为该列数据处理的标记*/
    a[0][i]=1; /*标记该列尚未处理*/
    a[1][1]=a[2][1]=a[3][1]=a[5][1]=0; /*输入条件矩阵中的各种条件*/
    a[1][3]=a[2][3]=a[3][3]=0; /*0表示不是该国的人*/
    a[1][4]=a[2][4]=a[3][4]=a[5][4]=a[6][4]=0;
    a[3][5]=0;
    a[1][6]=a[3][6]=a[5][6]=0;
    while(a[0][1]+a[0][2]+a[0][3]+a[0][4]+a[0][5]+a[0][6]>0)
    { /*当所有六列均处理完毕后退出循环*/
    for(i=1;i<7;i++) /*i:列坐标*/
    if(a[0][i]) /*若该列尚未处理,则进行处理*/
    {
    for(e=0,j=1;j<7;j++) /*j:行坐标 e:该列中非0元素计数器*/
    if(a[j][i]) { x=j;y=i;e++;}
    if(e==1) /*若该列只有一个元素为非零,则进行消去操作*/
    {
    for(t=1;t<7;t++)
    if(t!=i)a[x][t]=0; /*将非零元素所在的行的其它元素置0*/
    a[0][y]=0; /*设置该列已处理完毕的标记*/
    }
    }
    }
    for(i=1;i<7;i++) /*输出推理结果*/
    {
    printf("%c is coming from ",'A'-1+i);
    for(j=1;j<7;j++)
    if(a[i][j]!=0)
    { printf("%s.\n",m[a[i][j>); break;}
    }
    }
    *运行结果
    A is coming from ITALY. (意大利人)
    B is coming from EUSSIAN. (俄罗斯人)
    C is coming from U.K.. (英国人)
    D is coming from GER. (德国人)
    E is coming from FRANCE. (法国人)
    F is coming from U.S.. (美国人)
    *问题的进一步讨论
    生成条件矩阵然后使用消去法进行推理判断是一种常用的方法。对于解决较为复杂的逻辑问题是十分有效的。
    *思考题
    地理课上老师给出一张没有说明省份的中国地图,从中选出五个省从1到5编号,要大家写出省份的名称。交卷后五位同学每人只答了二个省份的名称如下,且每人只答对了一个省,问正确答案是什么?
    A 答:2号陕西,5号甘肃 B 答:2号湖北,4号山东
    C 答:1号山东,5号吉林 D 答:3号湖北,4号吉林
    E 答:2号甘肃,3号陕西
    57.谁家孩子跑最慢
     
    张王李三家各有三个小孩。一天,三家的九个孩子在一起比赛短跑,规定不分年龄大小,跑第一得9分,跑第2得8分,依此类推。比赛结果各家的总分相同,且这些孩子没有同时到达终点的,也没有一家的两个或三个孩子获得相连的名次。已知获第一名的是李家的孩子,获得第二的是王家的孩子。问获得最后一名的是谁家的孩子?
    *问题分析与算法设计
    按题目的条件,共有1+2+3+…+9=45分,每家的孩子的得分应为15分。根据题意可知:获第一名的是李家的孩子,获第二名的是王家的孩子,则可推出:获第三名的一定是张家的孩子。由“这些孩子没有同时到达终点的”可知:名次不能并列,由“没有一家的两个或三个孩子获得相连的名次”可知:第四名不能是张家的孩子。
    程序中为了方便起见,直接用分数表示。
    *程序说明与注释
    #include<stdio.h>
    int score[4][4];
    int main()
    {
    int i,j,k,who;
    score[1][1]=7; /*按已知条件进行初始化:score[1]:张家三个孩子的得分*/ 
    score[2][1]=8; /*score[2]:王家三个孩子的得分*/
    score[3][1]=9; /*李家三个孩子的得分*/
    for(i=4;i<6;i++) /*i:张家孩子在4到6分段可能的分数*/
    for(j=4;j<7;j++) /*j:王家孩子在4到6分段可能的分数*/
    for(k=4;i!=j&&k<7;k++) /*k:李家孩子在4到6分段可能的分数*/
    if(k!=i&&k!=j&&15-i-score[1][1]!=15-j-score[2][1] /*分数不能并列*/
    &&15-i-score[1][1]!=15-k-score[3][1]
    &&15-j-score[2][1]!=15-k-score[3][1])
    {
    score[1][2]=i;score[1][3]=15-i-7; /*将满足条件的结果记入数组*/
    score[2][2]=j;score[2][3]=15-j-8;
    score[3][2]=k;score[3][3]=15-k-9;
    }
    for(who=0,i=1;i<=3;i++,printf("\n"))
    for(j=1;j<=3;j++)
    {
    printf("%d",score[i][j]); /*输出各家孩子的得分情况*/
    if(score[i][j]==1)who=i; /*记录最后一名的家庭序号*/
    }
    if(who==1) /*输出最后判断的结果*/
    printf("The last one arrived to end is a child from family Zhang.\n");
    else if(who==2)
    printf("The last one arrived to end is a child from family Wang.\n");
    else printf("The last one arrived to end is a child from family Li.\n");
    }
    *运行结果
    7 5 3
    8 6 1
    9 4 2
    The last one arrived to end is a child from family Wang.
    (获得最后一名的是王家的孩子。
    58.拉丁方阵
    构造 NXN 阶的拉丁方阵(2<=N<=9),使方阵中的每一行和每一列中数字1到N只出现一次。如N=4时:
    1 2 3 4
    2 3 4 1
    3 4 1 2
    4 1 2 3
    *问题分析与算法设计
    构造拉丁方阵的方法很多,这里给出最简单的一种方法。观察给出的例子,可以发现:若将每 一行中第一列的数字和最后一列的数字连起来构成一个环,则该环正好是由1到N顺序构成;对于第i行,这个环的开始数字为i。按照 此规律可以很容易的写出程序。下面给出构造6阶拉丁方阵的程序。
    *程序说明与注释
    #include<stdio.h>
    #define N 6 /*确定N值*/
    int main()
    {
    int i,j,k,t;
    printf("The possble Latin Squares of order %d are:\n",N);
    for(j=0;j<N;j++) /*构造N个不同的拉丁方阵*/
    {
    for(i=0;i<N;i++)
    {
    t=(i+j)%N; /*确定该拉丁方阵第i 行的第一个元素的值*/
    for(k=0;k<N;k++) /*按照环的形式输出该行中的各个元素*/
    printf("%d",(k+t)%N+1);
    printf("\n");
    }
    printf("\n");
    }
    }
    *运行结果
    The possble Latin Squares of order 6 are:
    1 2 3 4 5 6 2 3 4 5 6 1 3 4 5 6 1 2
    2 3 4 5 6 1 3 4 5 6 1 2 4 5 6 1 2 3
    3 4 5 6 1 2 4 5 6 1 2 3 5 6 1 2 3 4
    4 5 6 1 2 3 5 6 1 2 3 4 6 1 2 3 4 5
    5 6 1 2 3 4 6 1 2 3 4 5 1 2 3 4 5 6
    6 1 2 3 4 5 1 2 3 4 5 6 2 3 4 5 6 1
    4 5 6 1 2 3 5 6 1 2 3 4 6 1 2 3 4 5
    5 6 1 2 3 4 6 1 2 3 4 5 1 2 3 4 5 6
    6 1 2 3 4 5 1 2 3 4 5 6 2 3 4 5 6 1
    1 2 3 4 5 6 2 3 4 5 6 1 3 4 5 6 1 2
    2 3 4 5 6 1 3 4 5 6 1 2 4 5 6 1 2 3
    3 4 5 6 1 2 4 5 6 1 2 3 5 6 1 2 3 4 
     
    59.填表格
    将1、2、3、4、5和6 填入下表中,要使得每一列右边的数字比左边的数字大,每一行下面的数字比上面的数字大。按此要求,可有几种填写方法?
       
       
     
    *问题分析与算法设计
    按题目的要求进行分析,数字1一定是放在第一行第一列的格中,数字6一定是放在第二行第三列的格中。在实现时可用一个一维数组表示,前三个元素表示第一行,后三个元素表示第二行。先根据原题初始化数组,再根据题目中填 写数字的要求进行试探。
    *程序说明与注释
    #include<stdio.h>
    int jud1(int s[]);
    void print(int u[]);
    int count; /*计数器*/
    int main()
    {
    static int a[]={1,2,3,4,5,6}; /*初始化数组*/
    printf("The possble table satisfied above conditions are:\n");
    for(a[1]=a[0]+1;a[1]<=5;++a[1]) /*a[1]必须大于a[0]*/
    for(a[2]=a[1]+1;a[2]<=5;++a[2]) /*a[2]必须大于a[1]*/
    for(a[3]=a[0]+1;a[3]<=5;++a[3]) /*第二行的a[3]必须大于a[0]*/
    for(a[4]=a[1]>a[3]?a[1]+1:a[3]+1;a[4]<=5;++a[4])
    /*第二行的a[4]必须大于左侧a[3]和上边a[1]*/
    if(jud1(a)) print(a); /*如果满足题意,打印结果*/
    }
    int jud1(int s[])
    {
    int i,l;
    for(l=1;l<4;l++)
    for(i=l+1;i<5;++i)
    if(s[l]==s[i]) return 0; /*若数组中的数字有重复的,返回0*/
    return 1; /*若数组中的数字没有重复的,返回1*/
    }
    void print(int u[])
    {
    int k;
    printf("\nNo.:%d",++count);
    for(k=0;k<6;k++)
    if(k%3==0) /*输出数组的前三个元素作为第一行*/
    printf("\n%d",u[k]);
    else /*输出数组的后三个元素作为第二行*/
    printf("%d",u[k]);
    }
    *运行结果
    The possble table satisfied above conditions are:
    No.1: No.2: No.3: No.4: No.5:
    1 2 3 1 2 4 1 2 5 1 3 4 1 3 5
    4 5 6 3 5 6 3 4 6 2 5 6 2 4 6 
     
    60.1~9分成1:2:3的三个3位数
    将1到9 这九个数字分成三个3位数,分求第一个3位数,正好是第二个3位数的二倍,是第三个3位数的三倍。问应当怎样分法。
    *问题分析与算法设计
    问题中的三个数之间是有数学关系的,实际上只要确定第一个三位数就可以解决问题。
    试探第一个三位数之后,计算出另外两个数,将其分别分解成三位数字,进行判断后确定所试探的数是否就是答案。
    需要提醒的是:试探的初值可以是123,最大值是333。因为不可能超出该范围。
    *程序与程序设计
    #include<stdio.h>
    int ok(int t,int *z);
    int a[9];
    int main()
    {
    int m,count=0;
    for(m=123;m<=333;m++) /*试探可能的三位数*/
    if(ok(m,a)&&ok(2*m,a+3)&&ok(3*m,a+6)) /*若满足题意*/
    printf("No.%d: %d %d %d\n",++count,m,2*m,3*m); /*输出结果*/
    }
    int ok(int t,int *z) /*分解t的值,将其存入z指向的三个数组元素,若满足要求返回1*/
    {
    int *p1,*p2;
    for(p1=z;p1<z+3;p1++)
    {
    *p1=t%10; /*分解整数*/
    t/=10;
    for(p2=a;p2<p1;p2++) /*查询分解出的数字是否已经出现过*/
    if(*p1==0||*p2==*p1)return 0; /*若重复则返回*/
    }
    return 1; /*否则返回1*/
    }
    *运行结果
    No.1:192 384 576
    No.2:219 438 657
    No.3:273 546 819
    No.4:327 654 981
    *思考题
    求出所有可能的以下形式的算式,每个算式中有九个数位,正好用尽1到9这九个数字。
    1)○○○+○○○=○○○ (共有168种可能的组合)
    2)○×○○○○=○○○○ (共有2种可能的组合)
    3)○○×○○○=○○○○ (共有7种可能的组合)
    4)○×○○○=○○×○○○ (共有13种可能的组合)
    5)○×○○○=○×○○○○ (共有28种可能的组合)
    6)○○×○○=○×○○○○ (共有7种可能的组合)
    7)○○×○○=○○×○○○ (共有11种可能的组合) 


    C/C++语言经典、实用、趣味程序设计编程百例精解(7) 
    61.1~9组成三个3位的平方数
     
    将1、2、3、4、5、6、7、8、9九个数字分成三组,每个数字只能用一次,即每组三个数不允许有重复数字,也不许同其它组的三个数字重复,要求每组中的三位数都组成一个平方数。
    *问题分析与算法设计
    本问题的思路很多,这里介绍一种简单快速的算法。
    首先求出三位数中不包含0且是某个整数平方的三位数,这样的三位数是不多的。然后将满足条件的三位数进行组合,使得所选出的3个三位数的9个数字没有重复。
    程序中可以将寻找足条件的三位数的过程和对该三位数进行数字分解的过程结合起来。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a[20],num[20][3],b[10]; /*a:存放满足条件的三位数*/
    /*若不是10 的倍数,则分解三位数*/
    /*分解该三位数中的每一个数字*/
    int i,j,k,m,n,t,flag;
    printf("The 3 squares with 3 different digits each are:\n");
    for(j=0,i=11;i<=31;i++) /*求出是平方数的三位数*/
    if(i%10!=0) /*若不是10的倍数,则分解三位数*/
    {
    k=i*i; /*分解该三位数中的每一个数字*/
    num[j+1][0]=k/100; /*百位*/
    num[j+1][1]=k/10%10; /*十位*/
    num[j+1][2]=k%10; /*个位*/
    if(!(num[j+1][0]==num[j+1][1]||num[j+1][0]==num[j+1][2]||
    num[j+1][1]==num[j+1][2])) /*若分解的三位数字均不相等*/
    a[++j]=k; /*j:计数器,统计已找到的满足要求的三位数*/
    }
    for(i=1;i<=j-2;++i) /*从满足条件的三位数中选出三个进行组合*/
    {
    b[1]=num[i][0];
    b[2]=num[i][1];
    b[3]=num[i][2];
    for(t=i+1;t<=j-1;++t)
    {
    b[4]=num[t][0]; /*取第t个数的三位数字*/
    b[5]=num[t][1];
    b[6]=num[t][2];
    for(flag=0,m=1;!flag&&m<=3;m++) /*flag:出现数字重复的标记*/
    for(n=4;!flag&&n<=6;n++) /*判断两个数的数字是否有重复*/
    if(b[m]==b[n])flag=1; /*flag=1:数字有重复*/
    if(!flag)
    for(k=t+1;k<=j;k++)
    {
    b[7]=num[k][0]; /*取第k个数的三位数字*/
    b[8]=num[k][1];
    b[9]=num[k][2];
    for(flag=0,m=1;!flag&&m<=6;m++) /*判断前两个数字是否*/
    for(n=7;!flag&&n<=9;n++) /*与第三个数的数字重复*/
    if(b[m]==b[n])flag=1;
    if(!flag) /*若均不重复则打印结果*/
    printf("%d,%d,%d\n",a[i],a[t],a[k]);
    }
    }
    }
    }
    *运行结果
    The 3 squares with 3 different digits each are:
    361,529,784
    *思考题
    将1、2、3、4、5、6、7、8、9九个数字分成二组,每个数字只能用一次,一组形成一个5位数,另一组形成一个4位数,使得前者为后者的n倍。求所有满足条件的5位数和4位数。(注意:N的最大值等于68,68以内的某些N也是不可能的。不可能的N值包括:1、10、11、20、21、25、30、31等共32个。)
     
    62.由8个整数形成奇特的立方体
    任意给出8个整数,将这8个整数分别放在一个立方体的八个顶点上,要求每个面上的四个数之和相等。
    *问题分析与算法设计
    简化问题:将8个顶点对应数组中的8个元素,将“每个面上的四个数之和皆相等”转换为数组无素之间和的相等关系。这里的关键在于正确地将立方体的8个顶点与数组的8个元素对应。
    可以利用简单的穷举方法建立8个数的全部排列。
    *程序说明与注释
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    int a[9],ii=0,i,a1,a2,a3,a4,b1,b2,b3,b4,flag;
    for(i=1;i<=8;i++) /*输入个数*/
    {
    printf("Please enter [%d]number:",i);
    scanf("%d",&a[i]);
    ii+=a[i];
    }
    printf("******************************************\n");
    if(ii%2) /*和为奇数则输入的8个数不可用*/
    {
    printf("Sorry they can't be constructed required cube!\n");
    exit(0);
    }
    for(flag=0,a1=1;a1<=8;a1++) /*flag:完成标记.flag=1;表示完成*/
    for(a2=1;a2<=8;a2++) /*采用八重循环建立八个整数的全排列*/
    if(a2!=a1) /*前两个数不能相同*/
    for(a3=1;a3<=8;a3++) 
    if(a3!=a2&&a3!=a1) /*前三个数不能相同*/
    for(a4=1;a4<=8;a4++)
    if(a4!=a3&&a4!=a2&&a4!=a1) /*前四个数不能相同*/
    for(b1=1;b1<=8;b1++)
    if(b1!=a4&&b1!=a3&&b1!=a2&&b1!=a1) /*不能相同*/
    for(b2=1;b2<=8;b2++)
    if(b2!=b1&&b2!=a4&&b2!=a3&&b2!=a2&&b2!=a1)
    for(b3=1;b3<=8;b3++)
    if(b3!=b2&&b3!=b1&&b3!=a4&&b3!=a3&&b3!=a2&&b3!=a1)
    /*不能取相同的数*/
    for(b4=1;b4<=8;b4++)
    if(b4!=b2&&b4!=b1&&b4!=b3&&b4!=a4&&b4!=a3&&b4!=a2&&b4!=a1)
    if(a[b1]+a[b2]+a[b3]+a[b4]==ii/2
    &&a[a1]+a[a2]+a[b1]+a[b2]==ii/2
    &&a[a1]+a[a4]+a[b1]+a[b4]==ii/2)
    {
    flag=1;goto out; /*满足条件则将flag置1后退出*/
    }
    out:
    if(flag)
    {
    printf("They can be constructed required cube as follow:\n");
    printf(" /%2d…………/%2d\n",a[a4],a[a3]);
    printf(" %2d/…………%2d/|\n",a[a1],a[a2]);
    printf(" | | | |\n");
    printf(" | | | |\n");
    printf(" | %2d| | |%2d\n",a[b4],a[b3]);
    printf(" /……………./\n");
    printf(" %2d/………….%2d/\n",a[b1],a[b2]);
    }
    else printf("Sorry they can't be constructed required cube!\n");
    }
    *运行结果
    Please enter [1] number:20
    Please enter [2] number:45
    Please enter [3] number:39
    Please enter [4] number:25
    Please enter [5] number:29
    Please enter [6] number:7
    Please enter [7] number:3
    Please enter [8] number:2
    Sorry they can't be constructed required cube! 
    *思考题
    程序中建立全排列的方法效率太低,算法虽然简单但程序过于冗余。请读者自行设计新的算法完成同样的工作。 
    63.减式还原
     
    编写程序求解下式中各字母所代表的数字,不同的字母代表不同的数字。
    PEAR
    - ARA
    ——–
    PEA
    *问题分析与算法设计
    类似的问题从计算机算法的角度来说是比较简单的,可以采用最常见的穷举方法解决。程序中采用循环穷举每个字母所可能代表的数字,然后将字母代表的数字转换为相应的整数,代入算式后验证算式是否成立即可解决问题。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int p,e,a,r;
    for(p=1;p<=9;p++) /*从1到9穷举字母p的全部可能取值*/
    for(e=0;e<=9;e++) /*从0到穷举字母e的全部可能取值*/
    if(p!=e) /*p不等于e*/ 
    for(a=1;a<=9;a++) /*从0到9穷举字母a的全部可能取值*/
    if(a!=p&&a!=e) 
    for(r=0;r<=9;r++) /*从0到9穷举字母r的全部可能取值*/
    if(r!=p&&r!=e&&r!=a&&p*1000+e*100+a*10+r-(a*100+r*10+a)
    ==p*100+e*10+a)
    {
    printf(" PEAR %d%d%d%d\n",p,e,a,r);
    printf(" -ARA - %d%d%d\n",a,r,a);
    printf("…………………….\n");
    printf(" PEA %d%d%d\n",p,e,a);
    }
    }
    *运行结果
    PEAR 1098
    - ARA - 989
    ———- ——
    PEA 109
    *思考题
    请复原下面的和式。不同的字母代表不同的数字。
    SEVEN 82524 82526
    THREE 19722 19722
    + TWO 答案: + 106 + 104
    ———- ———– ———–
    TWELVE 102352 102352 
     64.乘式还原
    A代表数字0到9中的前五个数字,Z代表后五个数字,请还原下列乘式。
    A Z A
    × A A Z
    ————
    A A A A
    A A Z Z
    Z A A
    ————
    Z A Z A A
    *问题分析与算法设计
    问题本身并不复杂,可以对乘式中的每一位使用穷举法,最终可以得到结果。本题的关键在于怎样有效的判断每个部分积的每一位是否满足题意,这一问题处理不好,编写的程序会很长。程序实现中采用了一个判断函数,通过传入函数的标志字符串对所有的数进行统一的判断处理。
    *程序说明与注释
    #include<stdio.h>
    void print(long a,long b,long s1,long s2,long s3);
    int jud(long q,char *pflag);
    int main()
    {
    long i,j,k,l,m,n,term,t1,t2,t3;
    int flag;
    for(i=0;i<=4;++i) /*被乘数的第一位*/
    for(j=5;j<=9;++j) /*被乘数的第二位*/
    for(k=0;k<=4;++k) /*被乘数的第三位*/
    {
    term=100*i+10*j+k; /*被乘数*/
    for(flag=0,n=0;n<4&&!flag;) /*乘数的第一位*/
    flag=jud((t3=++n*100*term)/100,"001"); /*判断第三个部分积*/
    if(flag)
    {
    for(flag=0,m=0;m<4&&!flag;) /*乘数的第二位*/
    flag=jud((t2=++m*10*term)/10,"1100"); /*判断第二个部分积*/
    if(flag)
    {
    for(flag=0,l=5;l<9&&!flag;) /*乘数的第三位*/
    flag=jud(t1=++l*term,"0000"); /*判断第一个部分积*/
    if(flag&&jud(t1+t2+t3,"00101")) /*判断乘式的积*/
    print(term,n*100+m*10+l,t1,t2,t3);
    }
    }
    }
    }
    void print(long a,long b,long s1,long s2,long s3) /*打印结果*/ 
    {
    printf("\n %ld\n",a);
    printf("*) %ld\n",b);
    printf("………………….\n");
    printf(" %ld\n %ld\n %ld\n",s1,s2/10,s3/100);
    printf("………………….\n");
    printf(" %ld\n",a*b);
    }
    int jud(long q,char *pflag) /*判断一个数的每一位是否满足要求的判断函数*/
    /*q:需要判断的数。pflag:标志字符串,A用1表示,Z用0表示。标志串排列顺序:个十百…*/
    {
    while(q!=0&&*pflag!=NULL) /*循环判断对应位的取值范围是否正确*/
    if(*pflag-'0'!=(q%10>=5?1:0)) /*标志位与对应的位不符,返回0*/
    return 0;
    else 
    {
    q/=10;++pflag; /*若相符则取下一位进行判断*/
    }
    if(q==0&&*pflag==NULL) /*q的位数与标志字符串的长度相同时,返回1*/
    return 1;
    else return 0;
    }
    *运行结果
    3 7 2
    × 2 4 6
    ———-
    2 2 3 2
    1 4 8 8
    7 4 4
    ————
    9 1 5 1 2
     
    *思考题
    E代表数字0到9中的偶数数字,O代表奇数数字,请还原下列乘式。
    E E O 2 8 5
    × O O 答案 × 3 9
    ———– ———–
    E O E O 2 5 6 5
    E O O 8 5 5
    ———– ———–
    O O O O O 1 1 1 1 5 
    65.乘式还原(2)
    有乘法算式如下:
    ○○○
    × ○○
    ————
    ○○○○
    ○○○○
    ————
    ○○○○○ 
    18个○的位置上全部是素数(1、3、5或7),请还原此算式。
    *问题分析与算法设计
    问题中虽然有18数位,但只要确定乘数和被乘数后经过计算就可确定其它的数位。
    乘数和被乘数共有5个数位,要求每个数都是质数。完全可以采用穷举的方法对乘数和被乘数进行穷举,经过判断后找出答案。但是这种方法给人的感觉是“太笨了”,因为组成的数字只是质数(4个),完全没有必要在那么大的范围内进行穷举,只需要试探每一位数字为质数时的情况即可。
    采用五重循环的方法实现对于5个数字的穷举,前面的许多例题中都已见过。循环实现简单易行,但嵌套的层次太多,需要穷举的变量的数量直接影响到循环嵌套的层数,这种简单的实现方法缺少技巧性。本例的程序中给出了另外一种同样功能的算法,该算法的实现思想请阅读程序。
    程序中并没有直接对质数进行穷举,而是将每个质数与1到4顺序一一对应,在穷举时为处理简单仅对1到4进行穷举处理,待要判断产生的乘积是否满足条件时再利用一个数组完成向对应质数的转换。请体会程序中的处理方法。程序中使用的算法实际上是回朔法。
    *程序说明与注释
    #include<stdio.h>
    #define NUM 5 /*需要穷举的变量数目*/
    #define C_NUM 4 /*每个变量的值的变化范围*/
    int a[NUM+1]; /*为需要穷举的变量开辟的数组*/
    /*a[1]:被乘数的百位,a[2]:十位,aa[3]:个位 a[4]:被乘数的十位 a[5]:个位*/
    int b[]={0,2,3,5,7}; /*存放质数数字的数组,不使用第0号元素*/
    int f(long sum);
    int main()
    {
    int i,not_finish=1;
    i=2; /*i:将要进行处理的元素的指针下标。设置初始值*/
    a[1]=1; /*为第1号元素设置初始值*/
    while(not_finish) /*not_finish:程序运行没结束标记*/
    {
    while(not_finish&&i<=NUM)
    /*处理包括第i个元素在内的后续元素,找出当前条件下的一种各个变量的一种可能的取值方法*/
    if(a[i]>=C_NUM) /*当要处理的元素取超过规定的C_NUM时*/
    if(i==1&&a[1]==C_NUM)
    not_finish=0; /*若1号元素已经到C_NUM,则处理全部结束*/
    else a[i–]=0; /*将要处理的元素置0,下标-1(回退一个元素)*/
    else a[i++]++; /*当前元素值加1后下标指针加1*/
    if(not_finish)
    {
    long int sum1,sum2,sum3,sum4; /*定义临时变量*/
    sum1=b[a[1>*100+b[a[2>*10+b[a[3>; /*计算被乘数*/
    /*利用数组的下标与质数的对应关系完成序号1到4向质数的转换*/
    sum2=sum1*b[a[5>; /*计算乘数个位与被乘数的部分积*/
    sum3=sum1*b[a[4>; /*计算乘数十位与被乘数的部分积*/
    if(sum2>=2222&&sum2<=7777&&f(sum2)&&sum3>=2222&&sum3<=7777&&f(sum3))
    /*判断两部分积是否满足题目条件*/
    if((sum4=sum2+sum3*10)>=22222&&sum4<=77777&&f(sum4))
    /*判断乘式的积是否满足题目条件*/
    {
    printf(" %d\n",sum1); /*若满足题意,则打印结果*/ 
    printf("* %d%d\n",b[a[4>,b[a[5>);
    printf("……………………\n");
    printf(" %d\n",sum2);
    printf(" %d\n",sum3);
    printf("……………………\n");
    printf(" %d\n",sum4);
    }
    i=NUM; /*为穷举下一个可能取值作准备*/
    }
    }
    }
    int f(long sum) /*判断sum的每一位数字是否是质数,若不是返回0,若是返回1*/
    {
    int i,k,flag; /*flag=1:数字是质数的标记*/
    while(sum>0) 
    {
    i=sum%10; /*取个位的数字*/
    for(flag=0,k=1;!flag&&k<=C_NUM;k++)
    if(b[k]==i)
    {
    flag=1;break;
    }
    if(!flag) return 0;
    else sum=sum/10;
    }
    return 1;
    }
    *运行结果
    7 7 5
    × 3 3
    ———-
    2 3 2 5
    2 3 2 5
    ———–
    2 5 5 7 5
     
    *思考题
    以下乘式中,A、B、C代表一确定的数字,○代表任意数字,请复原。
    A B C 2 8 6
    × B A C × 8 2 6
    ————- 答案: ————
    ○○○○ 1 7 1 6
    ○○A 5 7 2
    ○○○B 2 2 8 8
    ————- —————-
    ○○○○○○ 2 3 6 2 3 6 
    66.除式还原(1)
    给定下列除式,其中包含5个7,其它打×的是任意数字,请加以还原。
    × 7 × ————–商
    ————–
    除数——××| ×××××————-被除数
    ×7 7
    ————–
    × 7 ×
    × 7 ×
    ———-
    × ×
    × ×
    ———-

    *问题分析与算法设计
    首先分析题目,由除式本身尽可能多地推出已知条件。由除式本身书已知:
    1、被除数的范围是10000到99999,除数的范围是10到99,且可以整除;
    2、商为100到999之间,且十位数字为7;
    3、商的第一位与除数的积为三位数,且后两位为77;
    4、被除数的第三位一定为4;
    5、 7乘以除数的积为一个三位数,且第二位为7;
    6、商的最后一位不能为0,且与除数的积为一个二位数。
    由已知条件就可以采用穷举的方法找出结果。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    long int i;
    int j,l;
    for(i=10000;i<=99999;i++) /*1. i:被除数*/
    if(i%1000-i%100==400) /*4. 被除数的第三位一定为4*/
    for(j=10;j<=99;j++) /*1. j: 余数*/
    if(i%j==0&&(l=i/j)%100>=70&&l%100<80&&l%10!=0&&l>100&&l<=999)
    /*1. 可以整除&& 2.商l在100到999之间且十位数字为7&&6.商的个数不能为0*/
    if((j*(l%10))<100&&j*(l%10)>10) /*6. 商的个数与除数的积为二位数*/
    if(j*7%100>=70&&j*7%100<80) /*5. 7乘以除数的积的第二位为7*/
    if(j*(l/100)%100==77&&j*(l/100)>100)
    /*商的第一位与除数的积的后两位为77*/
    printf("%ld/%ld=%d\n",i,j,l);
    }
    *运行结果
    51463/53=971。 
    可以看作为下列算式:
    9 7 1
    ————-
    5 3| 5 1 4 6 3
    4 7 7
    ————-
    3 7 6
    3 7 1
    ———–
    5 3
    5 3
    ———–

    *问题的进一步讨论
    在推出的已知条件中,几所有的条件都是十分明显的,换句话说,推出的已知条件就是对题目的平铺直叙。这种推已知条件的方法十分简单,并且行之有效。
    *思考题
    下列除式中仅给定了一个8,其它打×的位置上是任意数字,请还原。
    × 8 × —————-商
    —————-
    除数——-×××| ××××××—————被除数
    ××××
    —————
    ×××
    ×××
    —————
    ××××
    ××××
    —————
    ○ 
     
    67.除式还原(2)
    下列除式中仅在商中给定了一个7,其它打×的位置全部是任意数字,请还原。
    ×7××× ————-商
    —————— 
    除数 ——————-×××| ×××××××× ————-被除数
    ×××× ————-1)
    ————— 
    ××× ————-2)
    ××× ————-3)
    —————
    ×××× ————-4)
    ××× ————-5)
    —————–
    ×××× ————-6)
    ×××× ————-7)
    —————–
    0
    *问题分析与算法设计
    这道题是不可能用单纯的穷举法求解的,一则计算时间太长,二则难于求出除式中各部分的值。
    对除式进行分析,改可能多地推出限制条件:
    由3)可以看出,商的第二位7乘除数得一个三位数,所以除数<=142。
    由除数乘商的第一位为一个四位数可知,商的第一位只能为8或9且除数>=112。同时商的第五位也为8或9数的前四位一定<=142*9+99且>=1000+10。
    由4)、5)、6)可以看出,4)的前两位一定为“10”;5)的第一位一定为“9”;6)的前两位一定在10到99之间;商的第四位一定为为0。
    由 5)的第一位一定是“9”和“112”<=除数<=142可知:商的第三位可能为7或8。
    由除式本身可知:商的第四位为0。
    由 1)可知:除数X商的第一位应当为一个四位数。
    由 5)可知:除数X商的第三位应当为一个三位数。
    编程时为了方便,将被除数分解:前四位用a[0]表示,第五位用a[1],第六位用a[2],第七八两位用a[3];除数用变量b表示;分解商:第一位用c[0],第五位用c[2];其它的部分商分别表示为:2)的前两位为d[0],4)的前三位为d[1],6)的前二位为d[2]。将上述分析用数学的方法综合起来可以表示为:
    被除数: 1010<=a[0]<=1377 0<=a[1]<=9
    0<=a[2]<=9 0<=a[3]<=99
    除数: 112<=b <=142
    商: 8<=c[0]<=9 7<=c[1]<=8 8<=c[2]<=9
    2)的前两位: 10<=d[0]<=99
    4)的前三位: 100<=d[1]<b
    6)的前两位: 10<=d[2]<=99
    1)式部分积: b*c[0]>1000
    5)式部分积: 100<b*c[1]<1000
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a[4],b,c[3],d[4],i=1;
    for(a[0]=1010;a[0]<=1377;a[0]++)
    for(b=112;b<=142;b++)
    for(c[0]=8;c[0]<=9;c[0]++)
    if(b*c[0]>1000&&(d[0]=a[0]-b*c[0])>=10&&d[0]<100)
    for(a[1]=0;a[1]<=9;a[1]++)
    if((d[1]=d[0]*10+a[1]-b*7)>=100&&d[1]<b)
    for(a[2]=0;a[2]<=9;a[2]++)
    for(c[1]=7;c[1]<=8;c[1]++)
    if(b*c[1]<1000&&(d[2]=d[1]*10+a[2]-b*c[1])>=10&&d[2]<100)
    for(a[3]=0;a[3]<=99;a[3]++)
    for(c[2]=8;c[2]<=9;c[2]++)
    if(d[2]*100+a[3]-b*c[2]==0)
    {
    printf("No%2d:",i++);
    printf("%d%d%d%d%d/",a[0],a[1],a[2],a[3]/10,a[3]%10);
    printf("%d=",b);
    printf("%d%d%d%d%d\n",c[0],7,c[1],0,c[2]);
    }
    }
    *运行结果:
    No 1:12128316/124=97809
    *思考题
    下列除式中“×”所在的位置全部是任意数字,请还原。
    ×××××
    ——————-
    ××× | ××××××××
    ××××
    ——————
    ××××
    ×××
    —————
    ×××
    ×××
    ———–
    ××××
    ××××
    ———–

    68.九位累进可除数
    求九位累进可除数。所谓九位累进可除数就是这样一个数:这个数用到1到9这九个数字组成,每个数字刚好只出现一次。这九个位数的前两位能被2整除,前三位能被3整除……前N位能被N整除,整个九位数能被9整除。
    *问题分析与算法设计
    问题本身可以简化为一个穷举问题:只要穷举每位数字的各种可能取值,按照题目的要求对穷举的结果进行判断就一定可以得到正确的结果。
    问题中给出了“累进可除”这一条件,就使得我们可以在穷举法中加入条件判断。在穷举的过程中,当确定部分位的值后,马上就判断产生的该部分是否符合“累进可除”条件,若符合,则继续穷举下一位数字;否则刚刚产生的那一位数字就是错误的。这样将条件判断引入到穷举法之中,可以尽可能早的发现矛盾,尽早地放弃不必要穷举的值,从而提高程序的执行效率。
    为了达到早期发现矛盾的目的,不能采用多重循环的方法实行穷举,那样编出的程序质量较差。程序中使用的算法不再是穷举法,而是回朔法。
    *程序说明与注释
    #include<stdio.h>
    #define NUM 9
    int a[NUM+1];
    int main()
    {
    int i,k,flag,not_finish=1;
    long sum;
    i=1;
    /*i:正在处理的数组元素,表示前i-1个元素已经满足要求,正处理的是第i个元素*/
    a[1]=1; /*为元素a[1]设置初值*/
    while(not_finish) /*not_finish=1:处理没有结束*/
    {
    while(not_finish&&i<=NUM)
    {
    for(flag=1,k=1;flag&&k<i;k++)
    if(a[k]==a[i])flag=0; /*判断第i个元素是否与前i-1个元素重复*/
    for(sum=0,k=1;flag&&k<=i;k++)
    {
    sum=10*sum+a[k];
    if(sum%k)flag=0; /*判断前k位组成的整数是否能被k整除*/
    }
    if(!flag) /*flag=0:表示第i位不满足要求,需要重新设置*/
    {
    if(a[i]==a[i-1]) /*若a[i]的值已经经过一圈追上a[i-1]*/
    {
    i–; /*i值减1,退回处理前一个元素*/
    if(i>1&&a[i]==NUM)
    a[i]=1; /*当第i位的值达到NUM时,第i位的值取1*/
    else if(i==1&&a[i]==NUM) /*当第1位的值达到NUM时结束*/
    not_finish=0; /*置程序结束标记*/
    else a[i]++; /*第i位的值取下一个,加1*/
    }
    else if(a[i]==NUM) a[i]=1;
    else a[i]++;
    }
    else /*第i位已经满足要求,处理第i+1位*/
    if(++i<=NUM) /*i+1处理下一元素,当i没有处理完毕时*/
    if(a[i-1]==NUM) a[i]=1; /*若i-1的值已为NUM,则a[i]的值为1*/
    else a[i]=a[i-1]+1; /*否则,a[i]的初值为a[i-1]值的"下一个"值*/
    }
    if(not_finish)
    {
    printf("\nThe progressire divisiable number is:");
    for(k=1;k<=NUM;k++) /*输出计算结果*/
    printf("%d",a[k]);
    if(a[NUM-1]<NUM) a[NUM-1]++;
    else a[NUM-1]=1;
    not_finish=0;
    printf("\n");
    }
    }
    }
    *运行结果
    The progressire divisible number is: 381654729
    *思考题
    求N位累进可除数。用1到9这九个数字组成一个N(3<=N<=9)位数,位数字的组成不限,使得该N位数的前两位能被2整除,前3位能被3整除,……,前N位能被N整除。求满足条件的N位数。
     
    69.魔术师的猜牌术(1)
    魔术师利用一副牌中的13张黑桃,预先将它们排好后迭在一起,牌面朝下。对观众说:我不看牌,只数数就可以猜到每张牌是什么,我大声数数,你们听,不信?你们就看。魔术师将最上面的那张牌数为1,把它翻过来正好是黑桃A,将黑桃A放在桌子上,然后按顺序从上到下数手上的余牌,第二次数1、2,将第一张牌放在这迭牌的下面,将第二张牌翻过来,正好是黑桃2,也将它放在桌子上,第三次数1、2、3,将前面两张依次放在这迭牌的下面,再翻第三张牌正好是黑桃3。这样依次进行将13张牌全翻出来,准确无误。问魔术师手中的牌原始顺序是怎样安排的?
    *问题分析与算法设计
    题目已经将魔术师出牌的过程描述清楚,我们可以利用倒推的方法,很容易地推出原来牌的顺序。
    人工倒推的方法是:在桌子上放13空盒子排成一圈,从1开始顺序编号,将黑桃A放入1号盒子中,从下一个空盒子开始对空的盒子计数,当数到第二个空盒子时,将黑桃2放入空盒子中,然后再从下一个空盒子开始对空盒子计数,顺序放入3、4、5…,直到放入全部3张牌。注意在计数时要跳过非空的盒子,只对空盒子计数。最后牌在盒子中的顺序,就是魔术师手中原来牌的顺序。
    这种人工的方法是行之有效的,计算机可以模拟求解。
    *程序说明与注释
    #include<stdio.h>
    int a[14];
    int main()
    {
    int i,n,j=1; /*j:数组(盒子)下标,初始时为1号元素*/
    printf("The original order of cards is:");
    for(i=1;i<=13;i++) /*i:要放入盒子中的牌的序号*/
    {
    n=1;
    do{
    if(j>13) j=1; /*由于盒子构成一个圈,j超过最后一个元素则指向1号元素*/
    if(a[j]) j++; /*跳过非空的盒子,不进行计数*/
    else{ if(n==i) a[j]=i; /*若数到第i个空盒子,则将牌放入空盒中*/
    j++;n++; /*对空盒计数,数组下标指向下一个盒子*/

    }while(n<=i); /*控制空盒计数为i*/
    }
    for(i=1;i<=13;i++) /*输出牌的排列顺序*/
    printf("%d ",a[i]);
    printf("\n");
    }
    *运行结果
    The original order of cards is:1 8 2 5 10 3 12 11 9 4 7 6 13
    70.魔术师的猜牌术(2)
    魔术师再次表演,他将红桃和黑桃全部迭在一起,牌面朝下放在手中,对观众说:最上面一张是黑桃A,翻开后放在桌上。以后,从上至下每数两张全依次放在最底下,第三张给观众看,便是黑桃2,放在桌上后再数两张依次放在最底下,第三张给观众看,是黑桃3。如此下去,观众看到放在桌子上牌的顺序是:
    黑桃 A 2 3 4 5 6 7 8 9 10 J Q K
    红桃 A 2 3 4 5 6 7 8 9 10 J Q K
    问魔术师手中牌的原始顺序是什么?
    *问题分析与算法设计
    本题可在上题的基础上进行编程,不同的在于计数的方法和牌的张数,这些并不影响我们求解题目的思路,仍可按照倒推的方法,得到原来魔术师手中的牌的顺序。
    *程序说明与注释
    #include<stdio.h>
    int a[27];
    int main()
    {
    int i,n,j=1;
    a[1]=1; /*初始化第一张牌*/
    printf("The original order of cards is:(r:rad b:block):\n");
    for(i=2;i<=26;i++)
    {
    n=1;
    do{ 
    if(j>26) j=1; /*超过最后一个元素则指向1号元素*/
    if(a[j]) j++; /*跳过非空的盒子,不进行计数*/
    else{
    if(n==3) a[j]=i; /*若数到第3个空盒子,则将牌放入空盒中*/
    j++; n++; /*对空盒计数,数组下标指向下一个盒子*/
    }
    }while(n<=3); /*控制空盒计数为3*/
    }
    for(i=1;i<=26;i++) /*输出牌的排列顺序*/
    {
    printf("%c",a[i]>13? 'r':'b');
    printf("%d ",a[i]>13? a[i]-13:a[i]);
    if(i==13) printf("\n");
    }
    printf("\n");
    }
    *运行结果
    The original order of cards is:(r:rad b:black):
    b1 r6 b10 b2 r12 r3 b3 b11 r9 b4 r7 b12 b5
    r4 r13 b6 b13 r11 b7 r5 r1 b8 r8 r10 b9 r2 
     
    C/C++语言经典、实用、趣味程序设计编程百例精解(8) 
    71.约瑟夫问题
    这是17世纪的法国数学家加斯帕在《数目的游戏问题》中讲的一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。
    *问题分析与算法设计
    约瑟夫问题并不难,但求解的方法很多;题目的变化形式也很多。这里给出一种实现方法。
    题目中30个人围成一圈,因而启发我们用一个循环的链来表示。可以使用结构数组来构成一个循环链。结构中有两个成员,其一为指向下一个人的指针,以构成环形的链;其二为该 人是否被扔下海的标记,为1表示还在船上。从第一个人开始对还未扔下海的人进行计数,每数到9时,将结构中的标记改为0,表示该人已被扔下海了。这样循环计数直到有15个人被扔下海为止。
    *程序说明与注释
    #include<stdio.h>
    struct node
    {
    int nextp; /*指向下一个人的指针(下一个人的数组下标)*/
    int no_out; /*是否被扔下海的标记。1:没有被扔下海。0:已被扔下海*/
    }link[31]; /*30个人,0号元素没有使用*/
    int main()
    {
    int i,j,k;
    printf("The original circle is(+:pagendom,@:christian):\n");
    for(i=1;i<=30;i++) /*初始化结构数组*/
    {
    link[i].nextp=i+1; /*指针指向下一个人(数组元素下标)*/
    link[i].no_out=1; /*标志置为1,表示人都在船上*/
    }
    link[30].nextp=1; /*第30个人的指针指向第一个人以构成环*/
    j=30; /*j:指向已经处理完毕的数组元素,从link[i]指向的人开始计数*/
    for(i=0;i<15;i++) /*i:已扔下海的人数计数器*/
    {
    for(k=0;;) /*k:决定哪个人被扔下海的计数器*/
    if(k<15)
    {
    j=link[j].nextp; /*修改指针,取下一个人*/
    k+=link[j].no_out; /*进行计数。因已扔下海的人计标记为0*/
    }
    else break; /*计数到15则停止计数*/
    link[j].no_out=0; /*将标记置 0,表示该人已被扔下海*/
    }
    for(i=1;i<=30;i++) /*输出结果*/
    printf("%c",link[i].no_out? '@':'+'); /*+:被扔下海, @:在船上*/
    printf("\n");
    }
    *运行结果
    The original circle is(+:pagandom, @:christian):
    +++@@+@+@@@+@+++@@+@@@+++@+@@+
    (+"表示被扔下海海的非教徒 @:留在船上活命的教徒)
    *思考题
    有N个小孩围 成一圈并依次编号,教师指定从第M个小孩开始报数,报到第S个小孩即令其出列。然后从下一个孩子继续报数,数到第S个小孩又令其出列,如此直到所有的孩子都出列。求小孩出列的先后顺序。
    72.邮票组合
    某人有四张3分的邮票和三张5分的邮票,用这些邮票中的一张或若干张可以得到多少种不同的邮资?
    *问题分析与算法设计 
    将问题进行数学分析,不同张数和面值的邮票组成的邮资可用下列公式计算:
    S=3*i+5*j
    其中i为3分邮柰的张数,j为5分的张数
    按题目的要求,3分的邮票可以取0、1、2、3、4张,5分的邮票可以取0、1、2、3张。采用穷举方法进行组合,可以求出这些不同面值不同张数的邮标组合后的邮资。
    *程序说明与注释
    #include<stdio.h>
    int a[27];
    int main()
    {
    int i,j,k,s,n=0;
    for(i=0;i<=4;i++) /*i:取三分邮票的张数*/
    for(j=0;j<=3;j++) /*j:取5分邮票的张数*/
    {
    s=i*3+j*5; /*计算组成的邮票面值*/
    for(k=0;a[k];k++) /*查找是否有相同的邮资*/
    if(s==a[k])break;
    if(!a[k]&&s) /*没有找到相同的邮资则满足要求存入数组*/
    {
    a[k]=s; n++;
    }
    }
    printf("%d kinds:",n); /*输出结果*/
    for(k=0;a[k];k++)
    printf("%d ",a[k]);
    printf("\n");
    }
    *运行结果
    19 kinds: 5 10 15 3 8 13 18 6 11 16 21 9 14 19 24 12 17 22 27 
     73.和数能表示1~23的5个正整数
    已知五个互不相同的正整数之和为23,且从这五个数中挑选若干个加起来可以表示从1到23之内的全部自然数。问这五个数是什么?
    *问题分析与算法设计
    从计算机程序设计的角度来说,可以用穷举法分解23,然后判断所分解的五个数是否可以表示1到23 之间的全部整数。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a,b,c,d,e,i,j,k,l,m,x,count=0,f=0; /*f:分解的5个数可以表示出1~23的标记*/
    printf("There are following possble result:\n");
    for(a=1;a<=23;a++) /*将23分解为a,b,c,d,e五个数*/
    for(b=1+a;b<=23-a;b++)
    for(c=1+b;c<=23-a-b;c++)
    for(d=1+c;d<=23-a-b-c;d++)
    {
    f=1;
    if((e=23-a-b-c-d)>d)
    for(f=0,x=1;x<24&&!f;x++) /*判断5个数可否表示1~23*/
    for(f=1,i=0;i<2&&f;i++) /*穷举5个数的全部取舍*/ 
    for(j=0;j<2&&f;j++)
    for(k=0;k<2&&f;k++)
    for(l=0;l<2&&f;l++)
    for(m=0;m<2&&f;m++)
    if(x==a*i+b*j+c*k+d*l+e*m) f=0;
    if(!f) printf("[%d]: %d %d %d %d %d\n",++count,a,b,c,d,e);
    }
    }
    *运行结果
    There are following possble result:
    [1]: 1 2 3 5 12
    [2]: 1 2 3 6 11
    [3]: 1 2 3 7 10
    [4]: 1 2 4 5 11
    [5]: 1 2 4 6 10
    [6]: 1 2 4 7 9 
    74.可称1~40磅的4块砝码
    法国数学家梅齐亚克在他著名的《数字组合游戏》(1962)中提出了一个问题:一位商人有一个重40磅的砝码,一天不小心将砝码摔成了四块。后来商人称得每块的重量都是整磅数,而且发现这四块碎片可以在天平上称1至40磅之间的任意重量。请问这四块碎片各重多少?
    *问题分析与算法设计
    本题是上一题的发展。题目中给出的条件是“在天平上”,这意味着:同一砝码既可以放在天平的左侧,也可以放在天平的右侧。若规定重物只能放在天平的左侧,则当天平平衡时有:
    重物重量+左侧砝码重量总和=右侧砝码重量总和
    由此可得:
    重物重量=右侧砝码重量总和-左侧砝码重量总和
    编程时只要根据以上公式,使“右侧砝码重量总和-左侧砝码重量总和”可以表示1到40之间的全部重量即可。编程中要注意的是:怎样采用一种简单的方法来表示一个砝码是在天平的左侧还是在天平的右侧,或是根本没有使用。
    以下程序采用1、 -1和0分别表示上述三种情况,请注意理解。
    *程序说明与注释
    #include<stdio.h>
    #include<math.h>
    int main()
    {
    int weight1,weight2,weight3,weight4,d1,d2,d3,d4,x,flag; /*flag:满足题意的标记*/
    printf("The weight is broke up as following 4 pieces:");
    for(weight1=1;weight1<=40;weight1++) /*将40分解成4份*/
    for(weight2=weight1+1;weight2<=40-weight1;weight2++)
    for(weight3=weight2+1;weight3<=40-weight1-weight2;weight3++)
    if((weight4=40-weight1-weight2-weight3)>=weight3)
    {
    for(flag=1,x=1;x<41&&flag;x++) /*判断可否称出1~40之间的全部重量*/
    for(flag=0,d1=1;d1>-2;d1–) /*将重物放在天平的左边*/
    for(d2=1;d2>-2&&!flag;d2–) /*1:砝码在天平右边*/
    for(d3=1;d3>-2&&!flag;d3–) /*0:不用该砝码*/
    for(d4=1;d4>-2&&!flag;d4–) /*-1:砝码在天平的左边*/
    if(x==weight1*d1+weight2*d2+weight3*d3+weight4*d4)
    flag=1;
    if(flag) printf("%d %d %d %d\n",weight1,weight2,weight3,weight4);
    flag=0; 
    }
    }
    *运行结果
    The weight is broke up as following 4 pieces: 1 3 9 27 
    75.10个小孩分糖果
    十个小孩围成一圈分糖果,老师分给第一个小孩10块,第二个小孩2块,第三个小孩8块,第四个小孩22块,第五个小孩16块,第六个小孩4块,第七个小孩10块,第八个小孩6块,第九个小孩14块,第十个小孩20块。然后所有的小孩同时将手中的糖分一半给右边的小孩;糖块数为奇数的人可向老师要一块。问经过这样几次后大家手中的糖的块数一样多?每人各有多少块糖?
    *问题分析与算法设计
    题目描述的分糖过程是一个机械的重复过程,编程算法完全可以按照描述的过程进行模拟。
    *程序说明与注释
    #include<stdio.h>
    void print(int s[]);
    int judge(int c[]);
    int j=0;
    int main()
    {
    static int sweet[10]={10,2,8,22,16,4,10,6,14,20}; /*初始化数组数据*/
    int i,t[10],l;
    printf(" child\n");
    printf(" round 1 2 3 4 5 6 7 8 9 10\n");
    printf("………………………..\n");
    print(sweet); /*输出每个人手中糖的块数*/
    while(judge(sweet)) /*若不满足要求则继续进行循环*/

    for(i=0;i<10;i++) /*将每个人手中的糖分成一半*/
    if(sweet[i]%2==0) /*若为偶数则直接分出一半*/
    t[i]=sweet[i]=sweet[i]/2;
    else /*若为奇数则加1后再分出一半*/
    t[i]=sweet[i]=(sweet[i]+1)/2;
    for(l=0;l<9;l++) /*将分出的一半糖给右(后)边的孩子*/
    sweet[l+1]=sweet[l+1]+t[l];
    sweet[0]+=t[9];
    print(sweet); /*输出当前每个孩子中手中的糖数*/
    }
    }
    int judge(int c[])
    {
    int i;
    for(i=0;i<10;i++) /*判断每个孩子手中的糖是否相同*/
    if(c[0]!=c[i]) return 1; /*不相同返回 1*/
    return 0;
    }
    void print(int s[]) /*输出数组中每个元素的值*/
    {
    int k;
    printf(" %2d ",j++);
    for(k=0;k<10;k++) printf("%4d",s[k]);
    printf("\n");

    76.小明买书
    小明假期同爸爸一起去书店,他选中了六本书,每本书的单价分别为:3.1,1.7,2,5.3,0.9和7.2。不巧的是,小明的爸爸只带了十几块钱,为了让小明过一个愉快的假期,爸爸扔然同意买书,但提邮购一个要求,要小明从六本书中选出若干本,使得单价相加所得的和同10最接近。你能够帮助小明解决这个问题吗?
    *问题分析与算法设计
    分析题意,可将题目简化为:从六个数中选出若干个求和,使得和与10的差值最小。
    题目中隐含两个问题,其一是怎样从六个数中选出若干个数;其二是求与10的差。
    从六个数中选出若干个数实质是从六个数中选出若干个进行组合。每个数在组合过程中只有两种情况:要么是选中参加求和,要么是没选中不参加求和。这样就可以使用六重循环对每个数是否参加求和进行全部可能情况的组合。
    关于求与10的差值应当注意的是:差值的含义是指差的绝对值。例如:“9-10=-1"和"11-10=1",但9和11这两者与10的差值都是1。若认为”9“与”10的差值为-1就错了。
    *程序说明与注释
    #include<stdio.h>
    #include<math.h>
    int main()
    {
    int d[6],m,i,j;
    long b[63],flag;
    float c[6],min,x;
    printf("Please enter the prices of 6 books:");
    for(i=0;i<6;i++) scanf("%f",&c[i]); /*输入六个浮点数*/
    for(i=0,min=-1,d[0]=0;d[0]<2;d[0]++) /*建立六个数的全部组合并处理*/
    for(d[1]=0;d[1]<2;d[1]++) /*i:差值具有min组合的数量*/
    for(d[2]=0;d[2]<2;d[2]++) /*min:与10的最小差值*/
    for(d[3]=0;d[3]<2;d[3]++) /*d[]:组合时是否取该数的标志*/
    for(d[4]=0;d[4]<2;d[4]++)
    for(d[5]=0;d[5]<2;d[5]++)
    {
    for(flag=0,x=0,j=5;j>=0;j–) 
    /*flag:将六个数的组合用对应的一个十进制位表示 x:对应六个数组合的和*/
    {
    x+=c[j]*d[j]; flag=flag*10+d[j];
    }
    x=((x-10>0)? x-10:10-x); /*x: 组合的和与10的差*/
    if(min<0)
    {
    min=x; /*对第一次计算出的差min进行处理*/
    b[i++]=flag; /*b[]:有相同的min的flag的数组 i:b[]数组的下标*/
    }
    else if(min-x>1.e-6) /*对新的min的处理*/
    {
    min=x; b[0]=flag; i=1;
    }
    else if(fabs((double)x-min)<1.e-6)
    b[i++]=flag; /*对相等min的处理*/
    }
    for(m=0;m<i;m++) /*输出全部i个与10的差值均为min的组合*/
    {
    printf("10(+ -)%.2f=",min);
    for(flag=b[m],j=0;flag>0;j++,flag/=10)
    if(flag%10) /*将b[]中存的标记flag还原为各个数的组合*/
    if(flag>1) printf("%.2f+",c[j]);
    else printf("%.2f\n",c[j]);
    }
    }
    *运行结果
    Please enter the prices of 6 books:3.1 1.7 2.0 5.3 0.9 7.2
    10(+ -)0.10=2.00+0.90+7.20
    10(+ -)0.10=1.70+2.00+5.30+0.90
    10(+ -)0.10=3.10+1.70+5.30
    *思考题
    可以看出,程序中求六个数所能产生全部组合的算法并不好,使用六重循环进行处理使程序显得不够简洁。可以设计出更通用、优化的算法产生全部组合。
     
    77.波松瓦酒的分酒趣题
    法国著名数学家波瓦松在表年时代研究过一个有趣的数学问题:某人有12品脱的啤酒一瓶,想从中倒出6品脱,但他没有6品脱的容器,仅有一个8品脱和5品脱的容器,怎样倒才能将啤酒分为两个6品脱呢?
    *问题分析与算法设计
    将12品脱酒 8品脱和5品脱的空瓶平分,可以抽象为解不定方程:
    8x-5y=6
    其意义是:从12品脱的瓶中向8品脱的瓶中倒x次,并且将5品脱瓶中的酒向12品脱的瓶中倒y次,最后在12品脱的瓶中剩余6品脱的酒。
    用a,b,c代表12品脱、8品脱和5品脱的瓶子,求出不定方程的整数解,按照不定方程的意义则倒法为:
    a -> b -> c ->a
    x y
    倒酒的规则如下:
    1) 按a -> b -> c ->a的顺序;
    2) b倒空后才能从a中取
    3) c装满后才能向a中倒
    按以上规则可以编写出程序如下:
    *程序说明与注释
    #include<stdio.h>
    void getti(int a,int y,int z);
    int i; /*最后需要分出的重量*/
    int main()
    {
    int a,y,z;
    printf("input Full a,Empty b,c,Get i:"); /*a 满瓶的容量 y:第一个空瓶的容量 z:第二个空瓶的容量*/
    scanf("%d%d%d%d",&a,&y,&z,&i);
    getti(a,y,z); /*按a -> y -> z -> a的操作步骤*/
    getti(a,z,y); /*按a -> z -> y -> a的步骤*/
    }
    void getti(int a,int y,int z) /*a:满瓶的容量 y:第一个空瓶的容量 z:第二个空瓶的容量*/
    {
    int b=0,c=0; /* b:第一瓶实际的重量 c:第二瓶实际的重量*/
    printf(" a%d b%d c%d\n %4d%4d%4d\n",a,y,z,a,b,c);
    while(a!=i||b!=i&&c!=i) /*当满瓶!=i或另两瓶都!=i*/
    {
    if(!b)
    { a-=y; b=y;} /*如果第一瓶为空,则将满瓶倒入第一瓶中*/
    else if(c==z)
    { a+=z; c=0;} /*如果第二瓶满,则将第二瓶倒入满瓶中*/
    else if(b>z-c) /*如果第一瓶的重量>第二瓶的剩余空间*/
    { b-=(z-c);c=z;} /*则将装满第二瓶,第一瓶中保留剩余部分*/
    else{ c+=b; b=0;} /*否则,将第一瓶全部倒入第二瓶中*/
    printf(" %4d %4d %4d\n",a,b,c);
    }
    }
    *思考题
    上面的程序中仅给出了两种分酒的方法,并没有找出全部的方法。请设计新的算法,找出全部的分酒方法,并找出一种倒酒次数最少的方法。 
    78.求π的近似值
    请利用“正多边形逼近”的方法求出π的近似值
    *问题分析与算法设计
    利用“正多边形逼近”的方法求出π值在很早以前就存在,我们的先人祖冲之就是用这种方法在世界上第一个得到精确度达小数点后第6位的π值的。
    利用圆内接正六边形边长等于半径的特点将边数翻番,作出正十二边形,求出边长,重复这一过程,就可获得所需精度的π的近似值。
    假设单位圆内接多边形的边长为2b,边数为i,则边数加倍后新的正多边形的边长为:
    x=√──────
    2-2*√───
    1-b*b
    ──────
    2
    周长为: 
    y=2 * i * x i:为加倍前的正多边形的边数
    *程序说明与注释
    #include<stdio.h>
    #include<math.h>
    int main()
    {
    double e=0.1,b=0.5,c,d;
    long int i; /*i: 正多边形边数*/
    for(i=6;;i*=2) /*正多边形边数加倍*/
    {
    d=1.0-sqrt(1.0-b*b); /*计算圆内接正多边形的边长*/
    b=0.5*sqrt(b*b+d*d);
    if(2*i*b-i*e<1e-15) break; /*精度达1e-15则停止计算*/
    e=b; /*保存本次正多边形的边长作为下一次精度控制的依据*/
    }
    printf("pai=%.15lf\n",2*i*b); /*输出π值和正多边形的边数*/
    printf("The number of edges of required polygon:%ld\n",i);
    }
    *运行结果
    pai=3.141592653589794
    The number of edges of required polygon:100663296
    *思考题
    请用外切正多边形逼近的方法求π的近似值。
    79.求π的近似值(2)
    利用随机数法求π的近似值
    *问题分析与算法设计
    随机数法求π的近似值的思路:在一个单位边长的正方形中,以边长为半径,以一个顶点为圆心,在政权方形上作四分之一圆。随机的向正方形内扔点,若落入四分之一圆内则计数。重复向正方形内扔足够多的点,将落入四分之一圆内的计数除以总的点数,其值就是π值四分之一的近似值。
    按此方法可直接进行编程,注意:本方法求出的π值只有统计次数足够多时才可能准确。
    *程序说明与注释
    #include<time.h>
    #include<stdlib.h>
    #include<stdio.h>
    #define N 30000
    int main()
    {
    float x,y;
    int c=0,d=0;
    randomize();
    while(c++<=N)
    {
    x=random(101); /*x:坐标。产生0到100之间共101个的随机数*/
    y=random(101); /*y:坐标。产生0到100之间共101个的随机数*/
    if(x*x+y*y<=10000) /*利用圆方程判断点是否落在圆内*/
    d++;
    }
    printf(" pi=%f\n",4. *d/N); /*输出求出的π值*/
    }
    *运行结果
    多次运行程序,可能得到多个不同的对口果,这是因为采用的是统计规律求出的近似值,只有当统计的次数足够大时,才可能逼近π值。运行四次,可能的结果是:
    3.122267
    3.139733
    3.133733
    80.奇数平方的一个有趣性质
    编程验证“大于1000的奇数其平方与1的差是8的倍数”。
    *问题分析与算法设计
    本题是一个很容易证明的数学定理,我们可以编写程序验证它。
    题目中给出的处理过程很清楚,算法不需要特殊设计。可以按照题目的叙述直接进行验证(程序中仅验证到3000)。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    long int a;
    for(a=1001;a<=3000;a+=2)
    {
    printf("%ld:",a); /*输出奇数本身*/
    printf("(%ld*%ld-1)/8",a,a); /*输出(奇数的平方减1)/8*/
    printf("=%ld",(a*a-1)/8); /*输出被8除后的商*/
    printf("+%ld\n",(a*a-1)%8); /*输出被8除后的余数*/
    }

     
    C/C++语言经典、实用、趣味程序设计编程百例精解(9) 
    81.角谷猜想
    日本一位中学生发现一个奇妙的“定理”,请角谷教授证明,而教授无能为力,于是产生角谷猜想。猜想的内容是:任给一个自然数,若为偶数除以2,若为奇数则乘3加1,得到一个新的自然数后按照上面的法则继续演算,若干次后得到的结果必然为1。请编程验证。
    *问题分析与算法设计
    本题是一个沿未获得一般证明的猜想,但屡试不爽,可以用程序验证。
    题目中给出的处理过程很清楚,算法不需特殊设计,可按照题目的叙述直接进行证。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int n,count=0;
    printf("Please enter number:");
    scanf("%d",&n); /*输入任一整数*/
    do{
    if(n%2)
    {
    n=n*3+1; /*若为奇数,n乘3加1*/
    printf("[%d]:%d*3+1=%d\n",++count,(n-1)/3,n);
    }
    else
    {
    n/=2; /*若为偶数n除以2*/
    printf("[%d]: %d/2=%d\n",++count,2*n,n);
    }
    }while(n!=1); /*n不等于1则继续以上过程*/

    82.四方定理
    数论中著名的“四方定理”讲的是:所有自然数至多只要用四个数的平方和就可以表示。
    请编程证此定理。
    *问题分析与算法设计
    本题是一个定理,我们不去证明它而是编程序验证。
    对四个变量采用试探的方法进行计算,满足要求时输出计算结果。
    *程序说明与注释
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    int number,i,j,k,l;
    printf("Please enter a number=");
    scanf("%d",&number); /*输入整数*/
    for(i=1;i<number/2;i++) /*试探法。试探i,j,k,k的不同值*/
    for(j=0;j<=i;j++)
    for(k=0;k<=j;k++)
    for(l=0;l<=k;l++)
    if(number==i*i+j*j+k*k+l*l) /*若满足定理要求则输出结果*/
    {
    printf(" %d=%d*%d+%d*%d+%d*%d+%d*%d\n",number,i,i,j,j,k,k,l,l);
    exit(0);
    }
    }
    *运行结果
    1) Please enter a number = 110
    110=7*7+6*6+4*4+3*3
    2) Please enter a number = 211
    211=8*8+7*7+7*7+7*7
    3) Please enter a number = 99
    99=7*7+5*5+4*4+3*3
    83.卡布列克常数
    验证卡布列克运算。任意一个四位数,只要它们各个位上的数字是不全相同的,就有这样的规律:
    1)将组成该四位数的四个数字由大到小排列,形成由这四个数字构成的最大的四位数;
    2)将组成该四位数的四个数字由小到大排列,形成由这四个数字构成的最小的四位数(如果四个数中含有0,则得到的数不足四位);
    3)求两个数的差,得到一个新的四位数(高位零保留)。
    重复以上过程,最后得到的结果是6174,这个数被称为卡布列克数。
    *问题分析与算法设计
    题目中给出的处理过程很清楚,算法不需要特殊设计,可按照题目的叙述直接进行验证。
    *程序说明与注释
    #include<stdio.h>
    void vr6174(int);
    void parse_sort(int num,int *each);
    void max_min(int *each,int *max,int *min);
    void parse_sort(int num,int *each);
    int count=0;
    int main()
    {
    int n;
    printf("Enter a number:");
    scanf("%d", &n); /*输入任意正整数*/
    vr6174(n); /*调用函数进行验证*/
    }
    void vr6174(int num)
    {
    int each[4],max,min;
    if(num!=6174&&num) /*若不等于74且不等于0则进行卡布列克运算*/
    {
    parse_sort(num,each); /*将整数分解,数字存入each数组中*/
    max_min(each,&max,&min); /*求数字组成的最大值和最小值*/ 
    num=max-min; /*求最大值和最小值的差*/
    printf("[%d]: %d-%d=%d\n",++count,max,min,num); /*输出该步计算过程*/
    vr6174(num); /*递归调用自身继续进行卡布列克运算*/
    }
    }
    void parse_sort(int num,int *each)
    {
    int i,*j,*k,temp;
    for(i=0;i<=4;i++) /*将NUM分解为数字*/
    {
    j=each+3-i;
    *j=num%10;
    num/=10;
    }
    for(i=0;i<3;i++) /*对各保数字从小到大进行排序*/
    for(j=each,k=each+1;j<each+3-i;j++,k++)
    if(*j>*k) { temp=*j;*j=*k;*k=temp;}
    return;
    }
    void max_min(int *each,int *max,int *min) /*将分解的数字还原为最大整数和最小整数*/
    {
    int *i;
    *min=0;
    for(i=each;i<each+4;i++) /*还原为最小的整数*/
    *min=*min*10+*i;
    *max=0;
    for(i=each+3;i>=each;i–) /*还原为最大的整数*/
    *max=*max*10+*i;
    return;
    }
    *运行结果
    1) Enter a number:4312
    [1]:4312-1234=3078
    [2]:8730-378=8352
    [3]:8532-2358=6174
    2) Enter a number:8720
    [1]:8720-278=8442
    [2]:8442-2448=5994
    [3]:9954-4599=5355
    [4]:5553-3555=1998
    [5]:9981-1899=8082
    [6]:8820-288=8523
    [7]:8532-2358=6174
    3)Enter a number:9643
    [1]:9643-3469=6174 
    84.尼科彻斯定理
    验证尼科彻斯定理,即:任何一个整数的立方都可以写成一串连续奇数的和。××
    *问题分析与算法设计
    本题是一个定理,我们先来证明它是成立的。
    对于任一正整数a,不论a是奇数还是偶数,整数(a×a-a+1)必然为奇数。
    构造一个等差数列,数列的首项为(a×a-a+1),等差数列的差值为2(奇数数列),则前a项的和为:
    a×((a×a-a+1))+2×a(a-1)/2
    =a×a×a-a×a+a+a×a-a
    =a×a×a
    定理成立。证毕。
    通过定理的证明过程可知L所要求的奇数数列的首项为(a×a-a+1),长度为a。编程的算法不需要特殊设计,可按照定理的证明过直接进行验证。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a,b,c,d;
    printf("Please enter a number:");
    scanf("%d",&a); /*输入整数*/
    b=a*a*a; /*求整数的三次方*/
    printf("%d*%d*%d=%d=",a,a,a,b);
    for(d=0,c=0;c<a;c++) /*输出数列,首项为a*a-a+1,等差值为2*/
    {
    d+=a*a-a+1+c*2; /*求数列的前a项的和*/
    printf(c?"+%d":"%d",a*a-a+1+c*2);
    }
    if(d==b)printf(" Y\n"); /*若条件满足则输出“Y”*/
    else printf(" N\n"); /*否则输出“N”*/
    }
    *运行结果
    1) Please enter a number:13
    13*13*13=2197=157+159+161+163+165+167+169+171+173+175+177+179+181 Y
    2) Please enter a number:14
    14*14*14=2744=183+185+187+189+191+193+195+197+199+201+203+205+207+209 Y
    *思考题
    本题的求解方法是先证明,在证明的过程中找到编程的算法,然后实现编程。实际上我们也可以不进行证明,直接使用编程中常用的试探方法来找出该数列,验证该定理。请读者自行设计算法。当然这样得到的数列可能与用定理方法得到的数列不一样。 
    85.回文数的形成
    任取一个十进制整数,将其倒过来后与原来的整数相加,得到一个新的整数后重复以上步聚,则最终可得到一个回文数。请编程验证。
    *问题分析与算法设计
    回文数的这一形成规则目前还属于一个猜想,尚未得到数学上的证明。有些回文数要经历上百个步聚才能获得。这里通过编程验证。
    题目中给出的处理过程很清楚,算法不需要特殊设计。可按照题目的叙述直接进行验证。
    *程序说明与注释
    #include<stdio.h>
    #define MAX 2147483647
    long re(long int);
    int nonres(long int s);
    int main()
    {
    long int n,m;
    int count=0;
    printf("Please enetr a number optionaly:");
    scanf("%ld",&n);
    printf("The generation process of palindrome:\n");
    while(!nonres((m=re(n))+n)) /*判断整数与其反序数相加后是否为回文数*/
    {
    if(m+n>=MAX)
    {
    printf(" input error,break.\n");
    break;
    }
    else
    {
    printf("[%d]:%ld+%ld=%ld\n",++count,n,m,m+n);
    n+=m;
    }
    }
    printf("[%d]:%ld+%ld=%ld\n",++count,n,m,m+n); /*输出最后得到的回文数*/
    printf("Here we reached the aim at last!\n");
    }
    long re(long int a) /*求输入整数的反序数*/
    {
    long int t;
    for(t=0;a>0;a/=10) /*将整数反序*/
    t=t*10+a%10;
    return t;
    }
    int nonres(long int s) /*判断给定的整数是否是回文数*/
    {
    if(re(s)==s) return 1; /*若是回文数则返回1*/
    else return 0; /*否则返回 0*/

     86.自动发牌
    一副扑克有52张牌,打桥牌时应将牌分给四个人。请设计一个程序完成自动发牌的工作。要求:黑桃用S(Spaces)表示;红桃用H(Hearts)表示;方块用D(Diamonds)表示;梅花用C(Clubs)表示。
    *问题分析与算法设计
    按照打桥牌的规定,每人应当有13张牌。在人工发牌时,先进行洗牌,然后将洗好的牌按一定的顺序发给每一个人。为了便于计算机模拟,可将人工方式的发牌过程加以修改:先确定好发牌顺序:1、2、3、4;将52张牌顺序编号:黑桃2对应数字0,红桃2对应数字1,方块2对应数字2,梅花2对应数字3,黑桃3对应数字4,红桃3对应数字5,…然后从52 张牌中随机的为每个人抽牌。
    这里采用C语言库函数的随机函数,生成0到51之间的共52个随机数,以产生洗牌后发牌的效果。
    *程序与程序注释 
    #include<stdlib.h>
    #include<stdio.h>
    int comp(const void *j,const void *i);
    void p(int b[],char n[]);
    int main(void)
    {
    static char n[]={'2','3','4','5','6','7','8','9','T','J','Q','K','A'};
    int a[53],b1[13],b2[13],b3[13],b4[13];
    int b11=0,b22=0,b33=0,b44=0,t=1,m,flag,i;
    while(t<=52) /*控制发52张牌*/
    {
    m=rand()%52; /*产生0到51之间的随机数*/
    for(flag=1,i=1;i<=t&&flag;i++)/*查找新产生的随机数是否已经存在*/
    if(m==a[i]) flag=0; /*flag=1:产生的是新的随机数flag=0:新产生的随机数已经存在*/
    if(flag)
    {
    a[t++]=m; /*如果产生了新的随机数,则存入数组*/
    if(t%4==0) b1[b11++]=a[t-1]; /*根据t的模值,判断当前*/
    else if(t%4==1) b2[b22++]=a[t-1]; /*的牌应存入哪个数组中*/
    else if(t%4==2) b3[b33++]=a[t-1];
    else if(t%4==3) b4[b44++]=a[t-1];
    }
    }
    qsort(b1,13,sizeof(int),comp); /*将每个人的牌进行排序*/
    qsort(b2,13,sizeof(int),comp);
    qsort(b3,13,sizeof(int),comp);
    qsort(b4,13,sizeof(int),comp);
    p(b1,n); p(b2,n); p(b3,n); p(b4,n); /*分别打印每个人的牌*/
    return 0;
    }
    void p(int b[],char n[])
    {
    int i;
    printf("\n\006 "); /*打印黑桃标记*/
    for(i=0;i<13;i++) /*将数组中的值转换为相应的花色*/
    if(b[i]/13==0) printf("%c ",n[b[i]%13]); /*该花色对应的牌*/
    printf("\n\003 "); /*打印红桃标记*/
    for(i=0;i<13;i++)
    if((b[i]/13)==1) printf("%c ",n[b[i]%13]);
    printf("\n\004 "); /*打印方块标记*/
    for(i=0;i<13;i++)
    if(b[i]/13==2) printf("%c ",n[b[i]%13]);
    printf("\n\005 "); /*打印梅花标记*/
    for(i=0;i<13;i++)
    if(b[i]/13==3||b[i]/13==4) printf("%c ",n[b[i]%13]);
    printf("\n");
    }
    int comp(const void *j,const void *i) /*qsort调用的排序函数*/
    {
    return(*(int*)i-*(int*)j);
    }
    87.黑白子交换
    有三个白子和三个黑子如下图布置:
    ○ ○ ○ . ● ● ● 
    游戏的目的是用最少的步数将上图中白子和黑子的位置进行交换:
    ● ● ● . ○ ○ ○ 
    游戏的规则是:(1)一次只能移动一个棋子; (2)棋子可以向空格中移动,也可以跳过一个对方的棋子进入空格,但不能向后跳,也不能跳过两个子。请用计算机实现上述游戏。
    *问题分析与算法设计
    计算机解决胜这类问题的关键是要找出问题的规律,或者说是要制定一套计算机行动的规则。分析本题,先用人来解决问题,可总结出以下规则:
    (1) 黑子向左跳过白子落入空格,转(5)
    (2) 白子向右跳过黑子落入空格,转(5)
    (3) 黑子向左移动一格落入空格(但不应产生棋子阻塞现象),转(5)
    (4) 白子向右移动一格落入空格(但不应产生棋子阻塞现萌),转(5)
    (5) 判断游戏是否结束,若没有结束,则转(1)继续。
    所谓的“阻塞”现象就是:在移动棋子的过程中,两个尚未到位的同色棋子连接在一起,使棋盘中的其它棋子无法继续移动。例如按下列方法移动棋子:
    0
    ○ ○ ○ . ● ● ● 
    1 ○ ○ . ○ ● ● ● 
    2 △ ○ ○ ● ○ . ● ● 
    3
    ○ ○ ● . ○ ● ● 
    4 两个●连在一起产生阻塞
    ○ ○ ● ● ○ . ● 
    或4 两个白连在一起产生阻塞
    ○ . ● ○ ○ ● ● 
    产生阻塞的现象的原因是在第2步(△状态)时,棋子○不能向右移动,只能将●向左移动。
    总结产生阻塞的原因,当棋盘出现“黑、白、空、黑”或“白、空、黑、白”状态时,不能向左或向右移动中间的棋子,只移动两边的棋子。
    按照上述规则,可以保证在移动棋子的过程中,不会出现棋子无法移动的现象,且可以用最少的步数完成白子和黑子的位置交换。
    *程序说明与注释
    #include<stdio.h>
    int number;
    void print(int a[]);
    void change(int *n,int *m);
    int main()
    {
    int t[7]={1,1,1,0,2,2,2}; /*初始化数组1:白子 2:黑子 0:空格*/
    int i,flag;
    print(t);
    while(t[0]+t[1]+t[2]!=6||t[4]+t[5]+t[6]!=3) /*判断游戏是否结束
    若还没有完成棋子的交换则继续进行循环*/
    {
    flag=1; /*flag 为棋子移动一步的标记1:尚未移动棋子 0:已经移动棋子*/
    for(i=0;flag&&i<5;i++) /*若白子可以向右跳过黑子,则白子向右跳*/
    if(t[i]==1&&t[i+1]==2&&t[i+2]==0)
    {change(&t[i],&t[i+2]); print(t); flag=0;}
    for(i=0;flag&&i<5;i++) /*若黑子可以向左跳过白子,则黑子向左跳*/
    if(t[i]==0&&t[i+1]==1&&t[i+2]==2)
    {change(&t[i],&t[i+2]); print(t); flag=0;}
    for(i=0;flag&&i<6;i++) /*若向右移动白子不会产生阻塞,则白子向右移动*/
    if(t[i]==1&&t[i+1]==0&&(i==0||t[i-1]!=t[i+2]))
    {change(&t[i],&t[i+1]); print(t);flag=0;}
    for(i=0;flag&&i<6;i++) /*若向左移动黑子不会产生阻塞,则黑子向左移动*/
    if(t[i]==0&&t[i+1]==2&&(i==5||t[i-1]!=t[i+2]))
    { change(&t[i],&t[i+1]); print(t);flag=0;}
    }
    }
    void print(int a[])
    {
    int i;
    printf("No. %2d:………………………..\n",number++);
    printf(" ");
    for(i=0;i<=6;i++)
    printf(" | %c",a[i]==1?'*':(a[i]==2?'@':' '));
    printf(" |\n ………………………..\n\n");
    }
    void change(int *n,int *m)
    {
    int term;
    term=*n; *n=*m; *m=term;
    }
    *问题的进一步讨论
    本题中的规则不仅适用于三个棋子的情况,而且可以推而广之,适用于任意N个棋子的情况。读者可以编程验证,按照本规则得到的棋子移动步数是最少的。
    事实上,制定规则是解决这类问题的关键。一个游戏程序“思考水平的高低,完全取决于使用规则的好坏。”
    *思考题
    有两个白子和两个黑子如下左图布置:
    ○ . ○ 
    . . . 
    ● . ● 
    棋盘中的棋子按”马步“规则行走,要求用最少的步数将图中白子和黑子的位置进行交换,最终结果如下一幅图所示。
    ● . ● 
    . . . 
    ○ . ○ 
    88.常胜将军
    现有21根火柴,两人轮流取,每人每次可以取走1至4根,不可多取,也不能不取,谁取最后一楰火柴谁输。请编写一个程序进行人机对弈,要求人先取,计算机后取;计算机一方为“常胜将军”。
    *问题分析与算法设计
    在计算机后走的情况下,要想使计算机成为“常胜将军”,必须找出取 关键。根据本题的要求枷以总结出,后走一方取子的数量与对方刚才一步取子的数量之和等于,就可以保证最后一个子是留给先取子的那个人的。
    据此分析进行算法设计就是很简单的工作,编程实现也十分容易。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int a=21,i;
    printf("Game begin:\n");
    while(a>0)
    {
    do{
    printf("How many stick do you wish to take(1~%d)?",a>4?4:a);
    scanf("%d",&i);
    }while(i>4||i<1||i>a); /*接收正在确的输入*/
    if(a-i>0) printf(" %d stick left in the pile.\n",a-i);
    if((a-i)<=0)
    {
    printf(" You have taken the last stick.\n");
    printf(" * * * You lose! \nGame Over.\n"); /*输出取胜标记*/
    break;
    }
    else
    printf(" Compute take %d stick.\n",5-i); /*输出计算机取的子数*/
    a-=5;
    printf(" %d stick left in the pile.\n",a);
    }
    }
    *思考题
    改变题目中火柴的数量(如为22根),则后走的一方就不一定能够保持常胜了,很可能改变成“常败”。此时后走一方的胜负就与火柴的初始数量和每次允许取的火柴数量的最大值有直接关系,请编写程序解决这一问题。 
    89.抢30
    这是中国民间的一个游戏。两人从1开始轮流报数,每人每次可报一个数或两个连续的数,谁先报到30,谁就为胜方。
    *问题分析与算法设计
    本题与上题类似,算法也类似,所不同的是,本谁先走第一步是可选的。若计算机走第一步,那么计算机一定是赢家。若人先走一步,那么计算机只好等待人犯错误,如果人先走第一步且不犯错误,那么人就会取胜;否则计算机会抓住人的一次错误使自己成为胜利者。
    *程序说明与注释
    #include<stdio.h>
    #include<time.h>
    #include<stdlib.h>
    int input(int t);
    int copu(int s);
    int main()
    {
    int tol=0;
    printf("\n* * * * * * * *catch thirty* * * * * * * \n");
    printf("Game Begin\n");
    randomize(); /*初始化随机数发生器*/
    if(random(2)==1) /*取随机数决定机器和人谁先走第一步*/
    tol=input(tol); /*若为1,则余元走第一步*/
    while(tol!=30) /*游戏结束条件*/
    if((tol=copu(tol))==30) /*计算机取一个数,若为30则机器胜利*/
    printf("I lose! \n");
    else
    if((tol=input(tol))==30) /*人取一个数,若为30则人胜利*/
    printf("I lose! \n");
    printf(" * * * * * * * *Game Over * * * * * * * *\n");
    }
    int input(int t)
    {
    int a;
    do{
    printf("Please count:");
    scanf("%d",&a);
    if(a>2||a<1||t+a>30)
    printf("Error input,again!");
    else
    printf("You count:%d\n",t+a);
    }while(a>2||a<1||t+a>30);
    return t+a; /*返回当前的已经取走的数累加和*/
    }
    int copu(int s)
    {
    int c;
    printf("Computer count:");
    if((s+1)%3==0) /*若剩余的数的模为1,则取1*/
    printf(" %d\n",++s);
    else if((s+2)%3==0)
    {
    s+=2; /*若剩余的数的模为2,则取2*/
    printf(" %d\n",s);
    }
    else
    {
    c=random(2)+1; /*否则随机取1或2*/
    s+=c;
    printf(" %d\n",s);
    }
    return s;
    }
    *思考题
    巧夺偶数。桌子上有25颗棋子,游戏双方轮流取子,每人每次最少取走一颗棋子,最多可取走3颗棋子。双方照这样取下去,直到取光所有的棋子。于是双方手中必然一方为偶数,一方为奇数,偶数方为胜者。请编程实现人机游戏。
    90.搬山游戏
    设有n座山,计算机与人为比赛的双方,轮流搬山。规定每次搬山的数止不能超 过k座,谁搬最后一座谁输。游戏开始时。计算机请人输入山的总数(n)和每次允许搬山的最大数止(k)。然后请人开始,等人输入了需要搬走的山的数目后,计算机马上打印出它搬多少座山,并提示尚余多少座山。双方轮流搬山直到最后一座山搬完为止。计算机会显示谁是赢家,并问人是否要继续比赛。若人不想玩了,计算机便会统计出共玩了几局,双方胜负如何。
    *问题分析与算法设计
    计算机参加游戏时应遵循下列原则:
    1) 当:
    剩余山数目-1<=可移动的最大数k 时计算机要移(剩余山数目-1)座,以便将最后一座山留给人。
    2)对于任意正整数x,y,一定有:
    0<=x%(y+1)<=y
    在有n座山的情况下,计算机为了将最后一座山留给人,而且又要控制每次搬山的数目不超过最大数k,它应搬山的数目要满足下列关系:
    (n-1)%(k+1)
    如果算出结果为0,即整除无余数,则规定只搬1座山,以防止冒进后发生问题。
    按照这样的规律,可编写出游戏程序如下:
    #include<stdio.h>
    int main()
    {
    int n,k,x,y,cc,pc,g;
    printf("More Mountain Game\n");
    printf("Game Begin\n");
    pc=cc=0;
    g=1;
    for(;;)
    {
    printf("No.%2d game \n",g++);
    printf("—————————————\n");
    printf("How many mpuntains are there?");
    scanf("%d",&n);
    if(!n) break;
    printf("How many mountains are allowed to each time?");
    do{
    scanf("%d",&k);
    if(k>n||k<1) printf("Repeat again!\n");
    }while(k>n||k<1);
    do{
    printf("How many mountains do you wish movw away?");
    scanf("%d",&x);
    if(x<1||x>k||x>n) /*判断搬山数是否符合要求*/
    {
    printf("IIIegal,again please!\n");
    continue;
    }
    n-=x;
    printf("There are %d mountains left now.\n",n);
    if(!n)
    {
    printf("……………I win. You are failure……………\n\n");cc++;
    }
    else
    {
    y=(n-1)%(k+1); /*求出最佳搬山数*/
    if(!y) y=1;
    n-=y;
    printf("Copmputer move %d mountains away.\n",y);
    if(n) printf(" There are %d mountains left now.\n",n);
    else
    {
    printf("……………I am failure. You win………………\n\n");
    pc++;
    }
    }
    }while(n);
    }
    printf("Games in total have been played %d.\n",cc+pc);
    printf("You score is win %d,lose %d.\n",pc,cc);
    printf("My score is win %d,lose %d.\n",cc,pc);
    }
    *思考题
    取石子游戏。将石子分成若干堆,每堆有若干粒,参加游戏的甲乙两方轮流从任意一堆中取走任意个石子,甚至可以全部取走,但每次只能在一堆中取,不允许从这堆取一些,再从另一堆中取一些。直到谁取走最后一粒石子谁就获胜。请编程进行人机对弈 


    C/C++语言经典、实用、趣味程序设计编程百例精解 (10)  
    91.人机猜数游戏
    由计算机“想”一个四位数,请人猜这个四位数是多少。人输入四位数字后,计算机首先判断这四位数字中有几位是猜对了,并且在对的数字中又有几位位置也是对的,将结果显示出来,给人以提示,请人再猜,直到人猜出计算机所想的四位数是多少为止。
    例如:计算机“想”了一个“1234”请人猜,可能的提示如下:
    人猜的整数 计算机判断有几个数字正确 有几个位置正确
    1122 2 1
    3344 2 1
    3312 3 0
    4123 4 0
    1243 4 2
    1234 4 4
    游戏结束
    请编程实现该游戏。游戏结束时,显示人猜一个数用了几次。
    *问题分析与算法设计
    问题本身清楚明了。判断相同位置上的数字是否相同不需要特殊的算法。只要截取相同位置上的数字进行比较即可。但在判断几位数字正确时,则应当注意:计算机所想的是“1123”,而人所猜的是“1576”,则正确的数字只有1位。
    程序中截取计算机所想的数的每位数字与人所猜的数字按位比较。若有两位数字相同,则要记信所猜中数字的位置,使该位数字只能与一位对应的数字“相同”。当截取下一位数字进行比较时,就不应再与上述位置上的数字进行比较,以避免所猜的数中的一位与对应数中多位数字“相同”的错误情况。
    *程序说明与注释
    #include<stdio.h>
    #include<time.h>
    #include<stdlib.h>
    int main()
    {
    int stime,a,z,t,i,c,m,g,s,j,k,l[4]; /*j:数字正确的位数 k:位置正确的位数*/
    long ltime;
    ltime=time(NULL); /*l:数字相同时,人所猜中数字的正确位置*/
    stime=(unsigned int)ltime/2;
    srand(stime);
    z=random(9999); /*计算机想一个随机数*/
    printf("I have a number with 4 digits in mind,please guess.\n");
    for(c=1;;c++) /*c: 猜数次数计数器*/
    {
    printf("Enter a number with 4 digits:");
    scanf("%d",&g); /*请人猜*/ 
    a=z;j=0;k=0;l[0]=l[1]=l[2]=l[3]=0;
    for(i=1;i<5;i++) /*i:原数中的第i位数。个位为第一位,千位为第4位*/
    {
    s=g;m=1;
    for(t=1;t<5;t++) /*人所猜想的数*/
    {
    if(a%10==s%10) /*若第i位与人猜的第t位相同*/
    {
    if(m&&t!=l[0]&&t!=l[1]&&t!=l[2]&&t!=l[3])
    {
    j++;m=0;l[j-1]=t; /*若该位置上的数字尚未与其它数字“相同”*/
    } /*记录相同数字时,该数字在所猜数字中的位置*/
    if(i==t) k++; /*若位置也相同,则计数器k加1*/
    }
    s/=10;
    }
    a/=10;
    }
    printf("You hane correctly guessed %d digits,\n",j);
    printf("and correctly guessed %d digits in exact position.\n",k);
    if(k==4) break; /*若位置全部正确,则人猜对了,退出*/
    }
    printf("Now you have correctly guessed the whole number after %d times.\n",c);
    }
    Now you have correctly guessed the whole number after 7 times.
    *思考题
    猜数游戏。由计算机“想”一个数请人猜,人输入猜的数,如果猜对了,则结束游戏,否则计算机会给出提示,指出人猜的数是太大,还是太小。当一个数猜了20次还未猜中时,应停止猜数者继续游戏的权力,从程序中退出。
     92.人机猜数游戏(2)
    将以上游戏(91.人机猜数游戏)双方倒一下,请人想一个四位的整数,计算机来猜,人给计算机提示信息,最终看计算机用几次猜出一个人“想”的数。请编程实现。
    *问题分析与算法设计
    解决这类问题时,计算机的思考过程不可能象人一样具完备的推理能力,关键在于要将推理和判断的过程变成一种机械的过程,找出相应的规则,否则计算机难以完成推理工作。
    基于对问题的分析和理解,将问题进行简化,求解分为两个步聚来完成:首先确定四位数字的组成,然后再确定四位数字的排列顺序。可以列出如下规则:
    1)分别显示四个1,四个2,……,四个0,确定四位数字的组成。
    2)依次产生四位数字的全部排列(依次两两交换全部数字的位置)。
    3)根据人输入的正确数字及正确位置的数目,进行分别处理:
    (注意此时不出现输入的情况,因为在四个数字已经确定的情况下,若有3个位置正确,则第四个数字的位置必然也是正确的)
    若输入4:游戏结束。
    判断本次输入与上次输入的差值
    若差为2:说明前一次输入的一定为0,本次输入的为2,本次交换的两个数字的位置是正确的,只要交换另外两个没有交换过的数字即可结束游戏。
    若差为-2:说明前一次输入的一定为2,本次的一定为0。说明刚交换过的两个数字的位置是错误的,只要将交换的两个数字位置还原,并交换另外两个没有交换过的数字即可结束游戏。
    否则:若本次输入的正确位置数<=上次的正确位置数
    则恢复上次四位数字的排列,控制转3)
    否则:将本次输入的正确位置数作为“上次输入的正确位置数”,控制转3)。
    *程序说明与注释
    #include<stdio.h>
    #include<stdlib.h>
    void bhdy(int s,int b);
    void prt();
    int a[4],flag,count;
    int main()
    {
    int b1,b2,i,j,k=0,p,c;
    printf("Game guess your number in mind is # # # #.\n");
    for(i=1;i<10&&k<4;i++) /*分别显示四个1~9确定四个数字的组成*/
    {
    printf("No.%d:your number may be:%d%d%d%d\n",++count,i,i,i,i);
    printf("How many digits have bad correctly guessed:");
    scanf("%d",&p); /*人输入包含几位数字*/ 
    for(j=0;j<p;j++)
    a[k+j]=i; /*a[]:存放已确定数字的数组*/
    k+=p; /*k:已确定的数字个数*/
    }
    if(k<4) /*自动算出四位中包的个数*/
    for(j=k;j<4;j++)
    a[j]=0;
    i=0;
    printf("No.%d:your number may be:%d%d%d%d\n",++count,a[0],a[1],a[2],a[3]);
    printf("How many are in exact positions:"); /*顺序显示四位数字*/
    scanf("%d",&b1); /*人输入有几位位置是正确的*/
    if(b1==4){prt();exit(0);} /*四位正确,打印结果。结束游戏*/
    for(flag=1,j=0;j<3&&flag;j++) /*实现四个数字的两两(a[j],a[k]交换*/
    for(k=j+1;k<4&&flag;k++)
    if(a[j]!=a[k])
    {
    c=a[j];a[j]=a[k];a[k]=c; /*交换a[j],a[k]*/
    printf("No.%d:Your number may be: %d%d%d%d\n",++count,a[0],a[1],a[2],a[3]);
    printf("How many are in exact positins:");
    scanf("%d",&b2); /*输入有几个位置正确*/
    if(b2==4){prt();flag=0;} /*若全部正确,结束游戏*/
    else if(b2-b1==2)bhdy(j,k); /*若上次与本次的差为2,则交换两个元素即可结束*/
    else if(b2-b1==-2) /*若上次与本次的差为-2,则说明已交换的(a[j],a[k])是错误的
    将(a[j],a[k]还原后,只要交换另外两个元素即可结束游戏*/
    {
    c=a[j];a[j]=a[k];a[k]=c;
    bhdy(j,k);
    }
    else if(b2<=b1)
    {
    c=a[j];a[j]=a[k];a[k]=c; /*恢复交换的两个数字*/
    }
    else b1=b2; /*其它情况则将新输入的位置信息作为上次的位置保存*/
    }
    if(flag) printf("You input error!\n"); /*交换结果仍没结果,只能是人输入的信息错误*/
    }
    void prt() /*打印结果,结束游戏*/
    {
    printf("Now your number must be %d%d%d%d.\n",a[0],a[1],a[2],a[3]);
    printf("Game Over\n");
    }
    void bhdy(int s,int b)
    {
    int i,c=0,d[2];
    for(i=0;i<4;i++) /*查找s和b以外的两个元素下标*/
    if(i!=s&&i!=b) d[c++]=i;
    i=a[d[1>;a[d[1>=a[d[0>; a[d[0>=i; /*交换除a[s]和a[b]以外的两个元素*/
    prt(); /*打印结果,结束游戏*/
    flag=0;
    }
    *运行示例
    假设人想的四位数是:7215
    Game Begin
    Now guess your number in mind is # # # #.
    No.1:your number may be:1111
     
    *问题的进一步讨论
    本程序具有逻辑结构清析、算法简单正确的优点,但在接受人的输入信息时缺少必要的出错保护功能,同时在进行第三步推理过程中没有保留每次猜出的数字位置信息及人输入的回答,这样对于每次人输入的信息就无法进行合法性检查,即无法检查人的输入信息是否自相矛盾;同晨也无法充分利用前面的结果。
    这些缺陷是可以改进的,但最后一个问题改进难度较大,留给大家自己去完成。
    *思考题
    “一条龙游戏”。在一个3×3的棋盘上,甲乙双方进行对弃,双方在棋盘上轮流放入棋子,如果一方的棋子成一直线(横、竖或斜线),则该方赢。请编写该游戏程序实现人与机器的比赛。比赛结果有三种:输、赢或平。
    在编程过程中请首先分析比赛中怎样才能获胜,找出第一步走在什么位置就最可能赢 
     93.汉诺塔
    约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔。目的是将最左边杆上的盘全部移到右边的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。
    *问题分析与算法设计
    这是一个著名的问题,几乎所有的教材上都有这个问题。由于条件是一次只能移动一个盘,且不允许大盘放在小盘上面,所以64个盘的移动次数是:
    18,446,744,073,709,551,615
    这是一个天文数字,若每一微秒可能计算(并不输出)一次移动,那么也需要几乎一百万年。我们仅能找出问题的解决方法并解决较小N值时的汉诺塔,但很难用计算机解决64层的汉诺塔。
    分析问题,找出移动盘子的正确算法。
    首先考虑a杆下面的盘子而非杆上最上面的盘子,于是任务变成了:
    *将上面的63个盘子移到b杆上;
    *将a杆上剩下的盘子移到c杆上;
    *将b杆上的全部盘子移到c杆上。
    将这个过程继续下去,就是要先完成移动63个盘子、62个盘子、61个盘子….的工作。
    为了更清楚地描述算法,可以定义一个函数movedisc(n,a,b,c)。该函数的功能是:将N个盘子从A杆上借助C杆移动到B杆上。这样移动N个盘子的工作就可以按照以下过程进行:
    1) movedisc(n-1,a,c,b);
    2) 将一个盘子从a移动到b上;
    3) movedisc(n-1,c,b,a);
    重复以上过程,直到将全部的盘子移动到位时为止。
    *程序说明与注释
    #include<stdio.h>
    void movedisc(unsigned n,char fromneedle,char toneedle,char usingneedle);
    int i=0;
    int main()
    {
    unsigned n;
    printf("please enter the number of disc:");
    scanf("%d",&n); /*输入N值*/
    printf("\tneedle:\ta\t b\t c\n");
    movedisc(n,'a','c','b'); /*从A上借助B将N个盘子移动到C上*/
    printf("\t Total: %d\n",i);
    }
    void movedisc(unsigned n,char fromneedle,char toneedle,char usingneedle)
    {
    if(n>0)
    {
    movedisc(n-1,fromneedle,usingneedle,toneedle);
    /*从fromneedle上借助toneedle将N-1个盘子移动到usingneedle上*/
    ++i;
    switch(fromneedle) /*将fromneedle 上的一个盘子移到toneedle上*/
    {
    case 'a': switch(toneedle)
    {
    case 'b': printf("\t[%d]:\t%2d………>%2d\n",i,n,n);
    break;
    case 'c': printf("\t[%d]:\t%2d……………>%2d\n",i,n,n);
    break;
    }
    break;
    case 'b': switch(toneedle)
    {
    case 'a': printf("\t[%d]:\t%2d<……………>%2d\n",i,n,n);
    break;
    case 'c': printf("\t[%d]:\t %2d……..>%2d\n",i,n,n);
    break;
    }
    break;
    case 'c': switch(toneedle)
    {
    case 'a': printf("\t[%d]:\t%2d<…………%2d\n",i,n,n);
    break;
    case 'b': printf("\t[%d]:\t%2d<……..%2d\n",i,n,n);
    break;
    }
    break;
    }
    movedisc(n-1,usingneedle,toneedle,fromneedle);
    /*从usingneedle上借助fromneedle将N-1个盘子移动到toneedle上*/
    }

     
    94.兎子产子
     
    从前有一对长寿兎子,它们每一个月生一对兎子,新生的小兎子两个月就长大了,在第二个月的月底开始生它们的下一代小兎子,这样一代一代生下去,求解兎子增长数量的数列。
    *问题分析与算法设计
    问题可以抽象成下列数学公式:
    Un=Un-1+Un-2
    其中:
    n是项数(n>=3)。它就是著名的菲波那奇数列,该数列的前几为:1,1,2,3,5,8,13,21…
    菲波那奇数列在程序中可以用多种方法进行处理。按照其通项递推公式利用最基本的循环控制就可以实现题目的要求。
    *程序说明与注释
    #include<stdio.h>
    int main()
    {
    int n,i,un1,un2,un;
    for(n=2;n<3;)
    {
    printf("Please enter required number of generation:");
    scanf("%d",&n);
    if(n<3) printf("\n Enter error!\n"); /*控制输入正确的N值*/
    }
    un=un2=1;
    printf("The repid increase of rabbits in first %d generation is as felow:\n",n);
    printf("l\tl\t");
    for(i=3;i<=n;i++)
    {
    un1=un2;
    un2=un;
    un=un1+un2; /*利用通项公式求解N项的值*/
    printf(i%10?"%d\t":"%d\n",un);
    }
    printf("\n");
    }
    *运行结果
    Please enter required number of generation: 20
    The repid increase of rabbits in first 20 generation is as felow:
    1 1 2 3 5 8 13 21 34 55
    89 144 233 377 610 987 1597 2584 4181 6765 
     
    95.将阿拉伯数字转换为罗马数字
    将大于0小于1000的阿拉伯数字转换为罗马数字。阿拉伯数字与罗马数字的对应关系如下:
    1 2 3 4 5 ……
    I II III IV V ……
    *问题分析与算法设计
    题目中给出了阿拉伯数字与罗马数字的对应关系,题中的数字转换实际上就是查表翻译。即将整数的百、十、个位依次从整数中分解出来,查找表中相应的行后输出对应的字符。
    *程序与程序设计
    #include<stdio.h>
    int main()
    {
    static char *a[][10]={"","I","II","III","IV","V","VI","VII","VIII","IX"
    "","X","XX","XXX","XL","L","LX","LXX","LXXX","XCC",
    "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"
    }; /*建立对照表*/
    int n,t,i,m;
    printf("Please enter number:");
    scanf("%d",&n); /*输入整数*/
    printf("%d=",n);
    for(m=0,i=1000;m<3;m++,i/=10)
    {
    t=(n%i)/(i/10); /*从高位向低位依次取各位的数字*/
    printf("%s",a[2-m][t]); /*通过对照表翻译输出*/
    }
    printf("\n");
    }
    *运行结果
    1. Please enter number:863
    863=DCCCLXIII
    2. Please enter number: 256
    256=CCLVI
    3. Please enter number:355
    355=CCCLV
    4. Please enter number:522
    522=DXXII
    5. Please enter number:15
    15=XV
    *思考题
    输入正整数N,产生对应的英文数字符串并输出,例如:
    1 ONE 2 TWO 3 THREE
    10 TEN 11 ELEVEN
    135 ONE HUNDRED THIRTY FIVE 
     
    96.选美比赛
    在选美大奖赛的半决胜赛现场,有一批选手参加比赛,比赛的规则是最后得分越高,名次越低。当半决决赛结束时,要在现场按照选手的出场顺序宣布最后得分和最后名次,获得相同分数的选手具有相同的名次,名次连续编号,不用考虑同名次的选手人数。例如:
    选手序号: 1,2,3,4,5,6,7
    选手得分: 5,3,4,7,3,5,6
    则输出名次为: 3,1,2,5,1,3,4
    请编程帮助大奖赛组委会完成半决赛的评分和排名工作。
    *问题分析与算法设计
    问题用程序设计语言加以表达的话,即为:将数组A中的整数从小到大进行连续编号,要求不改变数组中元素的顺序,且相同的整数要具有相同的编号。
    普通的排序方法均要改变数组元素原来的顺序,显然不能满足要求。为此,引入一个专门存放名次的数组,再采用通常的算法:在尚未排出名次的元素中找出最小值,并对具有相同值的元素进行处理,重复这一过程,直到全部元素排好为止。
    *程序说明与注释
    #include<stdio.h>
    #define NUM 7 /*定义要处理的人数*/
    int a[NUM+1]={0,5,3,4,7,3,5,6}; /*为简单直接定义选手的分数*/
    int m[NUM+1],l[NUM+1]; /*m:已编名次的标记数组 l:记录同名次元素的下标*/
    int main()
    {
    int i,smallest,num,k,j;
    num=1; /*名次*/
    for(i=1;i<=NUM;i++) /*控制扫描整个数组,每次处理一个名次*/
    if(m[i]==0) /*若尚未进行名次处理(即找到第一个尚未处理的元素)*/
    {
    smallest=a[i]; /*取第一个未处理的元素作为当前的最小值*/
    k=1; /*数组l的下标,同名次的人数*/
    l[k]=i; /*记录分值为smallest的同名次元素的下标*/
    for(j=i+1;j<=NUM;j++) /*从下一个元素开始对余下的元素进行处理*/
    if(m[j]==0) /*若为尚未进行处理的元素*/
    if(a[j]<smallest) /*分数小于当前最小值*/
    {
    smallest=a[j]; /*则重新设置当覵最小值*/
    k=0; /*重新设置同名次人数*/
    l[++k]=j; /*重新记录同名次元素下标*/
    }
    else if(a[j]==smallest) /*若与当前最低分相同*/
    l[++k]=j; /*记录同名次的元素下标*/
    for(j=1;j<=k;j++) /*对同名次的元素进行名次处理*/
    m[l[j>=num;
    num++; /*名次加1*/
    i=0; /*控制重新开始,找下一个没排名次的元素*/
    }
    printf("Player-No score Rank\n");
    for(j=1;j<=NUM;j++) /*控制输出*/
    printf(" %3d %4d %4d\n",j,a[j],m[j]);
    }
    *运行结果
    Player-No Score Rank
    1 5 3
    2 3 1
    3 4 2
    5 7 5
    5 3 1
    3 5 3
    7 6 4
    *思考题
    若将原题中的“名次连续编号,不用考虑同名次的选手人数”,改为”根据同名次的选手人数对选手的名次进行编号“,那么应该怎样修改程序。 
    97.满足特异条件的数列
    输入m和n(20>=m>=n>0)求出满足以下方程的正整数数列 i1,i2,…,in,使得:i1+i1+…+in=m,且i1>=i2…>=in。例如:
    当n=4, m=8时,将得到如下5 个数列:
    5 1 1 1 4 2 1 1 3 3 1 1 3 2 2 1 2 2 2 2
    *问题分析与算法设计
    可将原题抽象为:将M分解为N个整数,且N个整数的和为M,i1>=i2>=…>=in。分解整数的方法很低多,由于题目中有"i1>=i2>=…..>=in,提示我们可先确定最右边in元素的值为1,然后按照条件使前一个元素的值一定大于等于当前元素的值,不断地向前推就可以解决问题。下面的程序允许用户选定M和N,输出满足条件的所有数列。
    *程序说明与注释
    #include<stdio.h>
    #define NUM 10 /*允许分解的最大元素数量*/
    int i[NUM]; /*记录分解出的数值的数组*/
    int main()
    {
    int sum,n,total,k,flag,count=0;
    printf("Please enter requried terms(<=10):");
    scanf("%d",&n);
    printf(" their sum:");
    scanf("%d",&total);
    sum=0; /*当前从后向前k个元素的和*/
    k=n; /*从后向前正在处理的元素下标*/
    i[n]=1; /*将最后一个元素的值置为1作为初始值*/
    printf("There are following possible series:\n");
    while(1)
    {
    if(sum+i[k]<total) /*若后k位的和小于指定的total*/
    if(k<=1) /*若正要处理的是第一个元素*/
    {i[1]=total-sum;flag=1;} /*则计算第一个元素的并置标记*/
    else{
    sum+=i[k–];
    i[k]=i[k+1]; /*置第k位的值后k-1*/
    continue; /*继续向前处理其它元素*/
    }
    else if(sum+i[k]>total||k!=1) /*若和已超过total或不是第一个元素*/
    { sum-=i[++k]; flag=0;} /*k向后回退一个元素*/
    else flag=1; /*sum+i[k]=total&&k=1 则设置flag标记*/
    if(flag)
    {
    printf("[%d]:",++count);
    for(flag=1;flag<=n;++flag)
    printf("%d",i[flag]);
    printf("\n");
    }
    if(++k>n) /*k向后回退一个元素后判断是否已退出最后一个元素*/
    break;
    sum-=i[k];
    i[k]++; /*试验下一个分解*/
    }
    }
    *运行结果
    Please enter requried terms(<=10):4
    their sum:8
    There are following possible series:
    [1]: 5111
    [2]: 4211
    [3]: 3311
    [4]: 3221
    [5]: 2222 
     
    98.八皇后问题
    在一个8×8国际象棋盘上,有8个皇后,每个皇后占一格;要求皇后间不会出现相互“攻击”的现象,即不能有两个皇后处在同一行、同一列或同一对角线上。问共有多少种不同的方法。
    *问题分析与算法设计
    这是一个古老的具有代表性的问题,用计算机求解时的算法也很多,这里仅介绍一种。
    采用一维数组来进行处理。数组的下标i表示棋盘上的第i列,a[i]的值表示皇后在第i列所放的位置。如:a[1]=5,表示在棋盘的第一例的第五行放一个皇后。
    程序中首先假定a[1]=1,表示第一个皇后放在棋盘的第一列的第一行的位置上,然后试探第二列中皇后可能的位置,找到合适的位置后,再处理后续的各列,这样通过各列的反复试探,可以最终找出皇后的全部摆放方法。
    程序采用回溯法,算法的细节参看程序。
    *程序说明与注释
    #include<stdio.h>
    #define NUM 8 /*定义数组的大小*/
    int a[NUM+1];
    int main()
    {
    int i,k,flag,not_finish=1,count=0;
    i=1; /*正在处理的元素下标,表示前i-1个元素已符合要求,正在处理第i个元素*/
    a[1]=1; /*为数组的第一个元素赋初值*/
    printf("The possible configuration of 8 queens are:\n");
    while(not_finish) /*not_finish=1:处理尚未结束*/
    {
    while(not_finish&&i<=NUM) /*处理尚未结束且还没处理到第NUM个元素*/
    {
    for(flag=1,k=1;flag&&k<i;k++) /*判断是否有多个皇后在同一行*/
    if(a[k]==a[i])flag=0;
    for(k=1;flag&&k<i;k++) /*判断是否有多个皇后在同一对角线*/
    if((a[i]==a[k]-(k-i))||(a[i]==a[k]+(k-i))) flag=0;
    if(!flag) /*若存在矛盾不满足要求,需要重新设置第i个元素*/
    {
    if(a[i]==a[i-1]) /*若a[i]的值已经经过一圈追上a[i-1]的值*/
    {
    i–; /*退回一步,重新试探处理前一个元素*/
    if(i>1&&a[i]==NUM)
    a[i]=1; /*当a[i]为NUM时将a[i]的值置1*/
    else if(i==1&&a[i]==NUM)
    not_finish=0; /*当第一位的值达到NUM时结束*/
    else a[i]++; /*将a[i]的值取下一个值*/
    }
    else if(a[i]==NUM) a[i]=1;
    else a[i]++; /*将a[i]的值取下一个值*/
    }
    else if(++i<=NUM)
    if(a[i-1]==NUM) a[i]=1; /*若前一个元素的值为NUM则a[i]=1*/
    else a[i]=a[i-1]+1; /*否则元素的值为前一个元素的下一个值*/
    }
    if(not_finish)
    {
    ++count;
    printf((count-1)%3?" [%2d]: ":" \n[%2d]: ",count);
    for(k=1;k<=NUM;k++) /*输出结果*/
    printf(" %d",a[k]);
    if(a[NUM-1]<NUM) a[NUM-1]++; /*修改倒数第二位的值*/
    else a[NUM-1]=1;
    i=NUM-1; /*开始寻找下一个足条件的解*/
    }
    }
    }
    *思考题
    一个8×8的国际象棋盘,共有64个格子。最多将五个皇后放入棋盘中,就可以控制整个的盘面,不论对方的棋子放哪一格中都会被吃掉。请编程
    99.超长正整数的加法
    请设计一个算法来完成两个超长正整数的加法。
    *问题分析与算法设计
    首先要设计一种数据结构来表示一个超长的正整数,然后才能够设计算法。
    首先我们采用一个带有表头结点的环形链来表示一个非负的超大整数,如果从低位开始为每 个数字编号,则第一位到第四位、第五位到第八位…的每四位组成的数字,依次放在链表的第一个、第二个、…结点中,不足4位的最高位存放在链表的最后一个结点中,表头结点的值规定为-1。例如:
    大整数“587890987654321”可用如下的带表头结点head的链表表示:
    按照此数据结构,可以从两个表头结点开始,顺序依次对应相加,求出所需要的进位后代入下面的运算。具体的实现算法请见程序中的注释。
    *程序说明与注释
    #include<stdio.h>
    #include<stdlib.h>
    #define HUNTHOU 10000
    typedef struct node{ int data;
    struct node *next;
    }NODE; /*定义链表结构*/
    NODE *insert_after(NODE *u,int num); /*在u结点后插入一个新的NODE,其值为num*/
    NODE *addint(NODE *p,NODE *q); /*完成加法操作返回指向*p+*q结果的指针*/
    void printint(NODE *s);
    NODE *inputint(void);
    int main()
    {
    NODE *s1,*s2,*s;
    NODE *inputint(), *addint(), *insert_after();
    printf("Enter S1= ");
    s1=inputint(); /*输入被加数*/
    printf("Enter S2= ");
    s2=inputint(); /*输入加数*/
    printf(" S1="); printint(s1); putchar('\n'); /*显示被加数*/
    printf(" S2="); printint(s2); putchar('\n'); /*显示加数*/
    s=addint(s1,s2); /*求和*/
    printf("S1+S2="); printint(s); putchar('\n'); /*输出结果*/
    }
    NODE *insert_after(NODE *u,int num)
    {
    NODE *v;
    v=(NODE *)malloc(sizeof(NODE)); /*申请一个NODE*/
    v->data=num; /*赋值*/
    u->next=v; /*在u结点后插入一个NODE*/
    return v;
    }
    NODE *addint(NODE *p,NODE *q) /*完成加法操作返回指向*p+*q结果的指针*/
    {
    NODE *pp,*qq,*r,*s,*t;
    int total,number,carry;
    pp=p->next; qq=q->next;
    s=(NODE *)malloc(sizeof(NODE)); /*建立存放和的链表表头*/
    s->data=-1;
    t=s; carry=0; /*carry:进位*/
    while(pp->data!=-1&&qq->data!=-1) /*均不是表头*/
    {
    total=pp->data+qq->data+carry; /*对应位与前次的进位求和*/
    number=total%HUNTHOU; /*求出存入链中部分的数值 */
    carry=total/HUNTHOU; /*算出进位*/
    t=insert_after(t,number); /*将部分和存入s向的链中*/
    pp=pp->next; /*分别取后面的加数*/
    qq=qq->next;
    }
    r=(pp->data!=-1)?pp:qq; /*取尚未自理完毕的链指针*/
    while(r->data!=-1) /*处理加数中较大的数*/
    {
    total=r->data+carry; /*与进位相加*/
    number=total%HUNTHOU; /*求出存入链中部分的数值*/
    carry=total/HUNTHOU; /*算出进位*/
    t=insert_after(t,number); /*将部分和存入s指向的链中*/
    r=r->next; /*取后面的值*/
    }
    if(carry) t=insert_after(t,1); /*处理最后一次进位*/
    t->next=s; /*完成和的链表*/
    return s; /*返回指向和的结构指针*/
    }
    NODE *inputint(void) /*输入超长正整数*/
    {
    NODE *s,*ps,*qs;
    struct number {int num;
    struct number *np;
    }*p,*q;
    int i,j,k;
    long sum;
    char c;
    p=NULL; /*指向输入的整数,链道为整数的最低的个位,链尾为整数的最高位*/
    while((c=getchar())!='\n') /*输入整数,按字符接收数字*/
    if(c>='0'&&c<='9') /*若为数字则存入*/
    {
    q=(struct number *)malloc(sizeof(struct number)); /*申请空间*/
    q->num=c-'0'; /*存入一位整数*/
    q->np=p; /*建立指针*/
    p=q;
    }
    s=(NODE *)malloc(sizeof(NODE));
    s->data=-1; /*建立表求超长正整数的链头*/
    ps=s;
    while(p!=NULL) /*将接收的临时数据链中的数据转换为所要求的标准形式*/
    {
    sum=0;i=0;k=1;
    while(i<4&&p!=NULL) /*取出低四位*/
    {
    sum=sum+k*(p->num); 
    i++; p=p->np; k=k*10;
    }
    qs=(NODE *)malloc(sizeof(NODE)); /*申请空间*/
    qs->data=sum; /*赋值,建立链表*/
    ps->next=qs;
    ps=qs;
    }
    ps->next=s;
    return s;
    }
    void printint(NODE *s)
    {
    if(s->next->data!=-1) /*若不是表头,则输出*/
    {
    printint(s->next); /*递归输出*/
    if(s->next->next->data==-1)
    printf("%d",s->next->data);
    else{
    int i,k=HUNTHOU;
    for(i=1;i<=4;i++,k/=10)
    putchar('0'+s->next->data%(k)/(k/10));
    }
    }
    }
    *运行结果
    *思考题
     
    100.数字移动
    在图中的九个点上,空出中间的点,其余的点上任意填入数字1到8;1的位置固定不动,然后移动其余的数字,使1到8顺时针从小到大排列.移动的规律是:只能将数字沿线移向空白的点.
    请编程显示数字移动过程。
     
    *问题分析与算法设计
    分析题目中的条件,要求利用中间的空白格将数字顺时针方向排列,且排列过程中只能借空白的点来移动数字.问题的实质就是将矩阵外面的8个格看成一个环,8个数字在环内进行排序,同于受题目要求的限制"只能将数字沿线移向空白的点",所以要利用中间的空格进行排序,这样要求的排序算法与众不同.
    观察中间的点,它是唯一一个与其它8个点有连线的点,即它是中心点.中心点的活动的空间最大,它可以向8个方向移动,充分利用中心点这个特性是算法设计成功与否的关键.
    在找到1所在的位置后,其余各个数字的正确位置就是固定的.我们可以按照下列算法从数字2开始,一个一个地来调整各个数字的位置.
    *确定数字i应处的位置;
    *从数字i应处的位置开始,向后查找数字i现在的位置;
    *若数字i现在位置不正确,则将数字i从现在的位置(沿连线)移向中间的空格,而将原有位置空出;依次将现有空格前的所有元素向后移动;直到将i应处的位置空出,把它移入再次空出中间的格.
    从数字2开始使用以上过程,就可以完成全部数字的移动排序.
    编程时要将矩阵的外边八个格看成一个环,且环的首元素是不定的,如果算法设计得不好,程序中就要花很多精力来处理环中元素的前后顺序问题.将题目中的3X3矩阵用一个一维数组表示,中间的元素(第四号)刚好为空格,设计另一个指针数组,专门记录指针外八个格构成环时的连接关系.指针数组的每个元素依次记录环中数字在原来数组中对应的元素下标.这样通过指针数组将原来矩阵中复杂的环型关系表示成了简单的线性关系,从而大大地简化了程序设计.
    *程序说明与注释
    #include<stdio.h>
    int a[]={0,1,2,5,8,7,6,3}; /*指针数组.依次存入矩阵中构成环的元素下标*/
    int b[9]; /*表示3X3矩阵,b[4]为空格*/
    int c[9]; /*确定1所在的位置后,对环进行调整的指针数组*/
    int count=0; /*数字移动步数计数器*/
    int main()

    int i,j,k,t;
    void print();
    printf("Please enter original order of digits 1~8:");
    for(i=0;i<8;i++)
    scanf("%d",&b[a[i>);
    /*顺序输入矩阵外边的8个数字,矩阵元素的顺序由指针数组的元素a[i]控制*/
    printf("The sorting process is as felow:\n");
    print();
    for(t=-1,j=0;j<8&&t==-1;j++) /*确定数字1所在的位置*/
    if(b[a[j>==1) t=j; /*t:记录数字1所在的位置*/ 
    for(j=0;j<8;j++) /*调整环的指针数组,将数字1所在的位置定为环的首*/
    c[j]=a[(j+t)%8];
    for(i=2;i<9;i++) /*从2开始依次调整数字的位置*/
    /*i:正在处理的数字,i对应在环中应当的正确位置就是i-1*/
    for(j=i-1;j<8;j++) /*从i应处的正确位置开始顺序查找*/
    if(b[c[j>==i&&j!=i-1) /*若i不在正确的位置*/
    {
    b[4]=i; /*将i移到中心的空格中*/
    b[c[j>=0;print(); /*空出i原来所在的位置,输出*/
    for(k=j;k!=i-1;k–) /*将空格以前到i的正确位置之间的数字依次向后移动一格*/
    {
    b[c[k>=b[c[k-1>; /*数字向后移动*/
    b[c[k-1>=0;
    print();
    }
    b[c[k>=i; /*将中间的数字i移入正确的位置*/
    b[4]=0; /*空出中间的空格*/
    print();
    break;
    }
    else if(b[c[j>==i) break; /*数字i在正确的位置*/
    }
    void print(void) /*按格式要求输出矩阵*/
    {
    int c;
    for(c=0;c<9;c++)
    if(c%3==2) printf("%2d ",b[c]);
    else printf("%2d",b[c]);
    printf("—-%2d—-\n",count++);
    }
    *运行结果
    *问题的进一步讨论
    很显然,按照上述算法都能解决问题,但移动的步数并不是最少的。
    注意算法中的两个问题。其一:数字1的位置自始自终是保持不变的;其2:没有考虑到初始情况下,位置原本就已经是正确的数字。如例中的数字5和6,按照算法,当移动其它数字时,5和6了要跟着移动多次,这显然费了不少步数。
    对于实例,若让数字1参与其它数字的移动排序过程,并充分利用数字5和6初始位置已经正确这一条件,可以大大优化移动排序的过程。
    *思考题
    请重新设计算法,编写更优化的程序,尽可能减少移动的步数。
    请设计完成两个超长正整数的减法、乘法和除法的运算

    展开全文
  • shell编程100例

    万次阅读 多人点赞 2018-09-06 23:59:55
    /bin/bash # 脚本生成一个 100 以内的随机数,提示用户猜数字,根据用户的输入,提示用户猜对了, # 猜了或猜大了,直至用户猜对脚本结束。 # RANDOM 为系统自带的系统变量,值为 0‐32767的随机数 # 使用取余算法将...

    1、编写hello world脚本

    #!/bin/bash
    
    # 编写hello world脚本
    
    echo "Hello World!"

    2、通过位置变量创建 Linux 系统账户及密码

    #!/bin/bash
    
    # 通过位置变量创建 Linux 系统账户及密码
    
    #$1 是执行脚本的第一个参数,$2 是执行脚本的第二个参数
    useradd    "$1" 
    echo "$2"  |  passwd  ‐‐stdin  "$1"

    3、备份日志

    #!/bin/bash
    # 每周 5 使用 tar 命令备份/var/log 下的所有日志文件
    # vim  /root/logbak.sh
    # 编写备份脚本,备份后的文件名包含日期标签,防止后面的备份将前面的备份数据覆盖
    # 注意 date 命令需要使用反引号括起来,反引号在键盘<tab>键上面
    tar	-czf	log-`date +%Y%m%d`.tar.gz	/var/log 
     
    # crontab ‐e	#编写计划任务,执行备份脚本
    00	03	*	*	5	/root/logbak.sh

    4、一键部署 LNMP(RPM 包版本)

    #!/bin/bash
    # 一键部署 LNMP(RPM 包版本)
    # 使用 yum 安装部署 LNMP,需要提前配置好 yum 源,否则该脚本会失败
    # 本脚本使用于 centos7.2 或 RHEL7.2
    yum ‐y install httpd
    yum ‐y install mariadb mariadb‐devel mariadb‐server
    yum ‐y install php  php‐mysql
     
    systemctl start httpd mariadb
    systemctl enable httpd mariadb

    5、监控内存和磁盘容量,小于给定值时报警

    #!/bin/bash
    
    # 实时监控本机内存和硬盘剩余空间,剩余内存小于500M、根分区剩余空间小于1000M时,发送报警邮件给root管理员
    
    # 提取根分区剩余空间
    disk_size=$(df / | awk '/\//{print $4}')
    
    # 提取内存剩余空间
    mem_size=$(free | awk '/Mem/{print $4}')
    while :
    do
    # 注意内存和磁盘提取的空间大小都是以 Kb 为单位
    if  [  $disk_size -le 512000 -a $mem_size -le 1024000  ]
    then
        mail  ‐s  "Warning"  root  <<EOF
    	Insufficient resources,资源不足
    EOF
    fi
    done

    6、猜数字游戏

     #!/bin/bash
    
    # 脚本生成一个 100 以内的随机数,提示用户猜数字,根据用户的输入,提示用户猜对了,
    # 猜小了或猜大了,直至用户猜对脚本结束。
    
    # RANDOM 为系统自带的系统变量,值为 0‐32767的随机数
    # 使用取余算法将随机数变为 1‐100 的随机数
    num=$[RANDOM%100+1]
    echo "$num"
     
    # 使用 read 提示用户猜数字
    # 使用 if 判断用户猜数字的大小关系:‐eq(等于),‐ne(不等于),‐gt(大于),‐ge(大于等于),
    # ‐lt(小于),‐le(小于等于)
    while  :
    do
    	read -p "计算机生成了一个 1‐100 的随机数,你猜: " cai
        if [ $cai -eq $num ]
        then
           	echo "恭喜,猜对了"
           	exit
        	elif [ $cai -gt $num ]
        	then
               	echo "Oops,猜大了"
          	else
               	echo "Oops,猜小了"
     	fi
    done

    7、检测本机当前用户是否为超级管理员,如果是管理员,则使用 yum 安装 vsftpd,如果不是,则提示您非管理员(使用字串对比版本)

    #!/bin/bash
    
    # 检测本机当前用户是否为超级管理员,如果是管理员,则使用 yum 安装 vsftpd,如果不
    # 是,则提示您非管理员(使用字串对比版本) 
    if [ $USER == "root" ]
    then
    	yum ‐y install vsftpd
    else
        echo "您不是管理员,没有权限安装软件"
    fi

    8、检测本机当前用户是否为超级管理员,如果是管理员,则使用 yum 安装 vsftpd,如果不是,则提示您非管理员(使用 UID 数字对比版本)

    #!/bin/bash
    
    # 检测本机当前用户是否为超级管理员,如果是管理员,则使用 yum 安装 vsftpd,如果不
    # 是,则提示您非管理员(使用 UID 数字对比版本)
    if [ $UID -eq 0 ];then
        yum ‐y install vsftpd
    else
        echo "您不是管理员,没有权限安装软件"
    fi

    9、编写脚本:提示用户输入用户名和密码,脚本自动创建相应的账户及配置密码。如果用户不输入账户名,则提示必须输入账户名并退出脚本;如果用户不输入密码,则统一使用默认的 123456 作为默认密码。

    #!/bin/bash
    
    # 编写脚本:提示用户输入用户名和密码,脚本自动创建相应的账户及配置密码。如果用户
    # 不输入账户名,则提示必须输入账户名并退出脚本;如果用户不输入密码,则统一使用默
    # 认的 123456 作为默认密码。
    
    read -p "请输入用户名: " user
    #使用‐z 可以判断一个变量是否为空,如果为空,提示用户必须输入账户名,并退出脚本,退出码为 2
    #没有输入用户名脚本退出后,使用$?查看的返回码为 2
    if [ -z $user ];then
       	echo "您不需输入账户名"
     	exit 2
    fi
    #使用 stty ‐echo 关闭 shell 的回显功能
    #使用 stty  echo 打开 shell 的回显功能
    stty -echo
    read -p "请输入密码: " pass
    stty echo
    pass=${pass:‐123456}
    useradd "$user"
    echo "$pass" | passwd ‐‐stdin "$user"

    10、输入三个数并进行升序排序

    #!/bin/bash
    
    # 依次提示用户输入 3 个整数,脚本根据数字大小依次排序输出 3 个数字
    read -p "请输入一个整数:" num1
    read -p "请输入一个整数:" num2
    read -p "请输入一个整数:" num3
    # 不管谁大谁小,最后都打印 echo "$num1,$num2,$num3"
    # num1 中永远存最小的值,num2 中永远存中间值,num3 永远存最大值
    # 如果输入的不是这样的顺序,则改变数的存储顺序,如:可以将 num1 和 num2 的值对调
    tmp=0
    # 如果 num1 大于 num2,就把 num1 和和 num2 的值对调,确保 num1 变量中存的是最小值
    if [ $num1 -gt $num2 ];then   
    	tmp=$num1
    	num1=$num2
    	num2=$tmp
    fi
    # 如果 num1 大于 num3,就把 num1 和 num3 对调,确保 num1 变量中存的是最小值
    if [ $num1 -gt $num3 ];then   
      	tmp=$num1
      	num1=$num3
      	num3=$tmp
    fi
    # 如果 num2 大于 num3,就把 num2 和 num3 对标,确保 num2 变量中存的是小一点的值
    if [ $num2 -gt $num3 ];then
      	tmp=$num2
      	num2=$num3
      	num3=$tmp
    fi
    echo "排序后数据(从小到大)为:$num1,$num2,$num3"

    11、石头、剪刀、布游戏

    #!/bin/bash
    
    # 编写脚本,实现人机<石头,剪刀,布>游戏
    game=(石头 剪刀 布)
    num=$[RANDOM%3]
    computer=${game[$num]}
    # 通过随机数获取计算机的出拳
    # 出拳的可能性保存在一个数组中,game[0],game[1],game[2]分别是 3 中不同的可能
     
    echo "请根据下列提示选择您的出拳手势"
    echo "1.石头"
    echo "2.剪刀"
    echo "3.布"
     
    read -p "请选择 1‐3:" person
    case  $person  in
    1)
      if [ $num -eq 0 ]
      then
        echo "平局"
        elif [ $num -eq 1 ]
        then
          echo "你赢"
      else
        echo "计算机赢"
      fi;;
    2)   
      if [ $num -eq 0 ]
      then
        echo "计算机赢"
        elif [ $num -eq 1 ]
        then
          echo "平局"
      else
        echo "你赢"
      fi;;
    3)
      if [ $num -eq 0 ]
      then
        echo "你赢"
        elif [ $num -eq 1 ]
        then
          echo "计算机赢"
      else
        echo "平局"
      fi;;
    *)
      echo "必须输入 1‐3 的数字"
    esac

    12、编写脚本测试 192.168.4.0/24 整个网段中哪些主机处于开机状态,哪些主机处于关机状态(for 版本)

    #!/bin/bash
    
    # 编写脚本测试 192.168.4.0/24 整个网段中哪些主机处于开机状态,哪些主机处于关机
    # 状态(for 版本)
    for i in {1..254}
    do
    	# 每隔0.3秒ping一次,一共ping2次,并以1毫秒为单位设置ping的超时时间
       	ping ‐c 2 ‐i 0.3 ‐W 1 192.168.4.$i  &>/dev/null
        if  [ $? -eq 0 ];then
           	echo "192.168.4.$i is up"
       	else
           	echo  "192.168.4.$i is down"
       	fi
    done

    13、编写脚本测试 192.168.4.0/24 整个网段中哪些主机处于开机状态,哪些主机处于关机状态(while 版本) 

    #!/bin/bash
    
    # 编写脚本测试 192.168.4.0/24 整个网段中哪些主机处于开机状态,哪些主机处于关机
    # 状态(while 版本) 
    i=1
    while [ $i -le 254 ]
    do
       	ping ‐c 2 ‐i 0.3 ‐W 1 192.168.4.$i  &>/dev/null
       	if  [ $? -eq 0 ];then
           	echo "192.168.4.$i is up"
        else
           	echo  "192.168.4.$i is down"
       	fi
       	let i++
    done

    14、编写脚本测试 192.168.4.0/24 整个网段中哪些主机处于开机状态,哪些主机处于关机状态(多进程版)

    #!/bin/bash
    
    # 编写脚本测试 192.168.4.0/24 整个网段中哪些主机处于开机状态,哪些主机处于关机
    # 状态(多进程版)
    
    #定义一个函数,ping 某一台主机,并检测主机的存活状态
    myping(){
    ping ‐c 2 ‐i 0.3 ‐W 1 $1  &>/dev/null
    if  [ $? -eq 0 ];then
    	echo "$1 is up"
    else
    	echo "$1 is down"
    fi
    }
    for i in {1..254}
    do
       	myping 192.168.4.$i &
    done
    # 使用&符号,将执行的函数放入后台执行
    # 这样做的好处是不需要等待ping第一台主机的回应,就可以继续并发ping第二台主机,依次类推。

    15、编写脚本,显示进度条

    #!/bin/bash
    
    # 编写脚本,显示进度条
    jindu(){
    while :
    do
       	echo -n '#'
       	sleep 0.2
    done
    }
    jindu &
    cp -a $1 $2
    killall $0
    echo "拷贝完成"

    16、进度条,动态时针版本;定义一个显示进度的函数,屏幕快速显示|  / ‐ \

    #!/bin/bash
    
    # 进度条,动态时针版本
    # 定义一个显示进度的函数,屏幕快速显示|  / ‐ \
    rotate_line(){
    INTERVAL=0.5  #设置间隔时间
    COUNT="0"     #设置4个形状的编号,默认编号为 0(不代表任何图像)
    while :
    do
      COUNT=`expr $COUNT + 1` #执行循环,COUNT 每次循环加 1,(分别代表4种不同的形状)
      case $COUNT in          #判断 COUNT 的值,值不一样显示的形状就不一样
      "1")                    #值为 1 显示‐
              echo -e '‐'"\b\c"
              sleep $INTERVAL
              ;;
        "2")                  #值为 2 显示\\,第一个\是转义
              echo -e '\\'"\b\c"
              sleep $INTERVAL
              ;;
        "3")                  #值为 3 显示|
              echo -e "|\b\c"
              sleep $INTERVAL
              ;;
       "4")                   #值为 4 显示/
              echo -e "/\b\c"
              sleep $INTERVAL
              ;;
        *)                    #值为其他时,将 COUNT 重置为 0
              COUNT="0";;
        esac
    done
    }
    rotate_line

    17、9*9 乘法表

    #!/bin/bash
    
    # 9*9 乘法表(编写 shell 脚本,打印 9*9 乘法表) 
    for i in `seq 9`
    do
      	for j in `seq $i`
       	do
           	echo -n "$j*$i=$[i*j]  "
       	done
        echo
    done

    18、使用死循环实时显示 eth0 网卡发送的数据包流量

    #!/bin/bash
    
    # 使用死循环实时显示 eth0 网卡发送的数据包流量 
    
    while :
    do
     	echo  '本地网卡 eth0 流量信息如下: '
      	ifconfig eth0 | grep "RX pack" | awk '{print $5}'
        ifconfig eth0 | grep "TX pack" | awk '{print $5}'
       	sleep 1
    done

    19、使用 user.txt 文件中的人员名单,在计算机中自动创建对应的账户并配置初始密码本脚本执行,需要提前准备一个 user.txt 文件,该文件中包含有若干用户名信息

    #!/bin/bash
    
    # 使用 user.txt 文件中的人员名单,在计算机中自动创建对应的账户并配置初始密码
    # 本脚本执行,需要提前准备一个 user.txt 文件,该文件中包含有若干用户名信息
    for i in `cat user.txt`
    do
       	useradd  $i
       	echo "123456" | passwd ‐‐stdin $i
    done

    20、编写批量修改扩展名脚本

    #!/bin/bash
    
    # 编写批量修改扩展名脚本,如批量将 txt 文件修改为 doc 文件 
    # 执行脚本时,需要给脚本添加位置参数
    # 脚本名  txt  doc(可以将 txt 的扩展名修改为 doc)
    # 脚本名  doc  jpg(可以将 doc 的扩展名修改为 jpg)
     
    for i in `ls *.$1`
    do
       	mv $i ${i%.*}.$2
    done

    21、使用 expect 工具自动交互密码远程其他主机安装 httpd 软件

    #!/bin/bash
    
    # 使用 expect 工具自动交互密码远程其他主机安装 httpd 软件 
    
    # 删除~/.ssh/known_hosts 后,ssh 远程任何主机都会询问是否确认要连接该主机
    rm  ‐rf  ~/.ssh/known_hosts
    expect <<EOF
    spawn ssh 192.168.4.254
    
    expect "yes/no" {send "yes\r"}
    # 根据自己的实际情况将密码修改为真实的密码字串
    expect "password" {send  "密码\r"}
    expect "#" {send  "yum ‐y install httpd\r"}
    expect "#" {send  "exit\r"}
    EOF

    22、一键部署 LNMP(源码安装版本)

    #!/bin/bash
    
    # 一键部署 LNMP(源码安装版本)
    menu()
    {
    clear
    echo "  ##############‐‐‐‐Menu‐‐‐‐##############"
    echo "# 1. Install Nginx"
    echo "# 2. Install MySQL"
    echo "# 3. Install PHP"
    echo "# 4. Exit Program"
    echo "  ########################################"
    }
     
    choice()
    {
      read -p "Please choice a menu[1‐9]:" select
    }
     
    install_nginx()
    {
      id nginx &>/dev/null
      if [ $? -ne 0 ];then
        useradd -s /sbin/nologin nginx
      fi
      if [ -f nginx‐1.8.0.tar.gz ];then
        tar -xf nginx‐1.8.0.tar.gz
        cd nginx‐1.8.0
        yum -y install  gcc pcre‐devel openssl‐devel zlib‐devel make
        ./configure ‐‐prefix=/usr/local/nginx ‐‐with‐http_ssl_module
        make
        make install
        ln -s /usr/local/nginx/sbin/nginx /usr/sbin/
        cd ..
      else
        echo "没有 Nginx 源码包"
      fi
    }
     
    install_mysql()
    {
      yum -y install gcc gcc‐c++ cmake ncurses‐devel perl
      id mysql &>/dev/null
      if [ $? -ne 0 ];then
        useradd -s /sbin/nologin mysql
      fi
      if [ -f mysql‐5.6.25.tar.gz ];then
        tar -xf mysql‐5.6.25.tar.gz
        cd mysql‐5.6.25
        cmake .
        make
        make install
        /usr/local/mysql/scripts/mysql_install_db ‐‐user=mysql ‐‐datadir=/usr/local/mysql/data/
    ‐‐basedir=/usr/local/mysql/
        chown -R root.mysql /usr/local/mysql
        chown -R mysql /usr/local/mysql/data
        /bin/cp -f /usr/local/mysql/support‐files/mysql.server /etc/init.d/mysqld
        chmod +x /etc/init.d/mysqld
        /bin/cp -f /usr/local/mysql/support‐files/my‐default.cnf /etc/my.cnf
        echo "/usr/local/mysql/lib/" >> /etc/ld.so.conf
        ldconfig
        echo 'PATH=\$PATH:/usr/local/mysql/bin/' >> /etc/profile
        export PATH
      else
        echo  "没有 mysql 源码包"
        exit
      fi
    }
     
    install_php()
    {
    #安装 php 时没有指定启动哪些模块功能,如果的用户可以根据实际情况自行添加额外功能如‐‐with‐gd 等
    yum  -y  install  gcc  libxml2‐devel
    if [ -f mhash‐0.9.9.9.tar.gz ];then
      tar -xf mhash‐0.9.9.9.tar.gz
      cd mhash‐0.9.9.9
      ./configure
      make
      make install
      cd ..
    if [ ! ‐f /usr/lib/libmhash.so ];then
      ln -s /usr/local/lib/libmhash.so /usr/lib/
    fi
    ldconfig
    else
      echo "没有 mhash 源码包文件"
      exit
    fi
    if [ -f libmcrypt‐2.5.8.tar.gz ];then
      tar -xf libmcrypt‐2.5.8.tar.gz
      cd libmcrypt‐2.5.8
      ./configure
      make
      make install
      cd ..
      if [ ! -f /usr/lib/libmcrypt.so ];then  
        ln -s /usr/local/lib/libmcrypt.so /usr/lib/
      fi
      ldconfig
    else
      echo "没有 libmcrypt 源码包文件"
      exit
    fi
    if [ -f php‐5.4.24.tar.gz ];then
      tar -xf php‐5.4.24.tar.gz
      cd php‐5.4.24
      ./configure  ‐‐prefix=/usr/local/php5  ‐‐with‐mysql=/usr/local/mysql  ‐‐enable‐fpm    ‐‐
      enable‐mbstring  ‐‐with‐mcrypt  ‐‐with‐mhash  ‐‐with‐config‐file‐path=/usr/local/php5/etc  ‐‐with‐
      mysqli=/usr/local/mysql/bin/mysql_config
      make && make install
      /bin/cp -f php.ini‐production /usr/local/php5/etc/php.ini
      /bin/cp -f /usr/local/php5/etc/php‐fpm.conf.default /usr/local/php5/etc/php‐fpm.conf
      cd ..
    else
      echo "没有 php 源码包文件"
      exit
    fi 
    }
     
    while :
    do
      menu
      choice
      case $select in
      1)
        install_nginx
        ;;
      2)
        install_mysql
        ;;
      3)
        install_php
        ;;
      4)
        exit
        ;;
      *)
        echo Sorry!
      esac
    done

    23、编写脚本快速克隆 KVM 虚拟机

    #!/bin/bash
    
    # 编写脚本快速克隆 KVM 虚拟机
    
    # 本脚本针对 RHEL7.2 或 Centos7.2
    # 本脚本需要提前准备一个 qcow2 格式的虚拟机模板,
    # 名称为/var/lib/libvirt/images  /.rh7_template 的虚拟机模板
    # 该脚本使用 qemu‐img 命令快速创建快照虚拟机
    # 脚本使用 sed 修改模板虚拟机的配置文件,将虚拟机名称、UUID、磁盘文件名、MAC 地址
    # exit code:  
    #    65 ‐> user input nothing
    #    66 ‐> user input is not a number
    #    67 ‐> user input out of range
    #    68 ‐> vm disk image exists
     
    IMG_DIR=/var/lib/libvirt/images
    BASEVM=rh7_template
    read -p "Enter VM number: " VMNUM
    if [ $VMNUM -le 9 ];then
    VMNUM=0$VMNUM
    fi
     
    if [ -z "${VMNUM}" ]; then
        echo "You must input a number."
        exit 65
    elif [[  ${VMNUM} =~ [a‐z]  ]; then
        echo "You must input a number."
        exit 66
    elif [ ${VMNUM} -lt 1 -o ${VMNUM} -gt 99 ]; then
        echo "Input out of range"
        exit 67
    fi
     
    NEWVM=rh7_node${VMNUM}
     
    if [ -e $IMG_DIR/${NEWVM}.img ]; then
        echo "File exists."
        exit 68
    fi
     
    echo -en "Creating Virtual Machine disk image......\t"
    qemu‐img create -f qcow2 ‐b $IMG_DIR/.${BASEVM}.img $IMG_DIR/${NEWVM}.img &> /dev/null
    
    echo -e "\e[32;1m[OK]\e[0m"
     
    #virsh dumpxml ${BASEVM} > /tmp/myvm.xml
    cat /var/lib/libvirt/images/.rhel7.xml > /tmp/myvm.xml
    sed -i "/<name>${BASEVM}/s/${BASEVM}/${NEWVM}/" /tmp/myvm.xml
    sed -i "/uuid/s/<uuid>.*<\/uuid>/<uuid>$(uuidgen)<\/uuid>/" /tmp/myvm.xml
    sed -i "/${BASEVM}\.img/s/${BASEVM}/${NEWVM}/" /tmp/myvm.xml
     
    # 修改 MAC 地址,本例使用的是常量,每位使用该脚本的用户需要根据实际情况修改这些值 
    # 最好这里可以使用便利,这样更适合于批量操作,可以克隆更多虚拟机 
    sed -i "/mac /s/a1/0c/" /tmp/myvm.xml
     
    echo -en "Defining new virtual machine......\t\t"
    virsh define /tmp/myvm.xml &> /dev/null
    echo -e "\e[32;1m[OK]\e[0m"

    24、点名器脚本

    #!/bin/bash
    
    # 编写一个点名器脚本
    
    # 该脚本,需要提前准备一个 user.txt 文件
    # 该文件中需要包含所有姓名的信息,一行一个姓名,脚本每次随机显示一个姓名
    while :
    do
    #统计 user 文件中有多少用户
    line=`cat user.txt |wc ‐l`
    num=$[RANDOM%line+1]
    sed -n "${num}p"  user.txt
    sleep 0.2
    clear
    done

    25、查看有多少远程的 IP 在连接本机

    #!/bin/bash
    
    # 查看有多少远程的 IP 在连接本机(不管是通过 ssh 还是 web 还是 ftp 都统计) 
    
    # 使用 netstat ‐atn 可以查看本机所有连接的状态,‐a 查看所有,
    # -t仅显示 tcp 连接的信息,‐n 数字格式显示
    # Local Address(第四列是本机的 IP 和端口信息)
    # Foreign Address(第五列是远程主机的 IP 和端口信息)
    # 使用 awk 命令仅显示第 5 列数据,再显示第 1 列 IP 地址的信息
    # sort 可以按数字大小排序,最后使用 uniq 将多余重复的删除,并统计重复的次数
    netstat -atn  |  awk  '{print $5}'  | awk  '{print $1}' | sort -nr  |  uniq -c

    26、对 100 以内的所有正整数相加求和(1+2+3+4...+100)

    #!/bin/bash
    
    # 对 100 以内的所有正整数相加求和(1+2+3+4...+100)
     
    #seq 100 可以快速自动生成 100 个整数
    sum=0
    for i in `seq 100`
    do
      	sum=$[sum+i]
    done
    echo "总和是:$sum"

    27、统计 13:30 到 14:30 所有访问 apache 服务器的请求有多少个

    #!/bin/bash
    
    # 统计 13:30 到 14:30 所有访问 apache 服务器的请求有多少个
    
    # awk 使用‐F 选项指定文件内容的分隔符是/或者:
    # 条件判断$7:$8 大于等于 13:30,并且要求,$7:$8 小于等于 14:30
    # 最后使用 wc ‐l 统计这样的数据有多少行,即多少个
    awk -F "[ /:]" '$7":"$8>="13:30" && $7":"$8<="14:30"' /var/log/httpd/access_log |wc -l

    28、统计 13:30 到 14:30 所有访问本机 Aapche 服务器的远程 IP 地址是什么 

    #!/bin/bash
    
    # 统计 13:30 到 14:30 所有访问本机 Aapche 服务器的远程 IP 地址是什么 
    # awk 使用‐F 选项指定文件内容的分隔符是/或者:
    # 条件判断$7:$8 大于等于 13:30,并且要求,$7:$8 小于等于 14:30
    # 日志文档内容里面,第 1 列是远程主机的 IP 地址,使用 awk 单独显示第 1 列即可
    awk -F "[ /:]" '$7":"$8>="13:30" && $7":"$8<="14:30"{print $1}' /var/log/httpd/access_log

    29、打印国际象棋棋盘

    #!/bin/bash
    
    # 打印国际象棋棋盘
    # 设置两个变量,i 和 j,一个代表行,一个代表列,国际象棋为 8*8 棋盘
    # i=1 是代表准备打印第一行棋盘,第 1 行棋盘有灰色和蓝色间隔输出,总共为 8 列
    # i=1,j=1 代表第 1 行的第 1 列;i=2,j=3 代表第 2 行的第 3 列
    # 棋盘的规律是 i+j 如果是偶数,就打印蓝色色块,如果是奇数就打印灰色色块
    # 使用 echo ‐ne 打印色块,并且打印完成色块后不自动换行,在同一行继续输出其他色块
    for i in {1..8}
    do
      	for j in {1..8}
      	do
      		sum=$[i+j]
    		if [  $[sum%2] -eq 0 ];then
     			echo -ne "\033[46m  \033[0m"
    		else
    			echo -ne "\033[47m  \033[0m"
    		fi
      	done
      	echo
    done

    30、统计每个远程 IP 访问了本机 apache 几次?

    #!/bin/bash
    
    # 统计每个远程 IP 访问了本机 apache 几次? 
    awk  '{ip[$1]++}END{for(i in ip){print ip[i],i}}'  /var/log/httpd/access_log

    31、统计当前 Linux 系统中可以登录计算机的账户有多少个

    #!/bin/bash
    
    # 统计当前 Linux 系统中可以登录计算机的账户有多少个
    #方法 1:
    grep "bash$" /etc/passwd | wc -l
    #方法 2:
    awk -f: '/bash$/{x++}end{print x}'  /etc/passwd

    32、统计/var/log 有多少个文件,并显示这些文件名

    #!/bin/bash
    
    # 统计/var/log 有多少个文件,并显示这些文件名 
    # 使用 ls 递归显示所有,再判断是否为文件,如果是文件则计数器加 1
    cd  /var/log
    sum=0
    for i in `ls -r *`
    do
     	if [ -f $i ];then
         	let sum++
           	echo "文件名:$i"
       	fi
    done
    echo "总文件数量为:$sum"

    33、自动为其他脚本添加解释器信息

    #!/bin/bash
    
    # 自动为其他脚本添加解释器信息#!/bin/bash,如脚本名为 test.sh 则效果如下: 
    # ./test.sh  abc.sh	自动为 abc.sh 添加解释器信息
    # ./test.sh  user.sh	自动为 user.sh 添加解释器信息
    
    # 先使用 grep 判断对象脚本是否已经有解释器信息,如果没有则使用 sed 添加解释器以及描述信息
    if  !  grep  -q  "^#!"  $1; then
    sed  '1i #!/bin/bash'  $1
    sed  '2i #Description: '
    fi
    # 因为每个脚本的功能不同,作用不同,所以在给对象脚本添加完解释器信息,以及 Description 后还希望
    # 继续编辑具体的脚本功能的描述信息,这里直接使用 vim 把对象脚本打开,并且光标跳转到该文件的第 2 行
    vim +2 $1

    34、自动化部署 varnish 源码包软件

    #!/bin/bash
    
    # 自动化部署 varnish 源码包软件 
    # 本脚本需要提前下载 varnish‐3.0.6.tar.gz 这样一个源码包软件,该脚本即可用自动源码安装部署软件
     
    yum -y install gcc readline‐devel pcre‐devel
    useradd -s /sbin/nologin varnish
    tar -xf varnish‐3.0.6.tar.gz
    cd varnish‐3.0.6
     
    # 使用 configure,make,make install 源码安装软件包
    ./configure ‐‐prefix=/usr/local/varnish
    make && make install
     
    # 在源码包目录下,将相应的配置文件拷贝到 Linux 系统文件系统中
    # 默认安装完成后,不会自动拷贝或安装配置文件到 Linux 系统,所以需要手动 cp 复制配置文件
    # 并使用 uuidgen 生成一个随机密钥的配置文件
     
    cp redhat/varnish.initrc /etc/init.d/varnish
    cp redhat/varnish.sysconfig /etc/sysconfig/varnish
    cp redhat/varnish_reload_vcl /usr/bin/
    ln -s /usr/local/varnish/sbin/varnishd /usr/sbin/
    ln -s /usr/local/varnish/bin/* /usr/bin
    mkdir /etc/varnish
    cp /usr/local/varnish/etc/varnish/default.vcl /etc/varnish/
    uuidgen > /etc/varnish/secret

    35、编写 nginx 启动脚本

    #!/bin/bash
    
    # 编写 nginx 启动脚本 
    # 本脚本编写完成后,放置在/etc/init.d/目录下,就可以被 Linux 系统自动识别到该脚本
    # 如果本脚本名为/etc/init.d/nginx,则 service nginx start 就可以启动该服务
    # service nginx stop 就可以关闭服务
    # service nginx restart 可以重启服务
    # service nginx status 可以查看服务状态
     
    program=/usr/local/nginx/sbin/nginx
    pid=/usr/local/nginx/logs/nginx.pid
    start(){
    if [ -f $pid ];then
      echo  "nginx 服务已经处于开启状态"
    else
      $program
    fi
    stop(){
    if [ -! -f $pid ];then
      echo "nginx 服务已经关闭"
    else
      $program -s stop
      echo "关闭服务 ok"
    fi
    }
    status(){
    if [ -f $pid ];then
      echo "服务正在运行..."
    else
      echo "服务已经关闭"
    fi
    }
     
    case $1 in
    start)
      start;;
    stop)
      stop;;
    restart)
      stop
      sleep 1
      start;;
    status)
      status;;
    *)
      echo  "你输入的语法格式错误"
    esac

    36、自动对磁盘分区、格式化、挂载

     #!/bin/bash
    
    # 自动对磁盘分区、格式化、挂载
    # 对虚拟机的 vdb 磁盘进行分区格式化,使用<<将需要的分区指令导入给程序 fdisk
    # n(新建分区),p(创建主分区),1(分区编号为 1),两个空白行(两个回车,相当于将整个磁盘分一个区)
    # 注意:1 后面的两个回车(空白行)是必须的!
    fdisk /dev/vdb << EOF
    n
    p
    1
     
     
    wq
    EOF
     
    #格式化刚刚创建好的分区
    mkfs.xfs   /dev/vdb1
     
    #创建挂载点目录
    if [ -e /data ]; then
    exit
    fi
    mkdir /data
     
    #自动挂载刚刚创建的分区,并设置开机自动挂载该分区
    echo '/dev/vdb1     /data    xfs    defaults        1 2'  >> /etc/fstab
    mount -a
    

    37、自动优化 Linux 内核参数

    #!/bin/bash
    
    # 自动优化 Linux 内核参数
    
    #脚本针对 RHEL7
    cat >> /usr/lib/sysctl.d/00‐system.conf <<EOF
    fs.file‐max=65535
    net.ipv4.tcp_timestamps = 0
    net.ipv4.tcp_synack_retries = 5
    net.ipv4.tcp_syn_retries = 5
    net.ipv4.tcp_tw_recycle = 1
    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_fin_timeout = 30
    #net.ipv4.tcp_keepalive_time = 120
    net.ipv4.ip_local_port_range = 1024  65535
    kernel.shmall = 2097152
    kernel.shmmax = 2147483648
    kernel.shmmni = 4096
    kernel.sem = 5010 641280 5010 128
    net.core.wmem_default=262144
    net.core.wmem_max=262144
    net.core.rmem_default=4194304
    net.core.rmem_max=4194304
    net.ipv4.tcp_fin_timeout = 10
    net.ipv4.tcp_keepalive_time = 30
    net.ipv4.tcp_window_scaling = 0
    net.ipv4.tcp_sack = 0
    EOF
     
    sysctl –p
    

    38、切割 Nginx 日志文件(防止单个文件过大,后期处理很困难)

    #mkdir  /data/scripts
    #vim   /data/scripts/nginx_log.sh  
    #!/bin/bash
    
    # 切割 Nginx 日志文件(防止单个文件过大,后期处理很困难) 
    logs_path="/usr/local/nginx/logs/"
    mv ${logs_path}access.log ${logs_path}access_$(date -d "yesterday" +"%Y%m%d").log
    kill -USR1  `cat /usr/local/nginx/logs/nginx.pid`
     
    # chmod +x  /data/scripts/nginx_log.sh
    # crontab  ‐e                    #脚本写完后,将脚本放入计划任务每天执行一次脚本
    0  1  *  *   *   /data/scripts/nginx_log.sh

    39、检测 MySQL 数据库连接数量

    #!/bin/bash
    
    # 检测 MySQL 数据库连接数量 
    
    # 本脚本每 2 秒检测一次 MySQL 并发连接数,可以将本脚本设置为开机启动脚本,或在特定时间段执行
    # 以满足对 MySQL 数据库的监控需求,查看 MySQL 连接是否正常
    # 本案例中的用户名和密码需要根据实际情况修改后方可使用
    log_file=/var/log/mysql_count.log
    user=root
    passwd=123456
    while :
    do
        sleep 2
        count=`mysqladmin  -u  "$user"  -p  "$passwd"   status |  awk '{print $4}'`
        echo "`date +%Y‐%m‐%d` 并发连接数为:$count" >> $log_file
    done

    40、根据 md5 校验码,检测文件是否被修改

    #!/bin/bash
    
    # 根据 md5 校验码,检测文件是否被修改 
    # 本示例脚本检测的是/etc 目录下所有的 conf 结尾的文件,根据实际情况,您可以修改为其他目录或文件
    # 本脚本在目标数据没有被修改时执行一次,当怀疑数据被人篡改,再执行一次
    # 将两次执行的结果做对比,MD5 码发生改变的文件,就是被人篡改的文件
    for i in $(ls /etc/*.conf)
    do
    	md5sum "$i" >> /var/log/conf_file.log
    done

    41、检测 MySQL 服务是否存活

    #!/bin/bash
    
    # 检测 MySQL 服务是否存活 
    
    # host 为你需要检测的 MySQL 主机的 IP 地址,user 为 MySQL 账户名,passwd 为密码
    # 这些信息需要根据实际情况修改后方可使用
    host=192.168.51.198
    user=root
    passwd=123456
    mysqladmin -h '$host' -u '$user' -p'$passwd' ping &>/dev/null
    if [ $? -eq 0 ]
    then
            echo "MySQL is UP"
    else
            echo "MySQL is down"
    fi
    

    42、备份 MySQL 的 shell 脚本(mysqldump版本)

    #!/bin/bash
    
    # 备份 MySQL 的 shell 脚本(mysqldump版本) 
    
    # 定义变量 user(数据库用户名),passwd(数据库密码),date(备份的时间标签)
    # dbname(需要备份的数据库名称,根据实际需求需要修改该变量的值,默认备份 mysql 数据库)
     
    user=root
    passwd=123456
    dbname=mysql
    date=$(date +%Y%m%d)
     
    # 测试备份目录是否存在,不存在则自动创建该目录
    [ ! -d /mysqlbackup ] && mkdir /mysqlbackup
    # 使用 mysqldump 命令备份数据库
    mysqldump -u "$user" -p "$passwd" "$dbname" > /mysqlbackup/"$dbname"-${date}.sql

    43、将文件中所有的小写字母转换为大写字母

    #!/bin/bash
    
    # 将文件中所有的小写字母转换为大写字母 
    
    # $1是位置参数,是你需要转换大小写字母的文件名称
    # 执行脚本,给定一个文件名作为参数,脚本就会将该文件中所有的小写字母转换为大写字母
    tr "[a‐z]" "[A‐Z]" < $1

    44、非交互自动生成 SSH 密钥文件

    #!/bin/bash
    
    # 非交互自动生成 SSH 密钥文件 
    
    # ‐t 指定 SSH 密钥的算法为 RSA 算法;‐N 设置密钥的密码为空;‐f 指定生成的密钥文件>存放在哪里
    rm  -rf  ~/.ssh/{known_hosts,id_rsa*}
    ssh‐keygen -t RSA -N '' -f ~/.ssh/id_rsa
    

    45、检查特定的软件包是否已经安装

    #!/bin/bash
    
    # 检查特定的软件包是否已经安装 
    if [ $# -eq 0 ];then
      echo "你需要制定一个软件包名称作为脚本参数"
      echo "用法:$0 软件包名称 ..."
    fi
    # $@提取所有的位置变量的值,相当于$*
    for package in "$@"
    do
      	if rpm -q ${package} &>/dev/null ;then
    		echo -e "${package}\033[32m 已经安装\033[0m"
      	else
    		echo -e "${package}\033[34;1m 未安装\033[0m"
      	fi
    done

    46、监控 HTTP 服务器的状态(测试返回码)

    #!/bin/bash
    
    # 监控 HTTP 服务器的状态(测试返回码)
    
    # 设置变量,url为你需要检测的目标网站的网址(IP 或域名),比如百度
    url=http://http://183.232.231.172/index.html
    
    # 定义函数 check_http:
    # 使用 curl 命令检查 http 服务器的状态
    # ‐m 设置curl不管访问成功或失败,最大消耗的时间为 5 秒,5 秒连接服务为相应则视为无法连接
    # ‐s 设置静默连接,不显示连接时的连接速度、时间消耗等信息
    # ‐o 将 curl 下载的页面内容导出到/dev/null(默认会在屏幕显示页面内容)
    # ‐w 设置curl命令需要显示的内容%{http_code},指定curl返回服务器的状态码
    check_http()
    {
            status_code=$(curl -m 5 -s -o /dev/null -w %{http_code} $url)
    }
    
    while :
    do
            check_http
            date=$(date +%Y%m%d‐%H:%M:%S)
    
    # 生成报警邮件的内容
            echo "当前时间为:$date
            $url 服务器异常,状态码为${status_code}.
            请尽快排查异常." > /tmp/http$$.pid
    
    # 指定测试服务器状态的函数,并根据返回码决定是发送邮件报警还是将正常信息写入日志
            if [ $status_code -ne 200 ];then
                    mail -s Warning root < /tmp/http$$.pid
            else
                    echo "$url 连接正常" >> /var/log/http.log
            fi
            sleep 5
    done
    
    

    47、自动添加防火墙规则,开启某些服务或端口(适用于 RHEL7)

    #!/bin/bash
    
    # 自动添加防火墙规则,开启某些服务或端口(适用于 RHEL7)
    # 
    # 设置变量定义需要添加到防火墙规则的服务和端口号
    # 使用 firewall‐cmd ‐‐get‐services 可以查看 firewall 支持哪些服务
    service="nfs http ssh"
    port="80 22 8080"
     
    # 循环将每个服务添加到防火墙规则中
    for i in $service
    do
      	echo "Adding $i service to firewall"
      	firewall‐cmd  --add-service=${i}
    done
     
    #循环将每个端口添加到防火墙规则中
    for i in $port
    do
      	echo "Adding $i Port to firewall"
      	firewall‐cmd --add-port=${i}/tcp
    done
    #将以上设置的临时防火墙规则,转换为永久有效的规则(确保重启后有效)
    firewall‐cmd  --runtime-to-permanent

    48、使用脚本自动创建逻辑卷

    #!/bin/bash
    
    # 使用脚本自动创建逻辑卷 
    
    # 清屏,显示警告信息,创建将磁盘转换为逻辑卷会删除数据
    clear
    echo -e "\033[32m           !!!!!!警告(Warning)!!!!!!\033[0m"
    echo
    echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
    echo "脚本会将整个磁盘转换为 PV,并删除磁盘上所有数据!!!"
    echo "This Script will destroy all data on the Disk"
    echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
    echo
    read -p "请问是否继续 y/n?:" sure
    
    # 测试用户输入的是否为 y,如果不是则退出脚本
    [ $sure != y ] && exit
     
    # 提示用户输入相关参数(磁盘、卷组名称等数据),并测试用户是否输入了这些值,如果没有输入,则脚本退出
    read -p "请输入磁盘名称,如/dev/vdb:" disk
    [ -z $disk ] && echo "没有输入磁盘名称" && exit
    read -p "请输入卷组名称:" vg_name
    [ -z $vg_name ] && echo "没有输入卷组名称" && exit
    read -p "请输入逻辑卷名称:" lv_name
    [ -z $lv_name ] && echo "没有输入逻辑卷名称" && exit
    read -p "请输入逻辑卷大小:" lv_size
    [ -z $lv_size ] && echo "没有输入逻辑卷大小" && exit
     
    # 使用命令创建逻辑卷
    pvcreate $disk
    vgcreate $vg_name $disk
    lvcreate -L ${lv_size}M -n ${lv_name}  ${vg_name}

    49、显示 CPU 厂商信息

    #!/bin/bash
    
    # 显示 CPU 厂商信息 
    awk '/vendor_id/{print $3}' /proc/cpuinfo | uniq

    50、删除某个目录下大小为 0 的文件

    #!/bin/bash
    
    # 删除某个目录下大小为 0 的文件
    
    #/var/www/html 为测试目录,脚本会清空该目录下所有 0 字节的文件
    dir="/var/www/html"
    find $dir -type f -size 0 -exec rm -rf {} \;

    51、查找 Linux 系统中的僵尸进程

    #!/bin/bash
    
    # 查找 Linux 系统中的僵尸进程
    
    # awk 判断 ps 命令输出的第 8 列为 Z 是,显示该进程的 PID 和进程命令
    ps aux | awk '{if($8 == "Z"){print $2,$11}}'

    52、提示用户输入年份后判断该年是否为闰年

    #!/bin/bash
    
    # 提示用户输入年份后判断该年是否为闰年
    
    # 能被4整除并且并不能被100整除的年份是闰年
    # 能被400整除的年份也是闰年
    read -p "请输入一个年份:" year
     
    if [ "$year" = "" ];then
        echo "没有输入年份"
        exit
    fi
    #使用正则测试变量 year 中是否包含大小写字母
    if [[ "$year" =~ [a‐Z] ]];then
        echo "你输入的不是数字"
        exit
    fi
    # 判断是否为闰年
    if [ $[year % 4] -eq 0 ] && [ $[year % 100] -ne 0 ];then
        echo "$year年是闰年"  
    elif [ $[year % 400] -eq 0 ];then
        echo "$year年是闰年"
    else
        echo "$year年不是闰年"
    fi

    53、生成随机密码(urandom 版本)

    #!/bin/bash
    
    # 生成随机密码(urandom 版本) 
    
    # /dev/urandom 文件是 Linux 内置的随机设备文件
    # cat /dev/urandom 可以看看里面的内容,ctrl+c 退出查看
    # 查看该文件内容后,发现内容有些太随机,包括很多特殊符号,我们需要的密码不希望使用这些符号
    # tr ‐dc '_A‐Za‐z0‐9' < /dev/urandom
    # 该命令可以将随机文件中其他的字符删除,仅保留大小写字母,数字,下划线,但是内容还是太多
    # 我们可以继续将优化好的内容通过管道传递给 head 命令,在大量数据中仅显示头 10 个字节
    # 注意 A 前面有个下划线
    tr -dc '_A‐Za‐z0‐9' </dev/urandom | head -c 10

    54、生成随机密码(字串截取版本)

    #!/bin/bash
    
    # 生成随机密码(字串截取版本) 
    
    # 设置变量 key,存储密码的所有可能性(密码库),如果还需要其他字符请自行添加其他密码字符
    # 使用$#统计密码库的长度
    key="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    num=${#key}
    # 设置初始密码为空
    pass=''
    # 循环 8 次,生成随机密码
    # 每次都是随机数对密码库的长度取余,确保提取的密码字符不超过密码库的长度
    # 每次循环提取一位随机密码,并将该随机密码追加到 pass 变量的最后
    for i in {1..8}
    do  
      index=$[RANDOM%num]
      pass=$pass${key:$index:1}
    done
    echo $pass

    55、生成随机密码(UUID 版本,16 进制密码)

    #!/bin/bash
    
    # 生成随机密码(UUID 版本,16 进制密码) 
    uuidgen

    56、生成随机密码(进程 ID 版本,数字密码)

    #!/bin/bash
    
    # 生成随机密码(进程 ID 版本,数字密码)
    echo $$

    57、测试用户名与密码是否正确

    #!/bin/bash
    
    # 测试用户名与密码是否正确
    
    #用户名为 tom 并且密码为 123456,则提示登录成功,否则提示登录失败
    read -p "请输入用户名:"  user
    read -p "请输入密码:"    pass
    if [ "$user" == 'tom' -a "$pass" == '123456' ];then
    	echo "Login successful"
    else
    	echo "Login Failed"
    fi

    58、循环测试用户名与密码是否正确

    #!/bin/bash
    
    # 循环测试用户名与密码是否正确 
    
    # 循环测试用户的账户名和密码,最大测试 3 次,输入正确提示登录成功,否则提示登录失败
    # 用户名为 tom 并且密码为 123456  
    for i in {1..3}
    do
    	read -p "请输入用户名:" user
    	read -p "请输入密码:"   pass
    if [ "$user" == 'tom' -a "$pass" == '123456' ];then
      	echo "Login successful"
       	exit
    fi
    done
    echo "Login Failed"

    59、Shell 脚本的 fork 炸弹

    #!/bin/bash
    
    # Shell 脚本的 fork 炸弹 
    
    # 快速消耗计算机资源,致使计算机死机
    # 定义函数名为.(点), 函数中递归调用自己并放入后台执行
    .() { .|.& };.

    60、批量下载有序文件(pdf、图片、视频等等)

    #!/bin/bash
    
    # 批量下载有序文件(pdf、图片、视频等等)
    
    # 本脚本准备有序的网络资料进行批量下载操作(如 01.jpg,02.jpg,03.jpg)
    # 设置资源来源的域名连接
    url="http://www.baidu.com/"
    echo  "开始下载..."
    sleep 2
    type=jpg
    for i in `seq 100`
       	echo "正在下载$i.$type"
    	curl $url/$i.$type -o /tmp/${i}$type
       	sleep 1
    done
    #curl 使用-o 选项指定下载文件另存到哪里.

    61、显示当前计算机中所有账户的用户名称

     #!/bin/bash
    
    # 显示当前计算机中所有账户的用户名称
    
    # 下面使用3种不同的方式列出计算机中所有账户的用户名
    # 指定以:为分隔符,打印/etc/passwd 文件的第 1 列
    awk -F: '{print $1}' /etc/passwd
     
    # 指定以:为分隔符,打印/etc/passwd 文件的第 1 列
    cut -d: -f1 /etc/passwd
     
    # 使用 sed 的替换功能,将/etc/passwd 文件中:后面的所有内容替换为空(仅显示用户名)
    sed 's/:.*//' /etc/passwd

    62、制定目录路径,脚本自动将该目录使用 tar 命令打包备份到/data目录

    #!/bin/bash
    
    # 制定目录路径,脚本自动将该目录使用 tar 命令打包备份到/data目录 
    
    [ ! -d /data ] && mkdir /data
    [ -z $1 ] && exit
    if [ -d $1 ];then
    	tar -czf /data/$1.-`date +%Y%m%d`.tar.gz $1
    else
      	echo "该目录不存在"
    fi

    63、显示进度条(回旋镖版)

    #!/bin/bash
    
    # 显示进度条(回旋镖版)
    
    while :
    do
    	clear
    	for i in {1..20}
    	do
    		echo ‐e "\033[3;${i}H*"
    		sleep 0.1
      	done
      	clear
      	for i in {20..1}
      	do
    		echo ‐e "\033[3;${i}H*"
    		sleep 0.1
      	done
      	clear
    done

    64、安装 LAMP 环境(yum 版本)

    #!/bin/bash
    
    # 安装 LAMP 环境(yum 版本) 
    
    # 本脚本适用于 RHEL7(RHEL6 中数据库为 mysql)
    yum makecache &>/dev/null
    num=$(yum repolist | awk '/repolist/{print $2}' | sed 's/,//')
    if [ $num -lt 0 ];then
    	yum -y install httpd
    	yum -y install mariadb mariadb-server mariadb-devel
    	yum -y install php php-mysql
    else
    	echo "未配置 yum 源..."
    fi

    65、循环关闭局域网中所有主机

    #!/bin/bash
    
    # 循环关闭局域网中所有主机 
    
    # 假设本机为 192.168.4.100,编写脚本关闭除自己外的其他所有主机
    # 脚本执行,需要提前给所有其他主机传递 ssh 密钥,满足无密码连接
    for i in {1..254}
    do
    	[ $i -eq 100 ] && continue
    	echo "正在关闭 192.168.4.$i..."
    	ssh 192.168.4.$i poweroff
    done

    66、获取本机 MAC 地址

    #!/bin/bash
    
    # 获取本机 MAC 地址
    ip a s | awk 'BEGIN{print  " 本 机 MAC 地 址 信 息 如 下 :"}/^[0‐9]/{print $2;getline;if($0~/link\/ether/){print $2}}' | grep -v lo:
     
    # awk 读取 ip 命令的输出,输出结果中如果有以数字开始的行,先显示该行的地 2 列(网卡名称),
    # 接着使用 getline 再读取它的下一行数据,判断是否包含 link/ether
    # 如果保护该关键词,就显示该行的第 2 列(MAC 地址)
    # lo 回环设备没有 MAC,因此将其屏蔽,不显示

    67、自动配置 rsynd 服务器的配置文件 rsyncd.conf

    #!/bin/bash
    
    # 自动配置 rsynd 服务器的配置文件 rsyncd.conf
    
    # See rsyncd.conf man page for more options.
     
    [ ! -d /home/ftp ] && mkdir /home/ftp
    echo 'uid = nobody
    gid = nobody
    use chroot = yes
    max connections = 4
    pid file = /var/run/rsyncd.pid
    exclude = lost+found/
    transfer logging = yes
    timeout = 900
    ignore nonreadable = yes
    dont compress   = *.gz *.tgz *.zip *.z *.Z *.rpm *.deb *.bz2
    [ftp]
        path = /home/ftp
        comment = share' > /etc/rsyncd.conf

    68、修改 Linux 系统的最大打开文件数量

    #!/bin/bash
    
    # 修改 Linux 系统的最大打开文件数量 
    
    # 往/etc/security/limits.conf 文件的末尾追加两行配置参数,修改最大打开文件数量为 65536
    cat >> /etc/security/limits.conf <<EOF
    * soft nofile  65536
    * hard nofile  65536
    EOF

    69、设置 Python 支持自动命令补齐功能

    #!/bin/bash
    
    # 设置 Python 支持自动命令补齐功能 
    
    # Summary:Enable tab complete for python
    # Description:
    
    Needs import readline and rlcompleter module
    #
    import readline
    #
    import rlcompleter
    #
    help(rlcompleter) display detail: readline.parse_and_bind('tab: complete')
    #
    man python display detail: PYTHONSTARTUP variable
     
    if  [ ! -f /usr/bin/tab.py ];then
    	cat >> /usr/bin/tab.py <<EOF
    import readline
    import rlcompleter
    readline.parse_and_bind('tab: complete')
    EOF
    fi
    sed  -i '$a export PYTHONSTARTUP=/usr/bin/tab.py' /etc/profile
    source /etc/profile

    70、自动修改计划任务配置文件

    #!/bin/bash
    
    # 自动修改计划任务配置文件 
    
    read -p "请输入分钟信息(00‐59):" min
    read -p "请输入小时信息(00‐24):" hour
    read -p "请输入日期信息(01‐31):" date
    read -p "请输入月份信息(01‐12):" month
    read -p "请输入星期信息(00‐06):" weak
    read -p "请输入计划任务需要执行的命令或脚本:" program
    echo "$min $hour $date $month $weak $program" >> /etc/crontab

    71、使用脚本循环创建三位数字的文本文件(111-999 的文件)

    #!/bin/bash
    
    # 使用脚本循环创建三位数字的文本文件(111-999 的文件) 
    
    for i in {1..9}
    do
    	for j in {1..9}
    	do
    		for k in {1..9}
    		do
    			touch /tmp/$i$j$k.txt
    		done
      	done
    done

    72、找出/etc/passwd 中能登录的用户,并将对应在/etc/shadow 中第二列密码提出处理

    #!/bin/bash
    
    # 找出/etc/passwd 中能登录的用户,并将对应在/etc/shadow 中第二列密码提出处理
    
    user=$(awk -F: '/bash$/{print $1}' /etc/passwd)
    for i in $user
    do
    	awk -F: -v x=$i '$1==x{print $1,$2}' /etc/shadow
    done

    73、统计/etc/passwd 中 root 出现的次数

    #!/bin/bash
    
    # 统计/etc/passwd 中 root 出现的次数 
    
    #每读取一行文件内容,即从第 1 列循环到最后 1 列,依次判断是否包含 root 关键词,如果包含则 x++
    awk -F: '{i=1;while(i<=NF){if($i~/root/){x++};i++}} END{print "root 出现次数为"x}' /etc/passwd

    74、统计 Linux 进程相关数量信息

    #!/bin/bash
    
    # 统计 Linux 进程相关数量信息 
    
    running=0
    sleeping=0
    stoped=0
    zombie=0
    # 在 proc 目录下所有以数字开始的都是当前计算机正在运行的进程的进程 PID
    # 每个 PID 编号的目录下记录有该进程相关的信息
    for pid in /proc/[1‐9]*
    do
    	procs=$[procs+1]
    	stat=$(awk '{print $3}' $pid/stat)
    # 每个 pid 目录下都有一个 stat 文件,该文件的第 3 列是该进程的状态信息
      	case $stat in
      	R)
    		running=$[running+1]
    		;;
      	T)
    		stoped=$[stoped+1]
    		;;
      	S)
    		sleeping=$[sleeping+1]
    		;;
      	Z)
     		zombie=$[zombie+1]
     		;;
      	esac
    done
    echo "进程统计信息如下"
    echo "总进程数量为:$procs"
    echo "Running 进程数为:$running"
    echo "Stoped 进程数为:$stoped"
    echo "Sleeping 进程数为:$sleeping"
    echo "Zombie 进程数为:$zombie"

    75、从键盘读取一个论坛积分,判断论坛用户等级

    #!/bin/bash
    
    # 从键盘读取一个论坛积分,判断论坛用户等级
    
    #等级分类如下:
    #  大于等于 90				神功绝世
    #  大于等于 80,小于 90       登峰造极
    #  大于等于 70,小于 80       炉火纯青
    #  大于等于 60,小于 70       略有小成
    #  小于 60         			初学乍练
    read -p "请输入积分(0‐100):" JF
    if [ $JF -ge 90 ] ; then
    	echo "$JF 分,神功绝世"
    elif [ $JF -ge 80 ] ; then
        echo "$JF 分,登峰造极"
    elif [ $JF -ge 70 ] ; then
        echo "$JF 分,炉火纯青"
    elif [ $JF -lt 60 ] ; then
        echo "$JF 分,略有小成"
    else
        echo "$JF 分,初学乍练"
    fi

    76、判断用户输入的数据类型(字母、数字或其他) 

    #!/bin/bash
    
    # 判断用户输入的数据类型(字母、数字或其他) 
    read -p "请输入一个字符:" KEY
    case "$KEY" in
    	[a‐z]|[A‐Z])
    		echo "字母" 
    		;;
    	[0‐9])
    		echo "数字" 
    		;;
    	*)
    		echo "空格、功能键或其他控制字符"
    esac

    77、显示进度条(数字版) 

    #!/bin/bash
    
    # 显示进度条(数字版) 
    # echo 使用‐e 选项后,在打印参数中可以指定 H,设置需要打印内容的 x,y 轴的定位坐标
    # 设置需要打印内容在第几行,第几列
    for i in {1..100}
    do
      	echo -e "\033[6;8H["
      	echo -e "\033[6;9H$i%"
      	echo -e "\033[6;13H]"
      	sleep 0.1
    done

    78、打印斐波那契数列

    #!/bin/bash
    
    # 打印斐波那契数列(该数列的特点是后一个数字,永远都是前 2 个数字之和) 
    
    # 斐波那契数列后一个数字永远是前 2 个数字之和
    # 如:0  1  1  2  3  5  8  13 ... ...
    list=(0 1)
    for i in `seq 2 11`
    do
    	list[$i]=`expr ${list[‐1]} + ${list[‐2]}`
    done
    echo ${list[@]}

    79、判断用户输入的是 Yes 或 NO

    #!/bin/bash
    
    # 判断用户输入的是 Yes 或 NO 
    
    read -p  "Are you sure?[y/n]:"  sure
    case  $sure  in
    	y|Y|Yes|YES)  
    		echo "you enter $a"
    		;;
        n|N|NO|no)
     		echo "you enter $a"
     		;;
        *)
     		echo "error";;
    esac

    80、显示本机 Linux 系统上所有开放的端口列表

    #!/bin/bash
    
    # 显示本机 Linux 系统上所有开放的端口列表 
    
    # 从端口列表中观测有没有没用的端口,有的话可以将该端口对应的服务关闭,防止意外的攻击可能性
    ss -nutlp | awk '{print $1,$5}' | awk -F"[: ]" '{print "协议:"$1,"端口号:"$NF}' | grep "[0‐9]" | uniq

    81、将 Linux 系统中 UID 大于等于 1000 的普通用户都删除

    #!/bin/bash
    
    # 将 Linux 系统中 UID 大于等于 1000 的普通用户都删除 
    
    # 先用 awk 提取所有 uid 大于等于 1000 的普通用户名称
    # 再使用 for 循环逐个将每个用户删除即可
    user=$(awk -F: '$3>=1000{print $1}' /etc/passwd)
    for i in $user
    do
       	userdel -r $i
    done

    82、使用脚本开启关闭虚拟机

    #!/bin/bash
    
    # 使用脚本开启关闭虚拟机 
    
    # 脚本通过调用virsh命令实现对虚拟机的管理,如果没有该命令,需要安装 libvirt‐client 软件包
    # $1是脚本的第1个参数,$2是脚本的第2个参数
    # 第1个参数是你希望对虚拟机进行的操作指令,第2个参数是虚拟机名称
    case $1 in
      list)
        virsh list --all
        ;;
      start)
        virsh start $2
        ;;
      stop)
        virsh destroy $2
        ;;
      enable)
        virsh autostart $2
        ;;
      disable)
        virsh autostart --disable $2
        ;;
      *)
        echo "Usage:$0 list"
        echo "Usage:$0 [start|stop|enable|disable]  VM_name"
        cat << EOF
        #list      显示虚拟机列表
        #start     启动虚拟机
        #stop      关闭虚拟机
        #enable    设置虚拟机为开机自启
        #disable   关闭虚拟机开机自启功能
        EOF
        ;;
    esac

    83、调整虚拟机内存参数的 shell 脚本 

    #!/bin/bash
    
    # 调整虚拟机内存参数的 shell 脚本 
    
    # 脚本通过调用 virsh 命令实现对虚拟机的管理,如果没有该命令,需要安装 libvirt‐client 软件包
    cat << EOF
    1.调整虚拟机最大内存数值
    2.调整实际分配给虚拟机的内存数值
    EOF
    read -p "请选择[1‐2]:" select
    case $select in
    	1)
      		read -p "请输入虚拟机名称" name
      		read -p "请输入最大内存数值(单位:k):" size
      		virsh setmaxmem $name --size $size --config
      		;;
    	2)
      		read -p "请输入虚拟机名称" name
      		read -p "请输入实际分配内存数值(单位:k):" size
      		virsh setmem $name $size
      		;;
    	*)
      		echo "Error"
      		;;
    esac

    84、查看 KVM 虚拟机中的网卡信息(不需要进入启动或进入虚拟机) 

    #!/bin/bash
    
    # 查看 KVM 虚拟机中的网卡信息(不需要进入启动或进入虚拟机) 
    
    # 该脚本使用 guestmount 工具,可以将虚拟机的磁盘系统挂载到真实机文件系统中
    # Centos7.2 中安装 libguestfs‐tools‐c 可以获得 guestmount 工具
    # 虚拟机可以启动或者不启动都不影响该脚本的使用
    # 将虚拟机磁盘文件挂载到文件系统后,就可以直接读取磁盘文件中的网卡配置文件中的数据
    clear
    mountpoint="/media/virtimage"
    [ ! -d $mountpoint ] && mkdir $mountpoint
    read -p "输入虚拟机名称:" name
    echo "请稍后..."
    # 如果有设备挂载到该挂载点,则先 umount 卸载
    if mount | grep -q "$mountpoint" ;then
    	umount $mountpoint
    fi
    # 只读的方式,将虚拟机的磁盘文件挂载到特定的目录下,这里是/media/virtimage 目录
    guestmount -r -d $name -i $mountpoint
    echo
    echo "‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐"
    echo -e "\033[32m$name 虚拟机中网卡列表如下:\033[0m"
    dev=$(ls /media/virtimage/etc/sysconfig/network‐scripts/ifcfg-* |awk -F"[/‐]" '{print $9}')
    echo $dev
    echo "‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐"
    echo
    echo
    echo "+++++++++++++++++++++++++++++++++++++++++++"
    echo -e "\033[32m 网卡 IP 地址信息如下:\033[0m"
    for i in $dev
    do
      echo -n "$i:"
      grep -q "IPADDR" /media/virtimage/etc/sysconfig/network‐scripts/ifcfg-$i || echo "未配置 IP地址"
      awk -F= '/IPADDR/{print $2}' /media/virtimage/etc/sysconfig/network-scripts/ifcfg-$i
    done
    echo "+++++++++++++++++++++++++++++++++++++++++++"

    85、不登陆虚拟机,修改虚拟机网卡 IP 地址

    #!/bin/bash
    
    # 不登陆虚拟机,修改虚拟机网卡 IP 地址 
    
    # 该脚本使用 guestmount 工具,Centos7.2 中安装 libguestfs‐tools‐c 可以获得 guestmount 工具
    # 脚本在不登陆虚拟机的情况下,修改虚拟机的 IP 地址信息
    # 在某些环境下,虚拟机没有 IP 或 IP 地址与真实主机不在一个网段
    # 真实主机在没有 virt‐manger 图形的情况下,远程连接虚拟机很麻烦
    # 该脚本可以解决类似的问题
    read -p "请输入虚拟机名称:" name
    if virsh domstate $name | grep -q running ;then
    	echo "修改虚拟机网卡数据,需要关闭虚拟机"
    	virsh destroy $name
    fi
    mountpoint="/media/virtimage"
    [ ! -d $mountpoint ] && mkdir $mountpoint
    echo "请稍后..."
    if mount | grep -q "$mountpoint" ;then
    	umount $mountpoint
    fi
    guestmount  -d $name -i $mountpoint
    read -p "请输入需要修改的网卡名称:" dev
    read -p "请输入 IP 地址:" addr
    # 判断原本网卡配置文件中是否有 IP 地址,有就修改该 IP,没有就添加一个新的 IP 地址
    if grep -q "IPADDR"  $mountpoint/etc/sysconfig/network‐scripts/ifcfg‐$dev ;then
    	sed -i "/IPADDR/s/=.*/=$addr/"  $mountpoint/etc/sysconfig/network‐scripts/ifcfg‐$dev
    else
    	echo "IPADDR=$addr" >> $mountpoint/etc/sysconfig/network‐scripts/ifcfg‐$dev
    fi
    # 如果网卡配置文件中有客户配置的 IP 地址,则脚本提示修改 IP 完成
    awk -F= -v x=$addr '$2==x{print "完成..."}'  $mountpoint/etc/sysconfig/network‐scripts/ifcfg-$dev

    86、破解虚拟机密码,无密码登陆虚拟机系统

    #!/bin/bash
    
    # 破解虚拟机密码,无密码登陆虚拟机系统 
    
    # 该脚本使用 guestmount 工具,Centos7.2 中安装 libguestfs‐tools‐c 可以获得 guestmount 工具
    
    read -p "请输入虚拟机名称:" name
    if virsh domstate $name | grep -q running ;then
    	echo "破解,需要关闭虚拟机"
    	virsh destroy $name
    fi
    mountpoint="/media/virtimage"
    [ ! -d $mountpoint ] && mkdir $mountpoint
    echo "请稍后..."
    if mount | grep -q "$mountpoint" ;then
    	umount $mountpoint
    fi
    guestmount -d $name -i $mountpoint
    # 将 passwd 中密码占位符号 x 删除,该账户即可实现无密码登陆系统
    sed -i "/^root/s/x//" $mountpoint/etc/passwd

    87、Shell 脚本对信号的处理,执行脚本后,按键盘 Ctrl+C 无法终止的脚本

    #!/bin/bash
    
    # Shell 脚本对信号的处理,执行脚本后,按键盘 Ctrl+C 无法终止的脚本 
    
    # 使用 trap 命令可以拦截用户通过键盘或 kill 命令发送过来的信号
    # 使用 kill ‐l 可以查看 Linux 系统中所有的信号列表,其中 2 代表 Ctrl+C
    # trap 当发现有用户 ctrl+C 希望终端脚本时,就执行 echo "暂停 10s";sleep 10 这两条命令
    # 另外用户使用命令:[ kill ‐2 脚本的 PID ] 也可以中断脚本和 Ctrl+C 一样的效果,都会被 trap 拦截
    trap 'echo "暂停 10s";sleep 10' 2
    while :
    do
    	echo "go go go"
    done

    88、一键部署 memcached

    #!/bin/bash
    
    # 一键部署 memcached 
    
    # 脚本用源码来安装 memcached 服务器
    # 注意:如果软件的下载链接过期了,请更新 memcached 的下载链接
    wget http://www.memcached.org/files/memcached-1.5.1.tar.gz
    yum -y install gcc
    tar -xf  memcached‐1.5.1.tar.gz
    cd memcached‐1.5.1
    ./configure
    make
    make install

    89、一键配置 VNC 远程桌面服务器(无密码版本)

    #!/bin/bash
    
    # 一键配置 VNC 远程桌面服务器(无密码版本)
    
    # 脚本配置的 VNC 服务器,客户端无需密码即可连接
    # 客户端仅有查看远程桌面的权限,没有鼠标和键盘的操作权限
    
    rpm --quiet -q tigervnc‐server
    if [  $? -ne  0 ];then
    	yum  -y  tigervnc‐server
    fi
    x0vncserver AcceptKeyEvents=0 AlwaysShared=1 \
    AcceptPointerEvents=0 SecurityTypes=None  rfbport=5908

    90、关闭 SELinux

    #!/bin/bash
    
    # 关闭 SELinux 
    
    sed -i  '/^SELINUX/s/=.*/=disabled/' /etc/selinux/config
    setenforce 0

    91、查看所有虚拟机磁盘使用量以及CPU使用量信息

    #!/bin/bash
    
    # 查看所有虚拟机磁盘使用量以及CPU使用量信息 
    
    virt‐df
    read -n1 "按任意键继续" key
    virt‐top

    92、使用 shell 脚本打印图形

    #!/bin/bash
    
    # 使用 shell 脚本打印如下图形: 
    
    # 打印第一组图片
    # for(())为类 C 语言的语法格式,也可以使用 for i  in;do  ;done 的格式替换
    # for((i=1;i<=9;i++))循环会执行 9 次,i 从 1 开始到 9,每循环一次 i 自加 1
    clear
    for (( i=1; i<=9; i++ ))
    do
      for (( j=1; j<=i; j++ ))
      do
        echo -n "$i"
      done
      echo ""
    done
    read  -n1  "按任意键继续"  key
    #打印第二组图片
    clear
    for (( i=1; i<=5; i++ )) 
    do
      for (( j=1; j<=i; j++ ))
      do
        echo -n " |"
      done
      echo "_ "
    done
    read  -n1  "按任意键继续"  key
    #打印第三组图片
    clear
    for (( i=1; i<=5; i++ ))
    do
      for (( j=1; j<=i; j++ ))
      do
        echo -n " *"
      done
      echo ""
    done
    for (( i=5; i>=1; i‐‐ ))
    do
      for (( j=1; j<=i; j++ ))
      do
        echo -n " *"
      done
      echo ""
    done

    93、根据计算机当前时间,返回问候语,可以将该脚本设置为开机启动

    #!/bin/bash
    
    # 根据计算机当前时间,返回问候语,可以将该脚本设置为开机启动 
    
    # 00‐12 点为早晨,12‐18 点为下午,18‐24 点为晚上
    # 使用 date 命令获取时间后,if 判断时间的区间,确定问候语内容
    tm=$(date +%H)
    if [ $tm -le 12 ];then
    	msg="Good Morning $USER"
    elif [ $tm -gt 12 -a $tm -le 18 ];then
      	msg="Good Afternoon $USER"
    else
      	msg="Good Night $USER"
    fi
    echo "当前时间是:$(date +"%Y‐%m‐%d %H:%M:%S")"
    echo -e "\033[34m$msg\033[0m"

    94、读取用户输入的账户名称,将账户名写入到数组保存

    #!/bin/bash
    
    # 读取用户输入的账户名称,将账户名写入到数组保存 
    
    # 定义数组名称为 name,数组的下标为 i,小标从 0 开始,每输入一个账户名,下标加 1,继续存下一个账户
    # 最后,输入 over,脚本输出总结性信息后脚本退出
    i=0
    while :
    do
    	read -p "请输入账户名,输入 over 结束:" key
    	if [ $key == "over" ];then 
    		break
      	else
    		name[$i]=$key
    		let i++
      	fi
    done
    echo "总账户名数量:${#name[*]}"
    echo "${name[@]}"

    95、判断文件或目录是否存在

    #!/bin/bash
    
    # 判断文件或目录是否存在 
    
    if [ $# -eq 0 ] ;then
    echo "未输入任何参数,请输入参数"
    echo "用法:$0 [文件名|目录名]"
    fi
    if [ -f $1 ];then
    	echo "该文件,存在"
    	ls -l $1
    else
    	echo "没有该文件"
    fi
    if [ -d  $1 ];then
       	echo "该目录,存在"
       	ls -ld  $2
    else
       	echo "没有该目录"
    fi

    96、打印各种格式的时间

    #!/bin/bash
    
    # 打印各种时间格式 
    
    echo "显示星期简称(如:Sun)"
    date +%a
    echo "显示星期全称(如:Sunday)"
    date +%A
    echo "显示月份简称(如:Jan)"
    date +%b
    echo "显示月份全称(如:January)"
    date +%B
    echo "显示数字月份(如:12)"
    date +%m
    echo "显示数字日期(如:01 号)"
    date +%d
    echo "显示数字年(如:01 号)"
    date +%Y echo "显示年‐月‐日"
    date +%F
    echo "显示小时(24 小时制)"
    date +%H
    echo "显示分钟(00..59)"
    date +%M
    echo "显示秒"
    date +%S
    echo "显示纳秒"
    date +%N
    echo "组合显示"
    date +"%Y%m%d %H:%M:%S"

    97、使用 egrep 过滤 MAC 地址

    #!/bin/bash
    
    # 使用 egrep 过滤 MAC 地址 
    
    # MAC 地址由 16 进制组成,如 AA:BB:CC:DD:EE:FF
    # [0‐9a‐fA‐F]{2}表示一段十六进制数值,{5}表示连续出现5组前置:的十六进制
    egrep "[0‐9a‐fA‐F]{2}(:[0‐9a‐fA‐F]{2}){5}" $1

    98、统计双色球各个数字的中奖概率

    #!/bin/bash
    
    # 统计双色球各个数字的中奖概率 
    
    # 往期双色球中奖号码如下:
    # 01 04 11 28 31 32  16
    # 04 07 08 18 23 24  02
    # 02 05 06 16 28 29  04
    # 04 19 22 27 30 33  01
    # 05 10 18 19 30 31  03
    # 02 06 11 12 19 29  06
    # 统计篮球和红球数据出现的概率次数(篮球不分顺序,统计所有篮球混合在一起的概率)
    awk '{print $1"\n"$2"\n"$3"\n"$4"\n"$5"\n"$6}' 1.txt | sort | uniq -c | sort
    awk '{print $7}' 1.txt | sort | uniq -c | sort

    99、生成签名私钥和证书

    #!/bin/bash
    
    # 生成签名私钥和证书 
    
    read -p "请输入存放证书的目录:" dir
    if [ ! -d $dir ];then
    	echo "该目录不存在"
    	exit
    fi
    read -p "请输入密钥名称:" name
    # 使用 openssl 生成私钥
    openssl genrsa -out ${dir}/${name}.key
    # 使用 openssl 生成证书 #subj 选项可以在生成证书时,非交互自动填写 Common Name 信息
    openssl req -new -x509 -key ${dir}/${name}.key -subj "/CN=common" -out ${dir}/${name}.crt

    100、使用awk编写的wc程序

    #!/bin/bash
    
    # 使用awk编写的wc程序 
    
    # 自定义变量 chars 变量存储字符个数,自定义变量 words 变量存储单词个数
    # awk 内置变量 NR 存储行数
    # length()为 awk 内置函数,用来统计每行的字符数量,因为每行都会有一个隐藏的$,所以每次统计后都+1
    # wc 程序会把文件结尾符$也统计在内,可以使用 cat ‐A 文件名,查看该隐藏字符
    awk '{chars+=length($0)+1;words+=NF} END{print NR,words,chars}' $1

    注:本篇文章是根据王森的博客整理而来,仅供学习参考!

    展开全文
  • C/C++作为元老级的编程语言,任时光更迭依旧屹立不倒,哪怕如今...一个益智类的VC++小游戏,源码分享给大家,用鼠标点击方格,可看到图像,具体是一款什么游戏还不太清楚,不过源代码是完全可以顺利编译的。 游...

    C/C++作为元老级的编程语言,任时光更迭依旧屹立不倒,哪怕如今炙手可热的AI,其底层也是用其编写。C/C++可以说是永不过时的语言

    那么作为新手该如何上手这门语言?一切不敲代码的学编程手段都是扯淡

    今天小编就推荐一个适合新手练手的C/C++项目

    一个益智类的VC++小游戏,源码分享给大家,用鼠标点击方格,可看到图像,具体是一款什么游戏还不太清楚,不过源代码是完全可以顺利编译的。

    游戏界面展示:

    项目结构展示:

    部分源码展示:

    最后,如果你在学习C/C++的过程中遇到了问题,可以来问小编哦~小编很热情的(#^.^#)

    声明:本文内容来源于网络,如有侵权请联系删除

    展开全文
  • C++入门编程100

    2020-11-10 21:19:14
    C++编程入门100题》课程是由WangTeacher精心制作一部视频教学实战课程,初步计划100编程题目的详细讲解,从简单到复杂的顺序排列。并结合15+年的软件开发经验,把最先进的软件开发流程和编程技巧融合到课程中,...
  • C++游戏编程8步云

    万次阅读 多人点赞 2011-09-17 00:13:55
    C++游戏编程8步云  http://www.cplusplus.com/articles/1w6AC542/> 第一步 选择游戏库   要想编写一款游戏你必须选择一个游戏库,除非你要编写自己的游戏库。下面列出了一些游戏库的名称和网站,它们都提供了...
    
    

    C++游戏编程8步云

     <英语原文:http://www.cplusplus.com/articles/1w6AC542/>

    第一步 选择游戏库

     

    要想编写一款游戏你必须选择一个游戏库,除非你要编写自己的游戏库。下面列出了一些游戏库的名称和网站,它们都提供了相同的底层功能。

                   ●  Simple Fast Multi-Media Library (SFML): http://www.sfml-dev.org/
                   ●  Simple DirectMedia Layer (SDL): http://www.libsdl.org/
                   ●  Allegro: http://www.allegro.cc/
                   ●  OpenGL (GFX only, however, there are wrapper libs like AllegroGL): http://www.opengl.org/
                   ●  DirectX (Windows only): http://msdn.microsoft.com/en-us/directx/
                   ●  rrlicht (3d lib): http://irrlicht.sourceforge.net/

    还有,一款好的游戏库一般具备以下特征:

                  ●   一些专门加载和播放声音文件的系统。
                  ●  一些专门加载和显示图像的系统。
                  ●   至少要有一些为基本图像提供的操作(如图像的旋转,图像的缩放等)
                  ●  最基本的绘画功能(如画圆函数, 绘制点、线的函数, 绘制矩形的函数等)
                  ●  文本显示功能
                  ●  支持多线程
                  ●  计时器功能

     

    第二步 定义游戏规则

           所有的游戏都从规则开始。首先,提出你对该游戏所持有的想法(规则)。一旦你有了一个想法, 你就可以扩展出更多的基于它而产生的想法。假如,你编的是一款棋类游戏,你就会想怎么才算赢局?又有什么玩法?等等。假如,你的游戏是一款讲述故事情节的游戏,只需建立一个一个故事情节即可。一定要确保你有一个好的可玩性高的游戏规则,只有这样你的游戏才算得上是一款真正的游戏。越复杂的游戏,你就越该好好的周祥一番,从打算一开始编写它时就定义好你的游戏规则,这样你在编写代码时就不必担心游戏本身所存在的问题了。在你编写游戏时,游戏的演变步骤一定要了然于胸。


    第三步 划分好你的游戏引擎

           你有必要划分出你的游戏引擎的各个功能组件,以及知道如何将各个功能组件组装在一起。由于各大小游戏的复杂性程度的不同,你也许不必关心这个步骤。但是,通过把引擎分成各个部分,可以很好的对各个部分进行测试研究,你只需要在它们被组装起来之前,确保它们能够完成它们相应的功能即可。 相反,如果不分成各个组成部分,而逐个小功能的不断补充,情况将变得很糟糕,因为你很难把握各个功能的耦合。因此,你应该开始设计类结构图了(就是所谓的面向对象程序设计(OOP))。虽然,已有各种各样的已编好的游戏引擎供你在工程中使用,但是你还是必须记着这重要的一步。


    第四步 开始编写游戏引擎(如果你是自己写引擎的话)

           现在,该是开始编写引擎的时候了。编写引擎并不意味着就是编写游戏本身,但是在一定程度上,渲染内核,物理模型,文件处理,还有各种函数和类都是构建游戏世界的组成部分。然而,由于游戏的复杂度,引擎和游戏代码也许是同一个概念。对一个更复杂的游戏,将很可能会调用一个叫资源管理员(resource manager)的类对象。资源管理员不需要你知道什么是声音文件,它只是管理你的资源文件(如,图像文件,音频文件等)。定义资源管理员类来管理所有资源不仅能使你的代码更加整洁,还能使你避免内存泄露。

           来看以下两段优秀的代码。该代码能使整个引擎达到某种程度上的紧密性,以及它提供了易于使用的接口。以此来编写你的游戏,你就不用通过资源文件来找函数名,也不用知道它是什么。一种实现这种思想的简单方法就是用OOP思想实现的。

    两个示例:

    //碰撞检测管理,在游戏中碰撞检测算法频繁出现,可采用该类进行管理
    class collisions {
        bool check_col(obj1*, obj2*); //检测两个对象是否碰撞,obj通常都由一个类派生而来
        void handle_col(obj1*, obj2*); //碰撞发生后的处理函数
    
    
        public:
            void handle_all(); //处理所有碰撞后的对象
        }Collision;
    
    //渲染事件管理,在游戏中要绘制各种游戏对象,可采用该类进行管理//
    //http://blog.csdn.net//zhanxinhang
    class rendering {
        void bots();    //游戏对象bots
        void bullets(); //游戏对象bullets
        void players(); //游戏对象players
    
    
        public:
            void draw_all(); //绘制所有对象
        }Renderer;
    
    
    //所有对象的碰撞检测处理和对象的绘制都可以通过以下两个接口并放到游戏循环中
    Renderer.draw_all();
    Collision.handle_all();
    
    


    定义资源管理员类(Resource Manager):


    #include <map>
    #include <string>
    #include <exception>
    
    
    typedef const std::string URI;
    
    
    // 异常处理
    namespace Exceptions {
    
    
    	// 如果资源路径不存在抛出此异常
    	class URINotFound : public std::runtime_error 
    	{ 
    	public: 
    		URINotFound(const std::string& Message = "The specified URI was not found in the resource index.")
    			: runtime_error(Message) { } 
    	};
    
    
    	// 如果资源内存空间分配失败抛出此异常
    	class BadResourceAllocation : public std::runtime_error {
    	public: 
    		BadResourceAllocation(const std::string& Message = "Failed to allocate memory for resource.")
    			: runtime_error(Message) {}
    	};
    }// namespace Exceptions
    
    //=http://blog.csdn.net//zhanxinhang
    //资源管理类
    template <class Resource> class ResourceManagerB {
    	typedef std::pair<URI, Resource*> ResourcePair;
    	typedef std::map<URI, Resource*> ResourceList;
    
    
    	// 资源‘菜单’
    	ResourceList Resources;
    public:
    	~ResourceManagerB() { UnloadAll(); }
    
    
    	// 通过指定路径载入资源
    	URI& Load(URI& Uri);
    	// 通过指定路径卸载资源
    	void Unload(URI& Uri);
    	// 卸载所有资源
    	void UnloadAll();
    
    
    	// 得到资源的指针
    	Resource* GetPtr(URI& Uri);
    	// 得到资源的引用
    	Resource& Get(URI& Uri);
    };
    
    //=====================================
    //** 以下为资源管理类的接口实现代码 //
    //=====================================
    
    template <class Resource>
    URI& ResourceManagerB<Resource>::Load(URI& Uri)
    {
    	// 检测资源是否已载入,否则运行里面的判断体
    	if (Resources.find(Uri) == Resources.end())
    	{
                    //分配内存
    		Resource* temp = new (std::nothrow) Resource(Uri);
                    //如果分配失败抛出异常
    		if (!temp)
    			throw Exceptions::BadResourceAllocation();
                    //插入该资源到菜单
    		Resources.insert(ResourcePair(Uri, temp));
    	}
    	return Uri;
    }
    
    
    template <class Resource>
    void ResourceManagerB<Resource>::Unload(URI& Uri)
    {
    	//找到该资源在菜单中的位置
    	ResourceList::const_iterator itr = Resources.find(Uri);
    	// 如果找到
    	if (itr != Resources.end())
    	{
    		//释放资源
    		delete itr->second;
    		// 从菜单中删除
    		Resources.erase(Uri);
    	}
    }
    
    
    template <class Resource>
    void ResourceManagerB<Resource>::UnloadAll()
    {
    	// 遍历每个资源
    	ResourceList::iterator itr;
    	for (itr = Resources.begin(); itr != Resources.end(); itr++)
    		// 释放资源
    		delete itr->second;
    	// 清除菜单
    	Resources.clear();
    }
    
    
    template <class Resource>
    Resource* ResourceManagerB<Resource>::GetPtr(URI& Uri)
    {
    	// 从菜单中找到指定元素
    	ResourceList::const_iterator itr;
    	// 如果找到
    	if ((itr = Resources.find(Uri)) != Resources.end())
    		// 返回该元素
    		return itr->second;
    	// 否则返回0
    	return 0;
    }
    
    
    template <class Resource>
    Resource& ResourceManagerB<Resource>::Get(URI& Uri)
    {
    	// 得到该资源
    	Resource* temp = GetPtr(Uri);
    	// 如果已得到该资源
    	if (temp)
    		
                    //返回该资源	
    		return *temp;
    	else
    		
    		//否则抛出异常
    		throw Exceptions::URINotFound();
    }


    第五步 图像和声音

           在游戏规则的基础之上,开始绘制各种图像以及游戏音效用来制造游戏氛围。如果你要更进一步的发展,你就需要创建更多的图像效果(GFX)和特效(SFX),并且很可能不会使用它们中的某些。而且,随着整个进程的发展,这一步可能还会继续下去。

     

    第六步 编写你的游戏

           一旦你有你有了你的游戏引擎,你就可以开始编写你的实际代码了。这步将关系到一切与游戏规则,故事情节等相关代码工作。还有,这步也关系到了游戏循环。游戏循环反复地更新一切游戏所需要的数据。可看如下代码示例。如果你的引擎能够正确运行,这将会变得比编写游戏引擎更加容易,而且更有趣! 这里也将很可能是你添加声音文件的地方。一旦这个步骤完成了,你该进行一项工作区完成游戏的一份复制。越想得到,你就越能得到!

    Game Loop:


    //你的游戏循环会因为各种不同的游戏而不同,尤其是对一些棋类游戏,以下只是一个简单的示例
     
    while (!Game.lost())        //如果游戏失败,则退出循环
     {
        Game.handle_input();    //获得输入数据
     
        AI.update_bots();       //关于bot的人工智能
     
        Collision.handle_col(); //碰撞检测
     
        Game.check_win();       //检查游戏输赢
     
        Renderer.draw_all();    //绘制游戏画面
     
        Game.sleep();           //为了不让游戏走得太快,延迟时间
    }
     
     

    第七步 代码优化

           并不是因为你成功地实现了你的游戏,就意味着你就完成整个工作。除了在你的代码中添加详细的记录说明外,还很可能需要对你的代码进行一些优化处理。这步关系到了内存的使用(尽量不要使用全局变量,检查内存泄露等),还有程序执行效率(确保你的代码执行效率不要太慢,否则cpu很容易发生超载)。常规的程序调试也可以在这有效地组织起来。

     

    第八步 打包发售

           既然你的游戏已经制作完成,你就需要将它打包起来并依照你的意愿进行发售。至于如何打包,应该尽量使你的游戏文件有组织地进行存放并且将所有文件放在一个文件夹下以便打包,这会使你的游戏发售变得更加easy.^_^

     

    ………………………………………………………………………………………………………………………………………………………………………………

    C++游戏编程8步云 完。 欢迎email:zhanxinhang@gmail.com ^_^

     <转载不忘注明出处:http://blog.csdn.net/zhanxinhang,及作者:花心龟的扮演者ZhanHang>


    展开全文
  • 本系列文章由Luffy_Luke(朱焕俊)编写,转载请注明出处。...//总结下 制作小游戏的具体步骤 //首先 游戏的框架肯定是 Win32的框架了 //现在来看 还是使用C++的类封装比较好,一个是省去了很多的
  • C++小游戏数字炸弹

    千次阅读 多人点赞 2021-01-01 08:58:09
    这个小游戏代码是我学了半年编程打出开的第一个小游戏代码,游戏内无void以及goto. 适合给新手当一个模板。 二.代码 #include<iostream> #include<ctime> #include<windows.h> using namespace ...
  • C++编程

    千人学习 2019-03-18 16:55:25
    本课程针对C++程序设计和编程思想中核心的方法和技术进行了详细的讲解,帮助已经掌握了C语言但进一步希望学习面C++向对象编程方法和思想的同学快速进入C++的大门。课程提供了视频课件、笔记和例子代码,主要内容...
  • C++游戏编程:卡牌游戏

    千次阅读 2019-05-21 00:06:06
    一段时间一直喜欢玩欢乐斗地主~~本来打算做出一个“跑得快”(有的地方叫争上游),技术有点欠缺,于是做出了一个类似打牌的卡牌游戏。 玩家将与两个电脑玩家进行游戏,一副牌总共有54张,洗牌后分为3叠,玩家随机...
  •  学习C++已有一段时间了,鉴于楼主资质平平,目前尚且停留在C++的入门边缘,对C++的基本语法、基本特征也停留在基本认识的层面上。所以本系列所罗列的源代码部分将会出现以下几个问题:  1、编码不规范,易读...
  • 【游戏开发】C++游戏编程实例

    万次阅读 多人点赞 2018-09-11 09:44:18
    网络游戏开发分为:服务器编程、客户端编程、人工智能、数据库管理、游戏策划、美工设计、音乐特效等。 大型游戏往往需要团队合作开发,因此面向对象的编程思想在网络游戏中得到了广泛应用。 游戏开发基本流程:...
  • 3D射击游戏很容易出现作弊行为,只有很好的了解其工作原理才能从根本上改善射击类游戏环境,扬汤止沸,...课程适合有一定C++基础并对游戏安全感兴趣的朋友,临渊羡鱼不如归而结网,让我们一起畅游在C++编程的欢乐世界!
  • C++ 小游戏大全

    千次阅读 多人点赞 2021-05-11 15:05:37
    C++ 小游戏大全 感谢作者的提供! 小游戏列表 龙的传人1.1.2 作者 Evan_song: https://paste.ubuntu.com/p/Nn6CCDtfzC/ 哈利波特1.2.9 作者 Evan_song: https://paste.ubuntu.com/p/cCG3rNVW2D/ 生存游戏1.1.0 ...
  • 菜鸟前路---c/c++小游戏

    万次阅读 多人点赞 2019-01-07 09:15:58
    翻到开学之初写的一些小游戏,陆续给大家发出来,也便提高新手们的编程兴趣。 (在Dev,codeblocks,VC上都能运行) #include&lt;stdio.h&gt; #include&lt;time.h&gt; #include&lt;stdlib.h...
  • c++游戏编程 源代码

    万次阅读 2019-09-25 03:24:09
    //贪吃蛇大战HuangZhen    #include    #include    #include<conio.h>//kbhit();getch() ... #include<ctime>//随机种子 ... #include<cstdlib>//随机种子 ... int *p=(int *)malloc(100);//malloc ...
  • C++编程基础

    万人学习 2020-02-09 22:57:09
    本课程结合c++编程语言特点以及企业实际需求,经过精心编排和设计,主要有以下特色: 1、精细讲解C++重要语法,快速掌握C++编程技巧; 2、全面领悟STL标准模板库,为存储和处理数据提供标准步骤; 3、深入了解MFC...
  • C/C++游戏编程开发

    千次阅读 2017-03-25 12:12:26
    C/C++游戏编程开发,采用的是Win32/64下编程,利用的开发工具是VS2008,本套教程针对那些喜欢游戏开发的人是非常不错的教程,学习要求,有C/C++编程功底,可以不是很高,但是至少也要学习到函数这个地步,才能进行本...
  • C++ Qt编程

    千人学习 2018-03-04 23:56:36
    本课程以C++编程为导向来详细介绍Qt编程,课程包括十四个部分,分别介绍Qt的编程环境、窗体应用、控件应用、组件应用、文件操作、图形图像操作、多媒体应用、对系统操作、对注册表操作、数据库应用,网络应用开发、...
  • C++高级编程

    2020-06-30 15:10:05
    C++语言在经典的C语言基础上扩展了面向对象和泛型编程的能力,遵从实用哲学,理论上能够编写各种类型和规模的软件。 课程介绍:本门课程分为5大部分:C语言回顾、C++面向过程扩展、C++面向对象、C++泛型编程C++...
  • C++核心编程

    千人学习 2019-12-19 22:58:12
    C++核心编程,内容包括:开发环境、数据类型、string和vector、数组和指针、流程控制、容器、面向对象等。
  • 这就是用Easyx做出来的一个简单小游戏啦,自己做的不太好 因为是懒虫(HHHH),飞机和敌人什么的图片都没有弄好,都是在网上随便找的图片。敌人我就随便放了个照片 相信大家一定比我设计的好!!(飞机是真的丑哈哈...
  • C++编程指南

    千人学习 2018-12-17 11:26:59
    C++编程指南:趣味C++编程,使用边学边练的教学模式,以生动形象的语言来展示面向对象编程的魅力,带你掌握C++语言中类、对象、运算符重载、继承、多态等面向对象的程序设计方法,以及模板、标准模板库STL等泛型程序...
  • 一直说写个几百行的项目,于是我写了一个控制台的扫雷,没有想到精简完了代码才200行左右,不过考虑到这是我精简过后的,浓缩才是精华嘛,我就发出来大家一起学习啦,看到程序跑起来能玩,感觉还是蛮有成就感的~...
  • c++游戏编程初步(超简单)教学

    万次阅读 多人点赞 2019-02-21 19:49:37
    已经学了简单的编程,不做一些小游戏怎么行? 今天,我们就来了解一下关于做小游戏的技巧以及其中使用的函数; (本教程面向会循环嵌套,分支,一维数组,多重分支的人,如果不会请自行回避) 本次提供的是一个找宝藏...
  • C++控制台小游戏

    千次阅读 2020-02-12 14:32:25
        使用C++编写一些简单怀旧的小游戏,没有实现其中的玩法之类的,只实现核心功能 运行说明 运行平台是Windows     下载压缩包或者使用git都行,完成后使用clion(其他支持cmake工程的也行,环境需要配置好,...
  • 一个简单的c++小游戏——2048

    千次阅读 2020-11-27 20:17:02
    一个简单的c++小游戏——2048 代码 话不多说,代码如下: #include<iostream> #include<vector> #include<ctime> #include<cstdlib> using namespace std; class Game_2048 { public: Game...
  • C++课设】21点小游戏C++

    千次阅读 多人点赞 2018-10-19 00:15:00
    21点小游戏,顾名思义,玩家需要通过纸牌上的点数,通过继续拿牌和不要牌,来增长自己的点数,当最接近21点,或者等于21时,胜利;但当玩家的点数超过21点时,即游戏失败。利用面向对象程序设计方法以及C++编程...
  • 现在学习编程了,就要探寻本质了,通过这款小游戏,你可以掌握许多的编程技巧,比如碰撞检测、音乐播放、图形调用、游戏控制、游戏逻辑等方面,各种形状方块的接触面数据。 项目源代码: 部分代码 测试...
  • 少儿C++快乐编程

    千人学习 2019-08-15 14:49:25
    少儿C++快乐编程 适合小学高年级以及中学生自学的编程教程,包含了C++所有的基础知识,用实例教学,在家里就可以学习的少儿编程,不用去价格昂贵的培训机构。每天学习一节课,提高学生的编程能力。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 67,791
精华内容 27,116
关键字:

c++小游戏编程100例

c++ 订阅