用代码写动态显示偶数等差数列偶数项

编程知识:51个C语言经典算法说明与代码(1-17)
【1.河内之塔】
说明:河内之塔(Towers of
Hanoi)是法国人M.Claus(Lucas)于1883年从泰国带至法国的,河内为越战时北越的首都,即现在的胡志明市;1883年法国数学家Edouard
Lucas曾提及这个故事,据说创世纪时Benares有一座波罗教塔,是由三支钻石棒(Pag)所支撑,开始时神在第一根棒上放置64个由上至下依由小至大排列的金盘(Disc),并命令僧侣将所有的金盘从第一根石棒移至第三根石棒,且搬运过程中遵守大盘子在小盘子之下的原则,若每日仅搬一个盘子,则当盘子全数搬运完毕之时,此塔将毁损,而也就是世界末日来临之时。
解法:如果柱子标为ABC,要由A搬至C,在只有一个盘子时,就将它直接搬至C,当有两个盘子,就将B当作辅助柱。如果盘数超过2个,将第三个以下的盘子遮起来,就很简单了,每次处理两个盘子,也就是:A-&B、A
-&C、B-&C这三个步骤,而被遮住的部份,其实就是进入程式的递回处理。事实上,若有n个盘子,则移动完毕所需之次数为2^n
- 1,所以当盘数为64时,则所需次数为:264- 1 =
.82e+16年,也就是约5000世纪,如果对这数字没什么概念,就假设每秒钟搬一个盘子好了,也要约5850亿年左右。
void hanoi(int n, char A, char B, char C) {
if(n == 1) {
printf("Move sheet %d from %c to %c\n", n, A, C);
hanoi(n-1, A, C, B);
printf("Move sheet %d from %c to %c\n", n, A, C);
hanoi(n-1, B, A, C);
int main() {
printf("请输入盘数:");
scanf("%d", &n);
hanoi(n, 'A', 'B', 'C');
【2.Algorithm Gossip: 费式数列】
说明:Fibonacci为1200年代的欧洲数学家,在他的着作中曾经提到:「若有一只免子每个月生一只小免子,一个月后小免子也开始生产。起初只有一只免子,一个月后就有两只免子,二个月后有三只免子,三个月后有五只免子(小免子投入生产)......。如果不太理解这个例子的话,举个图就知道了,注意新生的小免子需一个月成长期才会投入生产,类似的道理也可以用于植物的生长,这就是Fibonacci数列,一般习惯称之为费氏数列,例如以下:
1、1 、2、3、5、8、13、21、34、55、89......
解法:依说明,我们可以将费氏数列定义为以下:fn=fn-1+fn-2 if n&1 fn=n if n=0, 1
#define N 20
int main(void) {
int Fib[N] = {0};
Fib[0] = 0;
Fib[1] = 1;
for(i = 2; i & N; i++)
Fib[i] = Fib[i-1] + Fib[i-2];
for(i = 0; i & N; i++)
printf("%d ", Fib[i]);
printf("\n");
【3.巴斯卡三角形】
#define N 12
long combi(int n, int r){
long p = 1;
for(i = 1; i &= i++)
p = p * (n-i+1) /
void paint() {
for(n = 0; n &= N; n++) {
for(r = 0; r &= r++) {
if(r == 0) {
for(i = 0; i &= (N-n); i++)
printf(" ");
printf(" ");
printf("=", combi(n, r));
printf("\n");
【4.Algorithm Gossip: 三色棋】
说明:三色旗的问题最早由E.W.Dijkstra所提出,他所使用的用语为Dutch Nation
Flag(Dijkstra为荷兰
人),而多数的作者则使用Three-Color
Flag来称之。假设有一条绳子,上面有红、白、蓝三种颜色的旗子,起初绳子上的旗子颜色并没有顺序,您希望将之分类,并排列为蓝、白、红的顺序,要如何移动次数才会最少,注意您只能在绳子上进行这个动作,而且一次只能调换两个旗子。
解法:在一条绳子上移动,在程式中也就意味只能使用一个阵列,而不使用其它的阵列来作辅助,问题的解法很简单,您可以自己想像一下在移动旗子,从绳子开头进行,遇到蓝色往前移,遇到白色留在中间,遇到红色往后移,如下所示:只是要让移动次数最少的话,就要有些技巧:如果图中W所在的位置为白色,则W+1,表示未处理的部份移至至白色群组。如果W部份为蓝色,则B与W的元素对调,而B与W必须各+1,表示两个群组都多了一个元素。如果W所在的位置是红色,则将W与R交换,但R要减1,表示未处理的部份减1。注意B、W、R并不是三色旗的个数,它们只是一个移动的指标;什幺时候移动结束呢?一开始时未处理的R指标会是等于旗子的总数,当R的索引数减至少于W的索引数时,表示接下来的旗子就都是红色了,此时就可以结束移动,如下所示:
#define BLUE 'b'
#define WHITE 'w'
#define RED 'r'
#define SWAP(x, y) { \
temp = color[x]; \
color[x] = color[y]; \
color[y] = }
int main() {
char color[] = {'r', 'w', 'b', 'w', 'w',
'b', 'r', 'b', 'w', 'r', '\0'};
int wFlag = 0;
int bFlag = 0;
int rFlag = strlen(color) - 1;
for(i = 0; i & strlen(color); i++)
printf("%c ", color[i]);
printf("\n");
while(wFlag &= rFlag) {
if(color[wFlag] == WHITE)
else if(color[wFlag] == BLUE) {
SWAP(bFlag, wFlag);
bFlag++; wFlag++;
while(wFlag & rFlag && color[rFlag] == RED)
SWAP(rFlag, wFlag);
for(i = 0; i & strlen(color); i++)
printf("%c ", color[i]);
printf("\n");
【5.Algorithm Gossip: 老鼠走迷官(一)】
说明:老鼠走迷宫是递回求解的基本题型,我们在二维阵列中使用2表示迷宫墙壁,使用1来表示老鼠的行走路径,试以程式求出由入口至出口的路径。
解法:老鼠的走法有上、左、下、右四个方向,在每前进一格之后就选一个方向前进,无法前进时退回选择下一个可前进方向,如此在阵列中依序测试四个方向,直到走到出口为止,这是递回的基本题,请直接看程式应就可以理解。
int visit(int, int);
int maze[7][7] = {{2, 2, 2, 2, 2, 2, 2},
{2, 0, 0, 0, 0, 0, 2},
{2, 0, 2, 0, 2, 0, 2},
{2, 0, 0, 2, 0, 2, 2},
{2, 2, 0, 2, 0, 2, 2},
{2, 0, 0, 0, 0, 0, 2},
{2, 2, 2, 2, 2, 2, 2}};
int startI = 1, startJ = 1; // 入口
int endI = 5, endJ = 5; // 出口
int success = 0;
int main(void) {
printf("显示迷宫:\n");
for(i = 0; i & 7; i++) {
for(j = 0; j & 7; j++)
if(maze[i][j] == 2)
printf("█");
printf(" ");
printf("\n");
if(visit(startI, startJ) == 0)
printf("\n没有找到出口!\n");
printf("\n显示路径:\n");
for(i = 0; i & 7; i++) {
for(j = 0; j & 7; j++) {
if(maze[i][j] == 2)
printf("█");
else if(maze[i][j] == 1)
printf("◇");
printf(" ");
printf("\n");
int visit(int i, int j) {
maze[i][j] = 1;
if(i == endI && j == endJ)
success = 1;
if(success != 1 && maze[i][j+1] == 0) visit(i, j+1);
if(success != 1 && maze[i+1][j] == 0) visit(i+1, j);
if(success != 1 && maze[i][j-1] == 0) visit(i, j-1);
if(success != 1 && maze[i-1][j] == 0) visit(i-1, j);
if(success != 1)
maze[i][j] = 0;
【6.Algorithm Gossip: 老鼠走迷官(二)】
说明:由于迷宫的设计,老鼠走迷宫的入口至出口路径可能不只一条,如何求出所有的路径呢?
解法:求所有路径看起来复杂但其实更简单,只要在老鼠走至出口时显示经过的路径,然后退回上一格重新选择下一个位置继续递回就可以了,比求出单一路径还简单,我们的程式只要作一点修改就可以了。
void visit(int, int);
int maze[9][9] = {{2, 2, 2, 2, 2, 2, 2, 2, 2},
{2, 0, 0, 0, 0, 0, 0, 0, 2},
{2, 0, 2, 2, 0, 2, 2, 0, 2},
{2, 0, 2, 0, 0, 2, 0, 0, 2},
{2, 0, 2, 0, 2, 0, 2, 0, 2},
{2, 0, 0, 0, 0, 0, 2, 0, 2},
{2, 2, 0, 2, 2, 0, 2, 2, 2},
{2, 0, 0, 0, 0, 0, 0, 0, 2},
{2, 2, 2, 2, 2, 2, 2, 2, 2}};
int startI = 1, startJ = 1; // 入口
int endI = 7, endJ = 7; // 出口
int main(void) {
printf("显示迷宫:\n");
for(i = 0; i & 7; i++) {
for(j = 0; j & 7; j++)
if(maze[i][j] == 2)
printf("█");
printf(" ");
printf("\n");
visit(startI, startJ);
void visit(int i, int j) {
maze[i][j] = 1;
if(i == endI && j == endJ) {
printf("\n显示路径:\n");
for(m = 0; m & 9; m++) {
for(n = 0; n & 9; n++)
if(maze[m][n] == 2)
printf("█");
else if(maze[m][n] == 1)
printf("◇");
printf(" ");
printf("\n");
if(maze[i][j+1] == 0) visit(i, j+1);
if(maze[i+1][j] == 0) visit(i+1, j);
if(maze[i][j-1] == 0) visit(i, j-1);
if(maze[i-1][j] == 0) visit(i-1, j);
maze[i][j] = 0;
【7.Algorithm Gossip: 骑士走棋盘】
说明:骑士旅游(Knight
tour)在十八世纪初倍受数学家与拼图迷的注意,它什么时候被提出已不可考,骑士的走法为西洋棋的走法,骑士可以由任一个位置出发,它要如何走完[所有的位置?
解法:骑士的走法,基本上可以使用递回来解决,但是纯綷的递回在维度大时相当没有效率,一个聪明的解法由J.C.
Warnsdorff在1823年提出,简单的说,先将最难的位置走完,接下来的路就宽广了,骑士所要走的下一步,「为下一步再选择时,所能走的步数最少的一步。」,使用这个方法,在不使用递回的情况下,可以有较高的机率找出走法(找不到走法的机会也是有的)。
int board[8][8] = {0};
int main(void) {
int startx,
printf("输入起始点:");
scanf("%d %d", &startx, &starty);
if(travel(startx, starty)) {
printf("游历完成!\n");
printf("游历失败!\n");
for(i = 0; i & 8; i++) {
for(j = 0; j & 8; j++) {
printf("- ", board[i][j]);
putchar('\n');
int travel(int x, int y) {
// 对应骑士可走的八个方向
int ktmove1[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int ktmove2[8] = {1, 2, 2, 1, -1, -2, -2, -1};
// 测试下一步的出路
int nexti[8] = {0};
int nextj[8] = {0};
// 记录出路的个数
int exists[8] = {0};
int i, j, k, m,
int count, min,
board[i][j] = 1;
for(m = 2; m &= 64; m++) {
for(l = 0; l & 8; l++)
exists[l] = 0;
// 试探八个方向
for(k = 0; k & 8; k++) {
tmpi = i + ktmove1[k];
tmpj = j + ktmove2[k];
// 如果是边界了,不可走
if(tmpi & 0 || tmpj & 0 || tmpi & 7 || tmpj & 7)
// 如果这个方向可走,记录下来
if(board[tmpi][tmpj] == 0) {
nexti[l] =
nextj[l] =
// 可走的方向加一个
// 如果可走的方向为0个,返回
if(count == 0) {
else if(count == 1) {
// 只有一个可走的方向
// 所以直接是最少出路的方向
// 找出下一个位置的出路数
for(l = 0; l & l++) {
for(k = 0; k & 8; k++) {
tmpi = nexti[l] + ktmove1[k];
tmpj = nextj[l] + ktmove2[k];
if(tmpi & 0 || tmpj & 0 ||
tmpi & 7 || tmpj & 7) {
if(board[tmpi][tmpj] == 0)
exists[l]++;
tmp = exists[0];
// 从可走的方向中寻找最少出路的方向
for(l = 1; l & l++) {
if(exists[l] & tmp) {
tmp = exists[l];
// 走最少出路的方向
i = nexti[min];
j = nextj[min];
board[i][j] =
【8.Algorithm Gossip: 八皇后】
说明:西洋棋中的皇后可以直线前进,吃掉遇到的所有棋子,如果棋盘上有八个皇后,则这八个皇后如何相安无事的放置在棋盘上,1970年与1971年,
E.W.Dijkstra与N.Wirth曾经用这个问题来讲解程式设计之技巧。
解法:关于棋盘的问题,都可以用递回求解,然而如何减少递回的次数?在八个皇后的问题中,不必要所有的格子都检查过,例如若某列检查过,该该列的其它格子就不用再检查了,这个方法称为分支修剪。
#define N 8
int column[N+1]; // 同栏是否有皇后,1表示有
int rup[2*N+1]; // 右上至左下是否有皇后
int lup[2*N+1]; // 左上至右下是否有皇后
int queen[N+1] = {0};
// 解答编号
void backtrack(int); // 递回求解
int main(void) {
for(i = 1; i &= N; i++)
column[i] = 1;
for(i = 1; i &= 2*N; i++)
rup[i] = lup[i] = 1;
backtrack(1);
void showAnswer() {
printf("\n解答%d\n", ++num);
for(y = 1; y &= N; y++) {
for(x = 1; x &= N; x++) {
if(queen[y] == x) {
printf(" Q");
printf(" .");
printf("\n");
void backtrack(int i) {
if(i & N) {
showAnswer();
for(j = 1; j &= N; j++) {
if(column[j] == 1 &&
rup[i+j] == 1 && lup[i-j+N] == 1) {
queen[i] =
// 设定为占用
column[j] = rup[i+j] = lup[i-j+N] = 0;
backtrack(i+1);
column[j] = rup[i+j] = lup[i-j+N] = 1;
【9.Algorithm Gossip: 八枚银币】
说明:现有八枚银币a b c d e f g
h,已知其中一枚是假币,其重量不同于真币,但不知是较轻或较重,如何使用天平以最少的比较次数,决定出哪枚是假币,并得知假币比真币较轻或较重。
解法:单就求假币的问题是不难,但问题限制使用最少的比较次数,所以我们不能以单纯的回圈比较来求解,我们可以使用决策树(decision
tree),使用分析与树状图来协助求解。一个简单的状况是这样的,我们比较a+b+c与d+e+f
,如果相等,则假币必是g或h,我们先比较g或h哪个较重,如果g较重,再与a比较(a是真币),如果g等于a,则g为真币,则h为假币,由于h比g轻而g是真币,则h假币的重量比真币轻。
void compare(int[], int, int, int);
void eightcoins(int[]);
int main(void) {
int coins[8] = {0};
srand(time(NULL));
for(i = 0; i & 8; i++)
coins[i] = 10;
printf("\n输入假币重量(比10大或小):");
scanf("%d", &i);
coins[rand() % 8] =
eightcoins(coins);
printf("\n\n列出所有钱币重量:");
for(i = 0; i & 8; i++)
printf("%d ", coins[i]);
printf("\n");
void compare(int coins[], int i, int j, int k) {
if(coins[i] & coins[k])
printf("\n假币%d 较重", i+1);
printf("\n假币%d 较轻", j+1);
void eightcoins(int coins[]) {
if(coins[0]+coins[1]+coins[2] ==
coins[3]+coins[4]+coins[5]) {
if(coins[6] & coins[7])
compare(coins, 6, 7, 0);
compare(coins, 7, 6, 0);
else if(coins[0]+coins[1]+coins[2] &
coins[3]+coins[4]+coins[5]) {
if(coins[0]+coins[3] == coins[1]+coins[4])
compare(coins, 2, 5, 0);
else if(coins[0]+coins[3] & coins[1]+coins[4])
compare(coins, 0, 4, 1);
if(coins[0]+coins[3] & coins[1]+coins[4])
compare(coins, 1, 3, 0);
else if(coins[0]+coins[1]+coins[2]
&&BR&coins[3]+coins[4]+coins[5]) {
if(coins[0]+coins[3] == coins[1]+coins[4])
compare(coins, 5, 2, 0);
else if(coins[0]+coins[3] & coins[1]+coins[4])
compare(coins, 3, 1, 0);
if(coins[0]+coins[3] & coins[1]+coins[4])
compare(coins, 4, 0, 1);
【10.Algorithm Gossip: 生命游戏】
说明:生命游戏(game of life)为1970年由英国数学家J. H.
Conway所提出,某一细胞的邻居包括上、下、左、右、左上、左下、右上与右下相邻之细胞,游戏规则如下:孤单死亡:如果细胞的邻居小于一个,则该细胞在下一次状态将死亡。拥挤死亡:如果细胞的邻居在四个以上,则该细胞在下一次状态将死亡。稳定:如果细胞的邻居为二个或三个,则下一次状态为稳定存活。复活:如果某位置原无细胞存活,而该位置的邻居为三个,则该位置将复活一细胞。
解法:生命游戏的规则可简化为以下,并使用CASE比对即可使用程式实作:邻居个数为0、1、4、5、6、7、8时,则该细胞下次状态为死亡。邻居个数为2时,则该细胞下次状态为复活。邻居个数为3时,则该细胞下次状态为稳定。
#define MAXROW 10
#define MAXCOL 25
#define DEAD 0
#define ALIVE 1
int map[MAXROW][MAXCOL], newmap[MAXROW][MAXCOL];
void init();
int neighbors(int, int);
void outputMap();
void copyMap();
int main() {
while(1) {
outputMap();
for(row = 0; row & MAXROW; row++) {
for(col = 0; col & MAXCOL; col++) {
switch (neighbors(row, col)) {
newmap[row][col] = DEAD;
newmap[row][col] = map[row][col];
newmap[row][col] = ALIVE;
copyMap();
printf("\nContinue next Generation ? ");
getchar();
ans = toupper(getchar());
if(ans != 'Y')
void init() {
for(row = 0; row & MAXROW; row++)
for(col = 0; col & MAXCOL; col++)
map[row][col] = DEAD;
puts("Game of life Program");
puts("Enter x, y where x, y is living cell");
printf("0 &= x &= %d, 0 &= y &= %d\n",
MAXROW-1, MAXCOL-1);
puts("Terminate with x, y = -1, -1");
while(1) {
scanf("%d %d", &row, &col);
if(0 &= row && row & MAXROW &&
0 &= col && col & MAXCOL)
map[row][col] = ALIVE;
else if(row == -1 || col == -1)
printf("(x, y) exceeds map ranage!");
int neighbors(int row, int col) {
int count = 0, c,
for(r = row-1; r &= row+1; r++)
for(c = col-1; c &= col+1; c++) {
if(r & 0 || r &= MAXROW || c & 0 || c &= MAXCOL)
if(map[r][c] == ALIVE)
if(map[row][col] == ALIVE)
void outputMap() {
printf("\n\n cGame of life cell status\n");
for(row = 0; row & MAXROW; row++) {
printf("\n c", ' ');
for(col = 0; col & MAXCOL; col++)
if(map[row][col] == ALIVE) putchar('#');
else putchar('-');
void copyMap() {
for(row = 0; row & MAXROW; row++)
for(col = 0; col & MAXCOL; col++)
map[row][col] = newmap[row][col];
【11.Algorithm Gossip: 字串核对】
说明:今日的一些高阶程式语言对于字串的处理支援越来越强大(例如Java、Perl等),不过字串搜寻本身仍是个值得探讨的课题,在这边以Boyer-
Moore法来说明如何进行字串说明,这个方法快且原理简洁易懂。
解法:字串搜寻本身不难,使用暴力法也可以求解,但如何快速搜寻字串就不简单了,传统的字串搜寻是从关键字与字串的开头开始比对,例如Knuth-Morris-Pratt
演算法字串搜寻,这个方法也不错,不过要花时间在公式计算上;Boyer-Moore字串核对改由关键字的后面开始核对字串,并制作前进表,如果比对不符合则依前进表中的值前进至下一个核对处,假设是p好了,然后比对字串中p-n+1至p的值是否与关键字相同。如果关键字中有重复出现的字元,则前进值就会有两个以上的值,此时则取前进值较小的值,如此就不会跳过可能的位置,例如texture这个关键字,t的前进值应该取后面的3而不是取前面的7。
void table(char*); // 建立前进表
int search(int, char*, char*); // 搜寻关键字
void substring(char*, char*, int, int); // 取出子字串
int skip[256];
int main(void) {
char str_input[80];
char str_key[80];
char tmp[80] = {'\0'};
printf("请输入字串:");
gets(str_input);
printf("请输入搜寻关键字:");
gets(str_key);
m = strlen(str_input); // 计算字串长度
n = strlen(str_key);
table(str_key);
p = search(n-1, str_input, str_key);
while(p != -1) {
substring(str_input, tmp, p, m);
printf("%s\n", tmp);
p = search(p+n+1, str_input, str_key);
printf("\n");
void table(char *key) {
n = strlen(key);
for(k = 0; k &= 255; k++)
for(k = 0; k & n - 1; k++)
skip[key[k]] = n - k - 1;
int search(int p, char* input, char* key) {
char tmp[80] = {'\0'};
m = strlen(input);
n = strlen(key);
while(p & m) {
substring(input, tmp, p-n+1, p);
if(!strcmp(tmp, key)) // 比较两字串是否相同
return p-n+1;
p += skip[input[p]];
return -1;
void substring(char *text, char* tmp, int s, int e) {
for(i = s, j = 0; i &= i++, j++)
mp[j] = text[i];
tmp[j] = '\0';
【12.Algorithm Gossip: 双色、三色河内塔】
说明:双色河内塔与三色河内塔是由之前所介绍过的河内塔规则衍生而来,双色河内塔的目的是将下图左上的圆环位置经移动成为右下的圆环位置:而三色河内塔则是将下图左上的圆环经移动成为右上的圆环:
解法:无论是双色河内塔或是三色河内塔,其解法观念与之前介绍过的河内塔是类似的,同样也是使用递回来解,不过这次递回解法的目的不同,我们先来看只有两个盘的情况,这很简单,只要将第一柱的黄色移动至第二柱,而接下来第一柱的蓝色移动至第三柱。再来是四个盘的情况,首先必须用递回完成下图左上至右下的移动:接下来最底层的就不用管它们了,因为它们已经就定位,只要再处理第一柱的上面两个盘子就可以了。那么六个盘的情况呢?一样!首先必须用递回完成下图左上至右下的移动:接下来最底层的就不用管它们了,因为它们已经就定位,只要再处理第一柱上面的四个盘子就可以了,这又与之前只有四盘的情况相同,接下来您就知道该如何进行解题了,无论是八个盘、十个盘以上等,都是用这个观念来解题。那么三色河内塔呢?一样,直接来看九个盘的情况,首先必须完成下图的移动结果:接下来最底两层的就不用管它们了,因为它们已经就定位,只要再处理第一柱上面的三个盘子就可以了。双色河内塔C代码:
void hanoi(int disks, char source, char temp, char target) {
if (disks == 1) {
printf("move disk from %c to %c\n", source, target);
printf("move disk from %c to %c\n", source, target);
hanoi(disks-1, source, target, temp);
hanoi(1, source, temp, target);
hanoi(disks-1, temp, source, target);
void hanoi2colors(int disks) {
char source = 'A';
char temp = 'B';
char target = 'C';
for(i = disks / 2; i & 1; i--) {
hanoi(i-1, source, temp, target);
printf("move disk from %c to %c\n", source, temp);
printf("move disk from %c to %c\n", source, temp);
hanoi(i-1, target, temp, source);
printf("move disk from %c to %c\n", temp, target);
printf("move disk from %c to %c\n", source, temp);
printf("move disk from %c to %c\n", source, target);
int main() {
printf("请输入盘数:");
scanf("%d", &n);
hanoi2colors(n);
三色河内塔C 实作
void hanoi(int disks, char source, char temp, char target) {
if (disks == 1) {
printf("move disk from %c to %c\n", source, target);
printf("move disk from %c to %c\n", source, target);
printf("move disk from %c to %c\n", source, target);
hanoi(disks-1, source, target, temp);
hanoi(1, source, temp, target);
hanoi(disks-1, temp, source, target);
void hanoi3colors(int disks) {
char source = 'A';
char temp = 'B';
char target = 'C';
if(disks == 3) {
printf("move disk from %c to %c\n", source, temp);
printf("move disk from %c to %c\n", source, temp);
printf("move disk from %c to %c\n", source, target);
printf("move disk from %c to %c\n", temp, target);
printf("move disk from %c to %c\n", temp, source);
printf("move disk from %c to %c\n", target, temp);;
hanoi(disks/3-1, source, temp, target);
printf("move disk from %c to %c\n", source, temp);
printf("move disk from %c to %c\n", source, temp);
printf("move disk from %c to %c\n", source, temp);
hanoi(disks/3-1, target, temp, source);
printf("move disk from %c to %c\n", temp, target);
printf("move disk from %c to %c\n", temp, target);
printf("move disk from %c to %c\n", temp, target);
hanoi(disks/3-1, source, target, temp);
printf("move disk from %c to %c\n", target, source);
printf("move disk from %c to %c\n", target, source);
hanoi(disks/3-1, temp, source, target);
printf("move disk from %c to %c\n", source, temp);
for (i = disks / 3 - 1; i & 0; i--) {
if (i&1) {
hanoi(i-1, target, source, temp);
printf("move disk from %c to %c\n",target, source);
printf("move disk from %c to %c\n",target, source);
if (i&1) {
hanoi(i-1, temp, source, target);
printf("move disk from %c to %c\n", source, temp);
int main() {
printf("请输入盘数:");
scanf("%d", &n);
hanoi3colors(n);
【13.Algorithm Gossip: 背包问题(Knapsack Problem)】
说明:假设有一个背包的负重最多可达8公斤,而希望在背包中装入负重范围内可得之总价物品,假设是水果好了,水果的编号、单价与重量如下所示:0
李子4KG NT$4500 1 苹果5KG NT$5700 2 橘子2KG NT$2250 3 草莓1KG NT$1100
解法:背包问题是关于最佳化的问题,要解最佳化问题可以使用「动态规划」(Dynamic
programming),从空集合开始,每增加一个元素就先求出该阶段的最佳解,直到所有的元素加入至集合中,最后得到的就是最佳解。以背包问题为例,我们使用两个阵列value与item,value表示目前的最佳解所得之总价,item表示最后一个放至背包的水果,假设有负重量1~8的背包8个,并对每个背包求其最佳解。逐步将水果放入背包中,并求该阶段的最佳解,由最后一个表格,可以得知在背包负重8公斤时,最多可以装入9050元的水果,而最后一个装入的水果是3号,也就是草莓,装入了草莓,背包只能再放入7公斤(8-1)的水果,所以必须看背包负重7公斤时的最佳解,最后一个放入的是2号,也就是橘子,现在背包剩下负重量5公斤(7-2),所以看负重5公斤的最佳解,最后放入的是1号,也就是苹果,此时背包负重量剩下0公斤(5-5),
无法再放入水果,所以求出最佳解为放入草莓、橘子与苹果,而总价为9050元。
#define LIMIT 8 // 重量限制
#define N 5 // 物品种类
#define MIN 1 // 最小重量
struct body {
char name[20];
item 3 2 3 0 1 3 2 3
int main(void) {
int item[LIMIT+1] = {0};
int value[LIMIT+1] = {0};
int newvalue, i, s,
object a[] = {{"李子", 4, 4500},
{"苹果", 5, 5700},
{"橘子", 2, 2250},
{"草莓", 1, 1100},
{"甜瓜", 6, 6700}};
for(i = 0; i & N; i++) {
for(s = a[i]. s &= LIMIT; s++) {
p = s - a[i].
newvalue = value[p] + a[i].
if(newvalue & value[s]) {// 找到阶段最佳解
value[s] =
printf("物品\t价格\n");
for(i = LIMIT; i &= MIN; i = i - a[item[i]].size) {
printf("%s\t%d\n",
a[item[i]].name, a[item[i]].price);
printf("合计\t%d\n", value[LIMIT]);
class Fruit {
public Fruit(String name, int size, int price) {
this.name =
this.size =
this.price =
public String getName() {
public int getPrice() {
public int getSize() {
public class Knapsack {
public static void main(String[] args) {
final int MAX = 8;
final int MIN = 1;
int[] item = new int[MAX+1];
int[] value = new int[MAX+1];
Fruit fruits[] = {
new Fruit("李子", 4, 4500),
new Fruit("苹果", 5, 5700),
new Fruit("橘子", 2, 2250),
new Fruit("草莓", 1, 1100),
new Fruit("甜瓜", 6, 6700)};
for(int i = 0; i & fruits. i++) {
for(int s = fruits[i].getSize(); s &= MAX; s++) {
int p = s - fruits[i].getSize();
int newvalue = value[p] +
fruits[i].getPrice();
if(newvalue & value[s]) {// 找到阶段最佳解
value[s] =
System.out.println("物品\t价格");
for(int i = MAX;
i = i - fruits[item[i]].getSize()) {
System.out.println(fruits[item[i]].getName()+
"\t" + fruits[item[i]].getPrice());
System.out.println("合计\t" + value[MAX]);
【14.Algorithm Gossip: 蒙地卡罗法求 PI】
说明:蒙地卡罗为摩洛哥王国之首都,该国位于法国与义大利国境,以赌博闻名。蒙地卡罗的基本原理为以乱数配合面积公式来进行解题,这种以机率来解题的方式带有赌博的意味,虽然在精确度上有所疑虑,但其解题的思考方向却是个值得学习的方式。
解法:蒙地卡罗的解法适用于与面积有关的题目,例如求PI值或椭圆面积,这边介绍如何求PI值;假设有一个圆半径为1,所以四分之一圆面积就为PI,而包括此四分之一圆的正方形面积就为1,如下图所示:如果随意的在正方形中投射飞标(点)好了,则这些飞标(点)有些会落于四分之一圆内,假设所投射的飞标(点)有n点,在圆内的飞标(点)有c点,则依比例来算,就会得到上图中最后的公式。至于如何判断所产生的点落于圆内,很简单,令乱数产生X与Y两个数值,如果X^2+Y^2等于1就是落在圆内。
#define N 50000
int main(void) {
int i, sum = 0;
srand(time(NULL));
for(i = 1; i & N; i++) {
x = (double) rand() / RAND_MAX;
y = (double) rand() / RAND_MAX;
if((x * x + y * y) & 1)
printf("PI = %f\n", (double) 4 * sum / N);
【15.Algorithm Gossip: Eratosthenes 筛选求质数】
说明:除了自身之外,无法被其它整数整除的数称之为质数,要求质数很简单,但如何快速的求出质数则一直是程式设计人员与数学家努力的课题,在这边介绍一个着名的Eratosthenes求质数方法。
解法:首先知道这个问题可以使用回圈来求解,将一个指定的数除以所有小于它的数,若可以整除就不是质数,然而如何减少回圈的检查次数?如何求出小于N的所有质数?首先假设要检查的数是N好了,则事实上只要检查至N的开根号就可以了,道理很简单,假设A*B
N,如果A大于N的开根号,则事实上在小于A之前的检查就可以先检查到B这个数可以整除N。不过在程式中使用开根号会精确度的问题,所以可以使用i*i
N进行检查,且执行更快。从1开始筛选,进行到最后留下的数就都是质数,这就是Eratosthenes筛选方法(Eratosthenes
Method)。检查的次数还可以再减少,事实上,只要检查6n+1与6n+5就可以了,也就是直接跳过2与3的倍数,使得程式中的if的检查动作可以减少。
#define N 1000
int main(void) {
int prime[N+1];
for(i = 2; i &= N; i++)
prime[i] = 1;
for(i = 2; i*i &= N; i++) { // 这边可以改进
if(prime[i] == 1) {
for(j = 2*i; j &= N; j++) {
if(j % i == 0)
prime[j] = 0;
for(i = 2; i & N; i++) {
if(prime[i] == 1) {
printf("M ", i);
if(i % 16 == 0)
printf("\n");
printf("\n");
【16.Algorithm Gossip: 超长整数运算(大数运算)】
说明:基于记忆体的有效运用,程式语言中规定了各种不同的资料型态,也因此变数所可以表达的最大整数受到限制,例如456789这样的整数就不可能储存在long变数中(例如C/C++等),我们称这为long数,这边翻为超长整数(避免与资料型态的长整数翻译混淆),或俗称大数运算。
解法:一个变数无法表示超长整数,则就使用多个变数,当然这使用阵列最为方便,假设程式
语言的最大资料型态可以储存至65535的数好了,为了计算方便及符合使用十进位制的习惯,让
每一个阵列元素可以储存四个位数,也就是0到9999的数,例如:
很多人问到如何计算像50!这样的问题,解法就是使用程式中的乘法函式,至于要算到多大,就
看需求了。
由于使用阵列来储存数值,关于数值在运算时的加减乘除等各种运算、位数的进位或借位就必
须自行定义,加、减、乘都是由低位数开始运算,而除法则是由高位数开始运算,这边直接提
供加减乘除运算的函式供作参考,以下的N为阵列长度。
void add(int *a, int *b, int *c) {
int i, carry = 0;
for(i = N - 1; i &= 0; i--) {
c[i] = a[i] + b[i] +
if(c[i] & 10000)
carry = 0;
else { // 进位
c[i] = c[i] - 10000;
carry = 1;
void sub(int *a, int *b, int *c) {
int i, borrow = 0;
for(i = N - 1; i &= 0; i--) {
c[i] = a[i] - b[i] -
if(c[i] &= 0)
borrow = 0;
else { // 借位
c[i] = c[i] + 10000;
borrow = 1;
void mul(int *a, int b, int *c) { // b 为乘数
int i, tmp, carry = 0;
for(i = N - 1; i &=0; i--) {
tmp = a[i] * b +
c[i] = tmp % 10000;
carry = tmp / 10000;
void div(int *a, int b, int *c) { // b 为除数
int i, tmp, remain = 0;
for(i = 0; i & N; i++) {
tmp = a[i] +
c[i] = tmp /
remain = (tmp % b) * 10000;
【17.Algorithm Gossip: 长 PI】
说明圆周率后的小数位数是无止境的,如何使用电脑来计算这无止境的小数是一些数学家与程式设计师所感兴趣的,在这边介绍一个公式配合大数运算,可以计算指定位数的圆周率。
解法:首先介绍J.Marchin的圆周率公式:PI = [16/5 - 16 / (3*53) + 16 / (5*55) -
16 / (7*57) + ......] -& [4/239 - 4/(3*2393) +
4/(5*2395) - 4/(7*2397) + ......] 可以将这个公式整理为:PI = [16/5 - 4/239] -
[16/(53) - 4/(2393)]/3+ [16/(55) - 4/(2395)]/5 + ......
也就是说第n项,若为奇数则为正数,为偶数则为负数,而项数表示方式为:[16/52*n-1 - 4/2392*n-1] /
(2*n-1) 如果我们要计算圆周率至10的负L次方,由于[16/52*n-1 -
4/2392*n-1]中16/52*n-1比4/2392*n-1来的大,具有决定性,所以表示至少必须计算至第n项:[16/52*n-1
] / (2*n-1) = 10-L 将上面的等式取log并经过化简,我们可以求得:n = L / (2log5) = L /
1.39794 所以若要求精确度至小数后L位数,则只要求至公式的第n项,其中n等于:n = [L/1.39794] + 1
在上式中[]为高斯符号,也就是取至整数(不大于L/1.39794的整数);为了计简方便,可以在程式中使用下面这个公式来计简第n项:[Wn-1/52-
Vn-1 / (2392)] / (2*n-1) 这个公式的演算法配合大数运算函式的演算法为: div(w, 25, w);
div(v, 239, v); div(v, 239, v); sub(w, v, q); div(q, 2*k-1, q)
至于大数运算的演算法,请参考之前的文章,必须注意的是在输出时,由于是输出阵列中的整数值,如果阵列中整数位数不满四位,则必须补上0,在C语言中只要使用格式指定字d,使得不足位数部份自动补上0再输出,至于Java的部份,使用NumberFormat来作格式化。
#define L 1000
#define N L/4+1
// L 为位数,N是array长度
void add(int*, int*, int*);
void sub(int*, int*, int*);
void div(int*, int, int*);
int main(void) {
int s[N+3] = {0};
int w[N+3] = {0};
int v[N+3] = {0};
int q[N+3] = {0};
int n = (int)(L/1.39793 + 1);
w[0] = 16*5;
v[0] = 4*239;
for(k = 1; k &= k++) {
// 套用公式
div(w, 25, w);
div(v, 239, v);
div(v, 239, v);
sub(w, v, q);
div(q, 2*k-1, q);
if(k%2) // 奇数项
add(s, q, s);
else // 偶数项
sub(s, q, s);
printf("%d.", s[0]);
for(k = 1; k & N; k++)
printf("d", s[k]);
printf("\n");
void add(int *a, int *b, int *c) {
int i, carry = 0;
for(i = N+1; i &= 0; i--) {
c[i] = a[i] + b[i] +
if(c[i] & 10000)
carry = 0;
else { // 进位
c[i] = c[i] - 10000;
carry = 1;
void sub(int *a, int *b, int *c) {
int i, borrow = 0;
for(i = N+1; i &= 0; i--) {
c[i] = a[i] - b[i] -
if(c[i] &= 0)
borrow = 0;
else { // 借位
c[i] = c[i] + 10000;
borrow = 1;
void div(int *a, int b, int *c) { // b 为除数
int i, tmp, remain = 0;
for(i = 0; i &= N+1; i++) {
tmp = a[i] +
c[i] = tmp /
remain = (tmp % b) * 10000;
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 偶数数列 的文章

 

随机推荐