【算法】算法的艺术(三)
打印魔方阵
一个奇数阶(设为n阶)的方阵,将1、2、3……n2填入方阵中,使每行、每列数据之和都相等,这样的方阵便是魔方阵。
实例解析:
填写魔方阵有一个固定的方法:
(1)1总是位于第一行的中间。
(2)从2开始,下一个数总是位于上一个数的右上方的空格内,如5应该位于4的右上方。
(3)若右上方超出表格的右边界,则数字填入到第一列,行数不变。图16-1中的3和8都是这种情况。
若右上方超出上边界,则数字填入最后一行,列数不变。图16-1中2和9都是这种情况。
若按上面规则推算出来的位置已经有数字存在,则下一个数位于上一个数的下方空格内。例如:本来4应该填入3的右上方,但3的右上方已经有数字1存在了,则4填入3下方的空格内。
下面是程序代码:
#define N 19 int main() {int a[N][N] = {0}, i, j, k, n; /*数组元素全部初始化为0,表示没有数据填入*/ scanf("%d",&n); //输入方阵阶数(奇数) i = 0; //1的行数应该是0 j = n/2; //计算1应该在的列数 a[i][j] = 1; //将1填入表中 for(k = 2; k <= n*n; k++){ if(--i < 0) //行数减一,若超出上边界,填到最后一行 i += n; if(++j == n) //列数加一,若超出右边界,填到最左边一列 j -= n; if(a[i][j] != 0){ //若该位置已有数存在 if((i+=2) > n-1) //行数加2,若超过下边界 i -= n; //计算上一个数下方的行号 if((j-=1) < 0) //列数减1,若超过左边界 j += n; //计算上一个数下方的列号 } a[i][j] = k; //将k填入 } for(i = 0; i < n; i++) { for(j = 0; j < n; j++) printf("%4d", a[i][j]); printf("\n"); } getch(); return 0; }
猜数游戏
随机生成一个0~100之间的数,由用户猜,允许猜5次,每次猜大了或猜小了,都要给出提示。最后,无论猜对或猜错,都给出正确答案。
实例解析:
随机数的生成可利用实例11所介绍的知识。用户猜数可用循环,最多5次,若某次猜中则break。
#include <stdio.h> #include <stdlib.h> #include <time.h> int main() {int n, i, k; randomize(); n = random(101); for(i = 1; i <= 5; i++) { printf( “\n请输入一个数,您还有%d次机会: ”, 6-i ); scanf(“%d”, &k); if( k == n ) break; if(k > n) printf(“\n不对,大了!”); else printf(“\n不对,小了!”); } if(i <= 5) //因break而退出 printf(“\n恭喜您,猜对了! 答案正是%d\n”, n); else //循环自然退出 printf(“\n抱歉,没猜对! 正确答案是%d\n”, n); getch(); return 0; }
二维数组的排序输出
有10名学生,每个学生考试三门功课:数学、英语、计算机,键盘输入学号和成绩(学号和分数都是整数),按总分高低排序输出。
实例解析:
本题目需要完成输入、排序和输出。由于每个学生有5项数据:学号、三门成绩、总分,存储10个人的数据需要一个二维数组:
int a[11][5]; //第0行闲置不用
设计思路:
数组的第0列用来存学号,第1~3列存单科成绩,第4列存总分。
所有数据从键盘输入,用循环实现,循环的同时计算每个人的总分。
排序用选择法,与一维数组不同的是,这里的排序如果需要交换数据,那么交换的是两行的数据,而不是仅交换总分。
下面是程序代码:
# define NUMBER 10 int main() { int a[NUMBER+1][5], i, j, k, t, n; for(i = 1; i <= NUMBER; i++){ printf( "请输入第 %d个学生的数据: ", i ); scanf("%d,%d,%d,%d",&a[i][0],&a[i][1], &a[i][2],&a[i][3]); a[i][4] = a[i][1] + a[i][2] + a[i][3]; } for(i = 1; i < NUMBER; i++) { //选择法排序 k = i; for(j = i+1; j <= NUMBER; j++) if(a[j][4] > a[k][4]) k = j; for(n = 0; n <= 4; n++){ //交换两行的5对数据 t = a[i][n]; a[i][n] = a[k][n]; a[k][n] = t; } } printf(" 学号 数学 英语 计算机 总分 名次\n"); for(i = 1; i <= NUMBER; i++) for(j = 0; j <= 5; j++){ if(j != 5) printf(" %3d ", a[i][j] ); else printf(" %3d \n", i ); //输出名次 } getch(); return 0; }
寻找假币
80枚硬币中有一枚假币,假币比真币稍轻,请用天平称4次,将假币找出。
提示:
天平两边都可以放硬币
实例解析:
要用天平称4次找出假币,必须这样称:
(1) 将硬币分成三组:27、27、26,将前两组硬币分放在天平两侧称量,可以确定假币在某一组,假币范围缩小到27(26)个硬币中。
(2) 将假币所在组再分三组9、9、9(或9、9、8),前两组放天平上称量,又可以确定假币在哪一小组,假币范围缩小为9(8)个硬币之中。
(3) 继续分组3、3、3(或3、3、2),可确定假币在3(2)个中。
(4) 继续分组1、1、1(或1、1、0),便能找出假币。
上面描述的是用天平找出假币的方法,但计算机不是天平,要用计算机编程解这个题,就需要用程序来模拟天平称量的过程,故必须先建立数学模型。
天平称重,其实是比较两边硬币的总重量是否相等。为了能计算总重量,我们定义一个数组,每个元素存储一个硬币的重量,并使真币的重量为1,假币重量为0(只要比真币轻即可),然后按照上面的方法分组4次,称量4次。每次称完,都用k记录下假币所在小组中第一枚硬币的序号,以便下次分组从k开始。
下面为程序代码:
#include "time.h" #include "stdlib.h" int main() {int i, j, k, m, s1, s2, c[81]; for(i = 1; i <= 80; i++) //先将所有元素都置为1,都是真币 c[i] = 1; randomize(); c[random(80)+1] = 0; //随机设定一枚假币 k = 1; //存储假币所在组中第一枚硬币的序号 m = 27; //第一次称,每组27个硬币 for(i = 1; i <= 4; i++){ //循环4次,表示总共称4次 s1 = s2 = 0; //每次计算总重量前,都要先清零 for(j = k; j < m+k; j++){ //计算左右两组的硬币总重量 s1 += c[j]; s2 += c[j+m]; } if(s1 > s2) //假币在第2组 k += m; if(s1 == s2) //假币在第3组 k += 2*m; m /= 3; //继续分组,下次循环每组m/3个硬币 } printf("假币的序号是:%d\n", k); getch(); return 0; }
计算矩阵相乘
编程序计算矩阵相乘: A3×4×B4×2= C3×2
实例解析:
每个矩阵可用一个数组表示,数组c中每一项都是累加的结果,因此c数组中的数据必须先全部初始化为0。
c数组中的某一项c[i][j]的值由a的第i行和b的第j列相乘而得。即:c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] + a[i][2]*b[2][j] + a[i][3]*b[3][j],此式的计算可用循环实现:
for(k =0; k<=3; k++) c[i][j] += a[i][k] * b[k][j];
上面只是求得数组c中的一项,利用循环可求出所有的数据。
程序代码:
#define M 3 #define K 4 #define N 2 int main() {int a[M][K] = {3,9,12,10,1,8,6,7,5,4,2,11}; int b[K][N] = {5,8,2,1,7,3,6,4}, c[M][N] = {0}; int i, j, k; clrscr(); for(i = 0; i < M; i++){ for(j = 0; j < N; j++){ for(k = 0; k < K; k++) c[i][j] += a[i][k] * b[k][j]; printf(“%5d”, c[i][j]); } printf(“\n”); } getch(); return 0; }
向排好序的数组中插入数据
数组中已按从小到大顺序存有10个整数,键盘输入一个整数插入到数组中,插入后的数据还是按顺序排列的。
实例解析:
解法1:
要向排好序的数据中插入一个数据x,必须首先确定x应该插入到数组的何处,然后再行插入。
要确定x应插入到何处,需要将x依次与数组中的每个元素进行比较,若x小于某元素,则该元素的位置便是x应该插入的位置。这个过程可用下面代码实现。
for(i = 0; i <= 9; i++) if(x < a[i]) break;
循环结束后,i的值便是x插入后的序号,即a[i]应当存储x。
但是,此时还不能将x存入a[i],因为这样做就把a[i]原值覆盖了。正确的做法是,先把a[i]后移,然后再存入x。但是,如果a[i]后移到a[i+1],就把a[i+1]覆盖了,如何解决?
可以从数组最后一个数据开始向后移动,即先把最后一个后移,再把倒数第二个数据后移……
可用下面代码实现:
k = i; //用k记录下i的值,以便后面循环再用i作循环变量 for(i = 9; i >= k; i--) a[i+1] = a[i]; 完成这一步后,便可以把x插入到a[k]了: a[k] = x; 下面是完整的程序代码: #include <stdio.h> int main() {int a[11] = {-2,0,3,8,11,15,17,20,24,32}; int x, i, k; scanf(“%d”, &x); for(i = 0; i <= 9; i++) if(x < a[i]) break; k = i; for(i = 9; i >= k; i--) /*自第k个数据之后的所有数据后移*/ a[i+1] = a[i]; a[k] = x; //将x插入 for(i = 0; i <= 10; i++) printf(“%5d”, a[i]); getch(); return 0; }
解法2:
将数组的元素从最后一个开始依次与x比较,若数组元素大于x,则后移,直到遇到一个不大于x的元素或所有元素都比较完了为止。
for(i = 9; i >= 0 && a[i] > x; i--) a[i + 1] = a[i];
当循环结束时,存在两种情况:
(1)遇到一个元素,使得a[i]不大于x,此时,x应插入到a[i+1]。
(2)所有元素都比较完了,使得x<0退出循环,x应插入到a[0],亦即a[i+1]。
两种情况都可以用a[i+1] = x; 来完成插入。
解法二的主要代码为:
for(i = 9; i >= 0 && a[i] > x; i--) a[i+1] = a[i]; a[i+1] = x;
本文出自 “成鹏致远” 博客,请务必保留此出处http://infohacker.blog.51cto.com/6751239/1171342