C語言趣味程序設計編程百例精解

C/C++語言經典、實用、趣味程序設計編程百例精解(1) 

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 in555555 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 inthe end of 100! is: %d.\n”,count); //打印結果
return 0;
}

*運行結果
The number of 0 in the endof 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 3readers:\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 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”);
}
}

void 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;
}

*思考題
自行設計一種實現楊輝三角形的方法

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 thatday.\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 %.2fat 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 abank:\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) 

位反序數

 

設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 than3000:\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 than200000:\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>
int main(void)
{
int m[16],n,i,t,count=0;
long unsigned a,k;
printf(“No. number it’s square(palindrome)\n”);
for(n=1;n<256;n++) /*窮舉n的取值範圍*/
{
k=0;t=1;a=n*n; /*計算n的平方*/

for(i=0;a!=0;i++) /*從低到高分解數a的每一位存於數組m[0]~m[16]*/
{
m[i]=a%10;//這個是取得a的個位,整個循環合起來就可以取得各個位
a/=10;
}

int j=0;
for(i–;j<i;j++,i–)//因爲n的平方的各個位都存在數組中了,下面判斷是不是對稱
if(m[j]!=m[i])break;//只要有一位不是對稱,那就說明不是對稱,就可以退出了

//所有的位都對稱就說明是對稱了,這樣就可以打印出結果了
if(j>=i)printf(“%2d%10d%10d\n”,++count,n,n*n);

}

return 0;
}

*運行結果
No. number it’s square(palindrome)
1 1 1
2 2 4
3 3 9
4 11 121
5 22 484
6 26 676
7 101 10201
8 111 12321
9 121 14641
10 202 40804
11 212 44944

//下面程序是原來的,有錯,而且費解

#include<stdio.h>
int main(void)
{
int m[16],n,i,t,count=0;
long unsigned a,k;
printf(“No. number it’s square(palindrome)\n”);
for(n=1;n<256;n++) /*窮舉n的取值範圍*/
{
k=0;t=1;a=n*n; /*計算n的平方*/

for(i=1;a!=0;i++) /*從低到高分解數a的每一位存於數組m[1]~m[16]*/
{
m[i]=a%10;//安安注:這個是取得a的個位,整個循環合起來就可以取得各個位,並存於數組中,爲了是下面判斷是不是對稱
a/=10;
}

for(;i>1;i–)
{
k+=m[i-1]*t;
t*=10;
}
if(k==n*n)
printf(“%2d%10d%10d\n”,++count,n,n*n);
}

return 0;
}

 

*運行結果
No. number it’s square(palindrome)
1 1 1
2 2 4
3 3 9
4 11 121
5 22 484
6 26 676
7 101 10201
8 111 12321
9 121 14641

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 satisfiedcondition\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);
}
}

intfflag(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;
}

intnum(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;
}

intok(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;
}

return1;
}

*思考題

求1000以內的孿生素數。孿生素數是指:若a爲素數,且a+2也是素數,則素數a和a+2稱爲孿生素數。

33.迴文素數

求不超過1000的迴文素數。

*問題分析與算法設計
  所謂迴文素數是指,對一個整數n從左向右和從由向左讀其結果值相同且是素數,即稱n爲迴文素數。所以本題的重點不是判斷素數的方法,而是求迴文整數。構造迴文數的方法很多,這裏僅介紹一種最簡單的算法。實現思路是先求出一個整數的迴文數,再判斷是否爲素數。
  不超過1000的迴文數包括二位和三位的迴文數,我們採用窮舉法來構造一個整數並求與其對應的反序數,若整數與其反序數相等,則該整數是迴文數。

*程序說明與註釋

#include<stdio.h>

inta(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;
}

return1;

}

*運行結果

Followingare 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……19791987 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]);
}
}

intfflag(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);

intmain()
{
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(); } /*輸出幻方矩陣*/
}
}
}
}
}

intnum(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;
}

intok(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;
}

voidprocess(int i) /*將第i個整數分解爲數字並存入數組*/
{
int j,num;
num=number[i][0];
for(j=4;j>=1;j–,num/=10)
number[i][j]=num%10;
}

voidcopy_num(int i) /*將array[i][0]指向的素數的各位數字複製到array[i]中*/
{
int j;
for(j=1;j<=4;j++)
array[i][j]=number[array[i][0>[j];
}

intcomp_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;
}

intfind1(int i) /*判斷列方向是否是可逆素數*/
{
int num,j;
for(num=0,j=0;j<4;j++)
num=num*10+array[j][i];
return find0(num);
}

intfind2(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;
}

intfind0(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;
}

voidp_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 100Yuan.\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 familyZhang.\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 familyLi.\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 45
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);
}
}
}
}

voidprint(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);

intmain()
{
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
○○A5 7 2
○○○B2 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。
可以看作爲下列算式:

97 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]<=13770<=a[1]<=9
0<=a[2]<=9 0<=a[3]<=99
除數: 112<=b <=142
商: 8<=c[0]<=97<=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

*思考題
下列除式中“×”所在的位置全部是任意數字,請還原。
×××××
——————-
××× | ××××××××
××××
——————
××××
×××
—————
×××
×××
———–
××××
××××
———–
0

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 QK
紅桃 A 2 3 4 5 6 7 8 9 10 J QK
問魔術師手中牌的原始順序是什麼?

*問題分析與算法設計
本題可在上題的基礎上進行編程,不同的在於計數的方法和牌的張數,這些並不影響我們求解題目的思路,仍可按照倒推的方法,得到原來魔術師手中的牌的順序。

*程序說明與註釋

#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); /*調用函數進行驗證*/
}

voidvr6174(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
2) Enter a number:8720
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[]);

intmain(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); /*分別打印每個人的牌*/

return0;
}

voidp(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”);
}

intcomp(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 %dtimes.\n”,c);
}

Nowyou 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 asfelow:\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:
[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);

intmain()
{
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矩陣,b4爲空格/
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不在正確的位置/
{
b4=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移入正確的位置/
b4=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初始位置已經正確這一條件,可以大大優化移動排序的過程。

*思考題
請重新設計算法,編寫更優化的程序,儘可能減少移動的步數。

請設計完成兩個超長正整數的減法、乘法和除法的運算

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章