Java经典算法大全

部署运行你感兴趣的模型镜像

1.河内之塔..
2.Algorithm Gossip: 费式数列.
3. 巴斯卡三角形
4.Algorithm Gossip: 三色棋
5.Algorithm Gossip: 老鼠走迷官(一)
6.Algorithm Gossip: 老鼠走迷官(二)
7.Algorithm Gossip: 骑士走棋盘
8.Algorithm Gossip: 八皇后
9.Algorithm Gossip: 八枚银币.
10.Algorithm Gossip: 生命游戏.
11.Algorithm Gossip: 字串核对
12.Algorithm Gossip: 双色、三色河内塔
13.Algorithm Gossip: 背包问题(Knapsack Problem
14.Algorithm Gossip: 蒙地卡罗法求 PI
15.Algorithm Gossip: Eratosthenes 筛选求质数
16.Algorithm Gossip: 超长整数运算(大数运算).
17.Algorithm Gossip: 长 PI.
18.Algorithm Gossip: 最大公因数、最小公倍数、因式分解
19.Algorithm Gossip: 完美数…
20.Algorithm Gossip: 阿姆斯壮数.
21.Algorithm Gossip: 最大访客数….
22.Algorithm Gossip: 中序式转后序式(前序式)…
23.Algorithm Gossip: 后序式的运算.
24.Algorithm Gossip: 洗扑克牌(乱数排列)
25.Algorithm Gossip: Craps 赌博游戏.
26.Algorithm Gossip: 约瑟夫问题(Josephus Problem)
27.Algorithm Gossip: 排列组合.
28.Algorithm Gossip: 格雷码(Gray Code
29.Algorithm Gossip: 产生可能的集合
30.Algorithm Gossip: m 元素集合的 n 个元素子集
31.Algorithm Gossip: 数字拆解
32.Algorithm Gossip: 得分排行
33.Algorithm Gossip: 选择、插入、气泡排序
34.Algorithm Gossip: Shell 排序法 - 改良的插入排序
35.Algorithm Gossip: Shaker 排序法 - 改良的气泡排序
36.排序法 - 改良的选择
37.Algorithm Gossip:速排序法(一)
38.Algorithm Gossip: 快速排序法(二)
39.Algorithm Gossip: 快速排序法(三)
40.Algorithm Gossip: 合并排序法
41.Algorithm Gossip: 基数排序法.
42.Algorithm Gossip: 循序搜寻法(使用卫兵)
43.Algorithm Gossip: 二分搜寻法(搜寻原则的代表)
44.Algorithm Gossip: 插补搜寻法.
45.Algorithm Gossip: 费氏搜寻法.
46.Algorithm Gossip: 稀疏矩阵
47.Algorithm Gossip: 多维矩阵转一维矩阵
48.Algorithm Gossip: 上三角、下三角、对称矩阵
49.Algorithm Gossip: 奇数魔方阵
50.Algorithm Gossip: 4N 魔方阵.
51.Algorithm Gossip: 2(2N+1)
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时,则
64
如果对这数字没什幺概念,就假设每秒钟搬一个盘子好了,也要约5850亿年左右。

include

include

include

define N 20

int main(void) {
int Fib[N] = {0};
int i;
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”);
return 0;
}

  1. 巴斯卡三角形

include

define N 12

long combi(int n, int r){
int i;
long p = 1;
for(i = 1; i <= r; i++)
p = p * (n-i+1) / i;
return p;
}
void paint() {
int n, r, t;
for(n = 0; n <= N; n++) {
for(r = 0; r <= n; r++) {
int i;/* 排版设定开始 */
if(r == 0) {
for(i = 0; i <= (N-n); i++)

}else {

printf(“

“);

printf(“

“);

} /* 排版设定结束 */
printf(“%3d”, 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的索引数时,表示接下来的旗
子就都是红色了,此时就可以结束移动,如下所示:

include

include

include

define BLUE ‘b’

define WHITE ‘w’

define RED ‘r’

define SWAP(x, y) { char temp; \

temp = color[x]; \
color[x] = color[y]; \
color[y] = temp; }
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;
int i;
for(i = 0; i < strlen(color); i++)
printf(“%c “, color[i]);
printf(“\n”);
while(wFlag <= rFlag) {
if(color[wFlag] == WHITE)
wFlag++;
else if(color[wFlag] == BLUE) {
SWAP(bFlag, wFlag);
bFlag++; wFlag++;
}
else {
while(wFlag < rFlag && color[rFlag] == RED)
rFlag–;
SWAP(rFlag, wFlag);
rFlag–;
}
}
for(i = 0; i < strlen(color); i++)
printf(“%c “, color[i]);
printf(“\n”);
return 0;
}
5.Algorithm Gossip: 老鼠走迷官(一)
说明老鼠走迷宫是递回求解的基本题型,我们在二维阵列中使用2表示迷宫墙壁,使用1来表
示老鼠的行走路径,试以程式求出由入口至出口的路径。
解法老鼠的走法有上、左、下、右四个方向,在每前进一格之后就选一个方向前进,无法前
进时退回选择下一个可前进方向,如此在阵列中依序测试四个方向,直到走到出口为止,这是
递回的基本题,请直接看程式应就可以理解。

include

include

include

include

include

include

include

define N 8

int column[N+1]; // 同栏是否有皇后,1表示有
int rup[2*N+1]; // 右上至左下是否有皇后
int lup[2*N+1]; // 左上至右下是否有皇后
int queen[N+1] = {0};
int num; // 解答编号
void backtrack(int); // 递回求解
int main(void) {
int i;
num = 0;
for(i = 1; i <= N; i++)
column[i] = 1;
for(i = 1; i <= 2*N; i++)
rup[i] = lup[i] = 1;
backtrack(1);
return 0;
}
void showAnswer() {
int x, y;
printf(“\n解答 %d\n”, ++num);
for(y = 1; y <= N; y++) {
for(x = 1; x <= N; x++) {
if(queen[y] == x) {
printf(” Q”);
}
else {
printf(” .”);
}
}
printf(“\n”);
}
}
void backtrack(int i) {
int j;
if(i > N) {
showAnswer();
}
else {
for(j = 1; j <= N; j++) {
if(column[j] == 1 &&
rup[i+j] == 1 && lup[i-j+N] == 1) {
queen[i] = j;
// 设定为占用
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假币的重量比真币轻。

include

include

include

include

include

include

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() {
int row, col;
char ans;
init();
while(1) {
outputMap();
for(row = 0; row < MAXROW; row++) {
for(col = 0; col < MAXCOL; col++) {
switch (neighbors(row, col)) {
case 0:
case 1:
case 4:
case 5:
case 6:
case 7:
case 8:
newmap[row][col] = DEAD;
break;
case 2:
newmap[row][col] = map[row][col];
break;
case 3:
newmap[row][col] = ALIVE;
break;
}
}
}
copyMap();
printf(“\nContinue next Generation ? “);
getchar();
ans = toupper(getchar());

}

if(ans != ‘Y’)

break;
return 0;
}
void init() {
int row, col;
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)
break;
else
printf(“(x, y) exceeds map ranage!”);
}
}
int neighbors(int row, int col) {
int count = 0, c, r;
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)
continue;
if(map[r][c] == ALIVE)
count++;
}
if(map[row][col] == ALIVE)
count–;
return count;
}
void outputMap() {
int row, col;
printf(“\n\n%20cGame of life cell status\n”);
for(row = 0; row < MAXROW; row++) {
printf(“\n%20c”, ’ ‘);
for(col = 0; col < MAXCOL; col++)

if(map[row][col] == ALIVE)

putchar(‘#’);

else

putchar(‘-‘);

}
}
void copyMap() {
int row, col;
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。

include

include

include

include

include

include

include

define LIMIT 8

define N 5

define MIN 1

struct body {
char name[20];
int size;
int price;
};

// 重量限制
// 物品种类
// 最小重量
typedef struct body object;
int main(void) {
int item[LIMIT+1] = {0};
int value[LIMIT+1] = {0};
int newvalue, i, s, p;
object a[] = {{“李子”, 4, 4500},
{“苹果”, 5, 5700},
{“橘子”, 2, 2250},
{“草莓”, 1, 1100},
{“甜瓜”, 6, 6700}};
for(i = 0; i < N; i++) {
for(s = a[i].size; s <= LIMIT; s++) {
p = s - a[i].size;
newvalue = value[p] + a[i].price;
if(newvalue > value[s]) {// 找到阶段最佳解
value[s] = newvalue;
item[s] = i;
}
}
}
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]);
return 0;
}
Java
class Fruit {
private String name;
private int size;
private int price;
public Fruit(String name, int size, int price) {
this.name = name;
this.size = size;
this.price = price;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
public int getSize() {
return size;
}
}
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.length; 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] = newvalue;
item[s] = i;
}
}
}
System.out.println(“物品\t价格”);
for(int i = MAX;
i >= MIN;
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
就是落在圆内。

include

include

include

define N 50000

int main(void) {
int i, sum = 0;
double x, y;
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)
sum++;
}
printf(“PI = %f\n”, (double) 4 * sum / N);
return 0;
}
15.Algorithm Gossip: Eratosthenes 筛选求质数
说明除了自身之外,无法被其它整数整除的数称之为质数,要求质数很简单,但如何快速的
求出质数则一直是程式设计人员与数学家努力的课题,在这边介绍一个着名的 Eratosthenes求质
数方法。
解法首先知道这个问题可以使用回圈来求解,将一个指定的数除以所有小于它的数,若可以
整除就不是质数,然而如何减少回圈的检查次数?如何求出小于N的所有质数?
首先假设要检查的数是N好了,则事实上只要检查至N的开根号就可以了,道理很简单,假设
A*B = N,如果A大于N的开根号,则事实上在小于A之前的检查就可以先检查到B这个数可以整
除N。不过在程式中使用开根号会精确度的问题,所以可以使用 i*i <= N进行检查,且执行更快 。
再来假设有一个筛子存放1~N,例如:

2

3 4 5

6 7

8

9 10

11 12

13 14 15

16 17

18 19

20 21 …….. N

先将2的倍数筛去:

2

3 5 7

9 11 13 15

17 19 21 …….. N

再将3的倍数筛去:

2

3 5 7

11 13

17 19 …….. N

再来将5的倍数筛去,再来将7的质数筛去,再来将11的倍数筛去……..,如此进行到最后留下的
数就都是质数,这就是Eratosthenes筛选方法(Eratosthenes Sieve Method)。
检查的次数还可以再减少,事实上,只要检查6n+1与6n+5就可以了,也就是直接跳过2与3的倍
数,使得程式中的if的检查动作可以减少。
实作
C

include

include

define N 1000

int main(void) {
int i, j;
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(“%4d “, i);
if(i % 16 == 0)
printf(“\n”);
}
}
printf(“\n”);
return 0;
}
16.Algorithm Gossip: 超长整数运算(大数运算)
说明基于记忆体的有效运用,程式语言中规定了各种不同的资料型态,也因此变数所可以表
达的最大整数受到限制,例如123456789123456789这样的 整数就不可能储存在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] + carry;
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] - borrow;
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 + carry;
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] + remain;
c[i] = tmp / b;
remain = (tmp % b) * 10000;
}
}
17.Algorithm Gossip: 长 PI
说明圆周率后的小数位数是无止境的,如何使用电脑来计算这无止境的小数是一些数学家与
程式设计师所感兴趣的,在这边介绍一个公式配合 大数运算,可以计算指定位数的圆周率。
解法首先介绍J.Marchin的圆周率公式:
3 5 7
3 5 7
可以将这个公式整理为:
3 3 5 5
也就是说第n项,若为奇数则为正数,为偶数则为负数,而项数表示方式为:

[16/5

2*n-1

2*n-1

2*n-1

2*n-1 2*n-1

2*n-1

来的

大,具有决定性,所以表示至少必须计算至第n项:

[16/5

2*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项:
2 2
这个公式的演算法配合大数运算函式的演算法为: div(w, 25, w);
div(v, 239, v);
div(v, 239, v);
sub(w, v, q);
div(q, 2*k-1, q)
至于大数运算的演算法,请参考之前的文章,必须注意的是在输出时,由于是输出阵列中的整
数值,如果阵列中整数位数不满四位,则必须补上0,在C语言中只要 使用格式指定字%04d,
使得不足位数部份自动补上0再输出,至于Java的部份,使用 NumberFormat来作格式化。

include

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);
int k;
w[0] = 16*5;
v[0] = 4*239;
for(k = 1; k <= n; 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(“%04d”, s[k]);
printf(“\n”);
return 0;
}
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] + carry;
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] - borrow;
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] + remain;
c[i] = tmp / b;
remain = (tmp % b) * 10000;
}
}
18.Algorithm Gossip: 最大公因数、最小公倍数、因式分解
说明最大公因数使用辗转相除法来求,最小公倍数则由这个公式来求:
GCD * LCM = 两数乘积
解法最大公因数可以使用递回与非递回求解,因式分解基本上就是使用小于输入数的数值当
作除数,去除以输入数值,如果可以整除就视为因数,要比较快的解法就是求出小于该数的所
有质数,并试试看是不是可以整除,求质数的问题是另一个课题,请参考 Eratosthenes 筛选求
质数。
实作(最大公因数、最小公倍数)

include

include

include

include

include

include

define N 1000

int prime(int*); // 求质数表
void factor(int*, int); // 求factor
int main(void) {
int ptable[N+1] = {0};
int count, i, temp;
count = prime(ptable);
printf(“请输入一数:”);
scanf(“%d”, &temp);
factor(ptable, temp);
printf(“\n”);
return 0;
}
int prime(int* pNum) {
int i, j;
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, j = 0; i < N; i++) {
if(prime[i] == 1)
pNum[j++] = i;
}
return j;
}
void factor(int* table, int num) {
int i;
for(i = 0; table[i] * table[i] <= num;) {
if(num % table[i] == 0) {
printf(“%d * “, table[i]);
num /= table[i];
}
else
i++;
}
printf(“%d\n”, num);
}
19.Algorithm Gossip: 完美数
说明如果有一数n,其真因数(Proper factor)的总和等于n,则称之为完美数(Perfect Number),
例如以下几个数都是完美数:
6 = 1 + 2 + 3
28 = 1 + 2 + 4 + 7 + 14
496 = 1 + 2 + 4 + 8 + 16 + 31 + 62 + 124 + 248
程式基本上不难,第一眼看到时会想到使用回圈求出所有真因数,再进一步求因数和,不过若n
值很大,则此法会花费许多时间在回圈测试上,十分没有效率,例如求小于10000的所有完美数 。
解法如何求小于10000的所有完美数?并将程式写的有效率?基本上有三个步骤:
求出一定数目的质数表
利用质数表求指定数的因式分解
利用因式分解求所有真因数和,并检查是否为完美数
步骤一 与 步骤二 在之前讨论过了,问题在步骤三,如何求真因数和?方法很简单,要先知道
将所有真因数和加上该数本身,会等于该数的两倍,例如:
2 * 28 = 1 + 2 + 4 + 7 + 14 + 28
等式后面可以化为:
0 1 2 0 1
所以只要求出因式分解,就可以利用回圈求得等式后面的值,将该值除以2就是真因数和了;等
式后面第一眼看时可能想到使用等比级数公式来解,不过会使用到次方运算,可以在回圈走访
因式分解阵列时,同时计算出等式后面的值,这在下面的实作中可以看到。

include

include

define N 1000

define P 10000

int prime(int*); // 求质数表
int factor(int*, int, int*); // 求factor
int fsum(int*, int); // sum ot proper factor
int main(void) {
int ptable[N+1] = {0}; // 储存质数表

int fact[N+1] = {0};

// 储存因式分解结果

int count1, count2, i;
count1 = prime(ptable);
for(i = 0; i <= P; i++) {
count2 = factor(ptable, i, fact);
if(i == fsum(fact, count2))
printf(“Perfect Number: %d\n”, i);
}
printf(“\n”);
return 0;
}
int prime(int* pNum) {
int i, j;
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, j = 0; i < N; i++) {
if(prime[i] == 1)
pNum[j++] = i;
}
return j;
}
int factor(int* table, int num, int* frecord) {
int i, k;
for(i = 0, k = 0; table[i] * table[i] <= num;) {
if(num % table[i] == 0) {
frecord[k] = table[i];
k++;
num /= table[i];
}
else
i++;
}
frecord[k] = num;
return k+1;
}
int fsum(int* farr, int c) {
int i, r, s, q;
i = 0;
r = 1;
s = 1;
q = 1;
while(i < c) {
do {
r *= farr[i];
q += r;
i++;
} while(i < c-1 && farr[i-1] == farr[i]);
s *= q;
r = 1;
q = 1;
}
return s / 2;
}
20.Algorithm Gossip: 阿姆斯壮数
说明
3 3 3
程式找出所有的三位数Armstrong数。
解法
Armstrong数的寻找,其实就是在问如何将一个数字分解为个位数、十位数、百位数……,这只
要使用除法与余数运算就可以了,例如输入 input为abc,则:
a = input / 100
b = (input%100) / 10
c = input % 10

include

include

include

include

include

define MAX 100

define SWAP(x,y) {int t; t = x; x = y; y = t;}

int partition(int[], int, int);
void quicksort(int[], int, int); // 快速排序法
int maxguest(int[], int[], int, int);
int main(void) {
int x[MAX] = {0};
int y[MAX] = {0};
int time = 0;
int count = 0;
printf(“\n输入来访与离开125;时间(0~24):”);
printf(“\n范例:10 15”);
printf(“\n输入-1 -1结束”);
while(count < MAX) {
printf(“\n>>”);
scanf(“%d %d”, &x[count], &y[count]);
if(x[count] < 0)
break;
count++;
}
if(count >= MAX) {
printf(“\n超出最大访客数(%d)”, MAX);
count–;
}
// 预先排序
quicksort(x, 0, count);
quicksort(y, 0, count);
while(time < 25) {
printf(“\n%d 时的最大访客数:%d”,
time, maxguest(x, y, count, time));
time++;
}
printf(“\n”);
return 0;
}
int maxguest(int x[], int y[], int count, int time) {
int i, num = 0;
for(i = 0; i <= count; i++) {
if(time > x[i])
num++;
if(time > y[i])
num–;
}
return num;
}
int partition(int number[], int left, int right) {
int i, j, s;
s = number[right];
i = left - 1;
for(j = left; j < right; j++) {
if(number[j] <= s) {
i++;
SWAP(number[i], number[j]);
}
}
SWAP(number[i+1], number[right]);
return i+1;
}
void quicksort(int number[], int left, int right) {
int q;
if(left < right) {
q = partition(number, left, right);
quicksort(number, left, q-1);
quicksort(number, q+1, right);
}
}
22.Algorithm Gossip: 中序式转后序式(前序式)
说明平常所使用的运算式,主要是将运算元放在运算子的两旁,例如a+b/d这样的式子,这称
之为中序(Infix)表示式,对于人类来说,这样的式子很容易理 解,但由于电脑执行指令时是
有顺序的,遇到中序表示式时,无法直接进行运算,而必须进一步判断运算的先后顺序,所以
必须将中序表示式转换为另一种表示方 法。
可以将中序表示式转换为后序(Postfix)表示式,后序表示式又称之为逆向波兰表示式(Reverse
polish notation),它是由波兰的数学家卢卡谢维奇提出,例如(a+b)*(c+d)这个式子,表示为后序
表示式时是ab+cd+*。
解法用手算的方式来计算后序式相当的简单,将运算子两旁的运算元依先后顺序全括号起来,
然后将所有的右括号取代为左边最接近的运算子(从最内层括号开始),最后去掉所有的左括号
就可以完成后序表示式,例如:

a+b*d+c/d

=> ((a+(b*d))+(c/d)) -> bd*+cd/+

如果要用程式来进行中序转后序,则必须使用堆叠,演算法很简单,直接叙述的话就是使用回
圈,取出中序式的字元,遇运算元直接输出,堆叠运算子与左括号, ISP>ICP的话直接输出堆
叠中的运算子,遇右括号输出堆叠中的运算子至左括号。

例 如 (a+b)*(c+d)
这个式子,依演算
法的输出过程如
下: OP
(
a
+
b
)
*
(
c
+
d

)

STACK
(
(
(+

(+

*
*(
*(
*(+
*(+

*

OUTPUT

a
a
ab
ab+
ab+
ab+
ab+c
ab+c
ab+cd
ab+cd+
ab+cd+*

如果要将中序式转为前序式,则在读取中序式时是由后往前读取,而左右括号的处理方式相反,
其余不变,但输出之前必须先置入堆叠,待转换完成后再将堆叠中的 值由上往下读出,如此就
是前序表示式。
实作
C

include

include

include

include

include

include

include

define N 52

int main(void) {
int poker[N + 1];
int i, j, tmp, remain;
// 初始化阵列
for(i = 1; i <= N; i++)
poker[i] = i;
srand(time(0));
// 洗牌
for(i = 1; i <= N; i++) {
j = rand() % 52 + 1;
tmp = poker[i];
poker[i] = poker[j];
poker[j] = tmp;
}
for(i = 1; i <= N; i++) {
// 判断花色
switch((poker[i]-1) / 13) {
case 0:
printf(“桃”); break;
case 1:
printf(“心”); break;
case 2:
printf(“砖”); break;
case 3:
printf(“梅”); break;
}
// 扑克牌数字
remain = poker[i] % 13;
switch(remain) {
case 0:
printf(“K “); break;
case 12:
printf(“Q “); break;
case 11:
printf(“J “); break;
default:
printf(“%d “, remain); break;
}
if(i % 13 == 0)
printf(“\n”);
}
return 0;
}
25.Algorithm Gossip: Craps 赌博游戏
说明一个简单的赌博游戏,游戏规则如下:玩家掷两个骰子,点数为1到6,如果第一次点数
和为7或11,则玩家胜,如果点数和为2、3或12,则玩家输,如果和 为其它点数,则记录第一
次的点数和,然后继续掷骰,直至点数和等于第一次掷出的点数和,则玩家胜,如果在这之前
掷出了点数和为7,则玩家输。

解法

规则看来有些复杂,但是其实只要使用switch配合if条件判断来撰写即可,小心不要弄

错胜负顺序即可。

include

include

include

define WON 0

define LOST 1

define CONTINUE 2

int rollDice() {
return (rand() % 6) + (rand() % 6) + 2;
}
int main(void) {
int firstRoll = 1;
int gameStatus = CONTINUE;
int die1, die2, sumOfDice;
int firstPoint = 0;
char c;
srand(time(0));
printf(“Craps赌博游戏,按Enter键开始游戏**“);
while(1) {
getchar();
if(firstRoll) {
sumOfDice = rollDice();
printf(“\n玩家掷出点数和:%d\n”, sumOfDice);
switch(sumOfDice) {
case 7: case 11:
gameStatus = WON; break;
case 2: case 3: case 12:
gameStatus = LOST; break;
default:
firstRoll = 0;
gameStatus = CONTINUE;
firstPoint = sumOfDice;
break;
}
}
else {
sumOfDice = rollDice();
printf(“\n玩家掷出点数和:%d\n”, sumOfDice);
if(sumOfDice == firstPoint)
gameStatus = WON;
else if(sumOfDice == 7)
gameStatus = LOST;
}
if(gameStatus == CONTINUE)
puts(“未分胜负,再掷一次**\n”);
else {
if(gameStatus == WON)
puts(“玩家胜”);
else
puts(“玩家输”);
printf(“再玩一次?”);
scanf(“%c”, &c);
if(c == ‘n’) {
puts(“游戏结束”);
break;
}
firstRoll = 1;
}
}
return 0;
}
26.Algorithm Gossip: 约瑟夫问题(Josephus Problem)

说明据说着名犹太历史学家

Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹

太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人到,于是决定了
一个自杀方式,41个人排成一个圆圈,由第1个人 开始报数,每报数到第3人该人就必须自杀,
然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排
在第16个与第31个位置,于是逃过了这场死亡游戏。
解法约瑟夫问题可用代数分析来求解,将这个问题扩大好了,假设现在您与m个朋友不幸参
与了这个游戏,您要如何保护您与您的朋友?只要画两个圆圈就可以让自己与朋友免于死亡游
戏,这两个圆圈内圈是排列顺序,而外圈是自杀顺序,如下图所示:
使用程式来求解的话,只要将阵列当作环状来处理就可以了,在阵列中由计数1开始,每找到三
个无资料区就填入一个计数,直而计数达41为止,然后将阵列由索引1开始列出,就可以得知每
个位置的自杀顺序,这就是约瑟夫排列,41个人而报数3的约琴夫排列如下所示:
14 36 1 38 15 2 24 30 3 16 34 4 25 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12
22 33 13 29 23
由上可知,最后一个自杀的是在第31个位置,而倒数第二个自杀的要排在第16个位置,之前的
人都死光了,所以他们也就不知道约琴夫与他的朋友并没有遵守游戏规则了。

include

include

define N 41

define M 3

int main(void) {
int man[N] = {0};
int count = 1;
int i = 0, pos = -1;
int alive = 0;
while(count <= N) {
do {
pos = (pos+1) % N; // 环状处理
if(man[pos] == 0)
i++;
if(i == M) { // 报数为3了
i = 0;
break;
}
} while(1);
man[pos] = count;
count++;
}
printf(“\n约琴夫排列:”);
for(i = 0; i < N; i++)
printf(“%d “, man[i]);
printf(“\n\n您想要救多少人?”);
scanf(“%d”, &alive);
printf(“\nL表示这%d人要放的位置:\n”, alive);
for(i = 0; i < N; i++) {

if(man[i] > alive)

printf(“D”);

else

printf(“L”);

if((i+1) % 5 == 0) printf(“

“);

}
printf(“\n”);
return 0; }
27.Algorithm Gossip: 排列组合
说明将一组数字、字母或符号进行排列,以得到不同的组合顺序,例如1 2 3这三个数的排列
组合有:1 2 3、1 3 2、2 1 3、2 3 1、3 1 2、3 2 1。
解法可以使用递回将问题切割为较小的单元进行排列组合,例如1 2 3 4的排列可以分为1 [2 3
4]、2 [1 3 4]、3 [1 2 4]、4 [1 2 3]进行排列,这边利用旋转法,先将旋转间隔设为0,将最右边的
数字旋转至最左边,并逐步增加旋转的间隔,例如:
1 2 3 4 -> 旋转1 -> 继续将右边2 3 4进行递回处理
2 1 3 4 -> 旋转1 2 变为 2 1-> 继续将右边1 3 4进行递回处理
3 1 2 4 -> 旋转1 2 3变为 3 1 2 -> 继续将右边1 2 4进行递回处理
4 1 2 3 -> 旋转1 2 3 4变为4 1 2 3 -> 继续将右边1 2 3进行递回处理

include

include

define N 4

void perm(int*, int);
int main(void) {
int num[N+1], i;
for(i = 1; i <= N; i++)
num[i] = i;
perm(num, 1);
return 0;
}
void perm(int* num, int i) {
int j, k, tmp;
if(i < N) {
for(j = i; j <= N; j++) {
tmp = num[j];
// 旋转该区段最右边数字至最左边
for(k = j; k > i; k–)
num[k] = num[k-1];
num[i] = tmp;
perm(num, i+1);
// 还原
for(k = i; k < j; k++)
num[k] = num[k+1];
num[j] = tmp;
}
}
else { // 显示此次排列
for(j = 1; j <= N; j++)
printf(“%d “, num[j]);
printf(“\n”);
}
}
28.Algorithm Gossip: 格雷码(Gray Code)
说明
Gray Code是一个数列集合,每个数使用二进位来表示,假设使用n位元来表示每个数好了,任
两个数之间只有一个位元值不同,例如以下为3位元的Gray Code:
000 001 011 010 110 111 101 100
由定义可以知道,Gray Code的顺序并不是唯一的,例如将上面的数列反过来写,也是一组Gray
Code:
100 101 111 110 010 011 001 000
Gray Code是由贝尔实验室的Frank Gray在1940年代提出的,用来在使用PCM(Pusle Code
Modulation)方法传送讯号时避免出错,并于1953年三月十七日取得美国专利。
解法
由于Gray Code相邻两数之间只改变一个位元,所以可观 察Gray Code从1变0或从0变1时的
位置,假设有4位元的Gray Code如下:
0000 0001 0011 0010 0110 0111 0101 0100
1100 1101 1111 1110 1010 1011 1001 1000
观察奇数项的变化时,我们发现无论它是第几个Gray Code,永远只改变最右边的位元,如果
是1就改为0,如果是0就改为1。
观察偶数项的变化时,我们发现所改变的位元,是由右边算来第一个1的左边位元。
以上两个变化规则是固定的,无论位元数为何;所以只要判断位元的位置是奇数还是偶数,就
可以决定要改变哪一个位元的值,为了程式撰写方便,将阵列索引 0当作最右边的值,而在列
印结果时,是由索引数字大的开始反向列印。
将2位元的Gray Code当作平面座标来看,可以构成一个四边形,您可以发现从任一顶点出发,
绕四边形周长绕一圈,所经过的顶点座标就是一组 Gray Code,所以您可以得到四组 Gray
Code。
同样的将3位元的Gray Code当作平面座标来看的话,可以构成一个正立方体,如果您可以从任
一顶点出发,将所有的边长走过,并不重复经过顶点的话,所经过的顶点座标顺序之组合也就
是一组Gray Code。

include

include

define MAXBIT 20

define TRUE 1

define CHANGE_BIT(x) x = ((x) == ‘0’ ? ‘1’ : ‘0’)

define NEXT(x) x = (1 - (x))

int main(void) {
char digit[MAXBIT];
int i, bits, odd;
printf(“输入位元数:”);
scanf(“%d”, &bits);
for(i = 0; i < bits; i++) {
digit[i] = ‘0’;
printf(“0”);
}
printf(“\n”);
odd = TRUE;
while(1) {
if(odd)
CHANGE_BIT(digit[0]);
else {
// 计算第一个1的位置
for(i = 0; i < bits && digit[i] == ‘0’; i++) ;
if(i == bits - 1) // 最后一个Gray Code
break;
CHANGE_BIT(digit[i+1]);
}
for(i = bits - 1; i >= 0; i–)
printf(“%c”, digit[i]);
printf(“\n”);
NEXT(odd);
}
return 0;
}
29.Algorithm Gossip: 产生可能的集合
说明
给定一组数字或符号,产生所有可能的集合(包括空集合), 例如给定1 2 3,则可能的集合为:
{}、{1}、{1,2}、{1,2,3}、{1,3}、{2}、{2,3}、{3}。
解法
如果不考虑字典顺序,则有个简单的方法可以产生所有的集合,思考二进位数字加法,并注意
1出现的位置,如果每个位置都对应一个数字,则由1所对应的数字所产生的就是一个集合,例
如:

000
001
010
011
100
101
110
111

{}
{3}
{2}
{2,3}
{1}
{1,3}
{1,2}
{1,2,3}

了解这个方法之后,剩下的就是如何产生二进位数?有许多方法可以使用,您可以使用unsigned
型别加上&位元运算来产生,这边则是使用阵列搜 寻,首先阵列内容全为0,找第一个1,在还
没找到之前将走访过的内容变为0,而第一个找到的0则变为 1,如此重复直到所有的阵列元素
都变为1为止,例如:
000 => 100 => 010 => 110 => 001 => 101 => 011 => 111
如果要产生字典顺序,例如若有4个元素,则:
{} => {1} => {1,2} => {1,2,3} => {1,2,3,4} =>
{1,2,4} =>
{1,3} => {1,3,4} =>
{1,4} =>
{2} => {2,3} => {2,3,4} =>
{2,4} =>
{3} => {3,4} =>
{4}
简单的说,如果有n个元素要产生可能的集合,当依序产生集合时,如果最后一个元素是n,而
倒数第二个元素是m的话,例如:
{a b c d e n}
则下一个集合就是{a b c d e+1},再依序加入后续的元素。
例如有四个元素,而当产生{1 2 3 4}集合时,则下一个集合就是{1 2 3+1},也就是{1 2 4},由于
最后一个元素还是4,所以下一个集合就是{1 2+1},也就是{1 3},接下来再加入后续元素4,也
就是{1 3 4},由于又遇到元素4,所以下一个集合是{1 3+1},也就是{1 4}。
实作
C(无字典顺序)

include

include

define MAXSIZE 20

int main(void) {
char digit[MAXSIZE];
int i, j;
int n;
printf(“输入集合个数:”);
scanf(“%d”, &n);
for(i = 0; i < n; i++)
digit[i] = ‘0’;
printf(“\n{}”); // 空集合
while(1) {
// 找第一个0,并将找到前所经过的元素变为0
for(i = 0; i < n && digit[i] == ‘1’; digit[i] = ‘0’, i++);
if(i == n) // 找不到0
break;

else

// 将第一个找到的0变为1
digit[i] = ‘1’;
// 找第一个1,并记录对应位置
for(i = 0; i < n && digit[i] == ‘0’; i++);
printf(“\n{%d”, i+1);
for(j = i + 1; j < n; j++)
if(digit[j] == ‘1’)
printf(“,%d”, j + 1);
printf(“}”);
}
printf(“\n”);
return 0;
}
C(字典顺序)

include

include

define MAXSIZE 20

int main(void) {
int set[MAXSIZE];
int i, n, position = 0;
printf(“输入集合个数:”);
scanf(“%d”, &n);
printf(“\n{}”);
set[position] = 1;
while(1) {
printf(“\n{%d”, set[0]); // 印第一个数
for(i = 1; i <= position; i++)
printf(“,%d”, set[i]);
printf(“}”);
if(set[position] < n) { // 递增集合个数
set[position+1] = set[position] + 1;
position++;
}
else if(position != 0) { // 如果不是第一个位置

position–;

// 倒退

set[position]++; // 下一个集合尾数
}
else // 已倒退至第一个位置
break;
}
printf(“\n”);
return 0;
}
30.Algorithm Gossip: m 元素集合的 n 个元素子集
说明
假设有个集合拥有m个元素,任意的从集合中取出n个元素,则这n个元素所形成的可能子集有
那些?
解法
假设有5个元素的集点,取出3个元素的可能子集如下:
{1 2 3}、{1 2 4 }、{1 2 5}、{1 3 4}、{1 3 5}、{1 4 5}、{2 3 4}、{2 3 5}、{2 4 5}、
{3 4 5}
这些子集已经使用字典顺序排列,如此才可以观察出一些规则:
如果最右一个元素小于m,则如同码表一样的不断加1
如果右边一位已至最大值,则加1的位置往左移
每次加1的位置往左移后,必须重新调整右边的元素为递减顺序
所以关键点就在于哪一个位置必须进行加1的动作,到底是最右一个位置要加1?还是其它的位
置?
在实际撰写程式时,可以使用一个变数positon来记录加1的位置,position的初值设定为n-1,
因为我们要使用阵列,而最右边的索引值为最大 的n-1,在position位置的值若小于m就不断加
1,如果大于m了,position就减1,也就是往左移一个位置;由于位置左移后,右边的元素会 经
过调整,所以我们必须检查最右边的元素是否小于 m,如果是,则position调整回n-1,如果不
是,则positon维持不变。
实作
C

include

include

define MAX 20

int main(void) {
int set[MAX];
int m, n, position;
int i;
printf(“输入集合个数 m:”);
scanf(“%d”, &m);
printf(“输入取出元素 n:”);
scanf(“%d”, &n);
for(i = 0; i < n; i++)
set[i] = i + 1;
// 显示第一个集合
for(i = 0; i < n; i++)
printf(“%d “, set[i]);
putchar(‘\n’);
position = n - 1;
while(1) {
if(set[n-1] == m)
position–;
else
position = n - 1;
set[position]++;
// 调整右边元素
for(i = position + 1; i < n; i++)
set[i] = set[i-1] + 1;
for(i = 0; i < n; i++)
printf(“%d “, set[i]);
putchar(‘\n’);
if(set[0] >= m - n + 1)
break;
}
return 0;
}
31.Algorithm Gossip: 数字拆解
说明
这个题目来自于 数字拆解,我将之改为C语言的版本,并加上说明。
题目是这样的:
3 = 2+1 = 1+1+1 所以3有三种拆法
4 = 3 + 1 = 2 + 2 = 2 + 1 + 1 = 1 + 1 + 1 + 1 共五种
5 = 4 + 1 = 3 + 2 = 3 + 1 + 1 = 2 + 2 + 1 = 2 + 1 + 1 + 1 = 1 + 1 +1 +1 +1
共七种
依此类推,请问一个指定数字NUM的拆解方法个数有多少个?
解法
我们以上例中最后一个数字5的拆解为例,假设f( n )为数字n的可拆解方式个数,而f(x, y)为使
用y以下的数字来拆解x的方法个数,则观察:
5 = 4 + 1 = 3 + 2 = 3 + 1 + 1 = 2 + 2 + 1 = 2 + 1 + 1 + 1 = 1 + 1 +1 +1 +1
使用函式来表示的话:
f(5) = f(4, 1) + f(3,2) + f(2,3) + f(1,4) + f(0,5)
其中f(1, 4) = f(1, 3) + f(1, 2) + f(1, 1),但是使用大于1的数字来拆解1没有意义,所以f(1, 4) =
f(1, 1),而同样的,f(0, 5)会等于f(0, 0),所以:
f(5) = f(4, 1) + f(3,2) + f(2,3) + f(1,1) + f(0,0)
依照以上的说明,使用动态程式规画(Dynamic programming)来进行求解,其中f(4,1)其实就
是f(5-1, min(5-1,1)),f(x, y)就等于f(n-y, min(n-x, y)),其中n为要拆解的数字,而min()表示取两
者中较小的数。
使用一个二维阵列表格table[x][y]来表示f(x, y),刚开始时,将每列的索引0与索引1元素值设定
为1,因为任何数以0以下的数拆解必只有1种,而任何数以1以下的数拆解也必只有1种:
for(i = 0; i < NUM +1; i++){
table[i][0] = 1; // 任何数以0以下的数拆解必只有1种
table[i][1] = 1; // 任何数以1以下的数拆解必只有1种
}
接下来就开始一个一个进行拆解了,如果数字为NUM,则我们的阵列维度大小必须为NUM x
(NUM/2+1),以数字10为例,其维度为10 x 6我们的表格将会如下所示:
1 1 0 0 0 0
1 1 0 0 0 0
1 1 2 0 0 0
1 1 2 3 0 0
1 1 3 4 5 0
1 1 3 5 6 7
1 1 4 7 9 0
1 1 4 8 0 0
1 1 5 0 0 0
1 1 0 0 0 0
实作
C

include

include

define NUM 10

// 要拆解的数字

define DEBUG 0

int main(void) {
int table[NUM][NUM/2+1] = {0}; // 动态规画表格
int count = 0;
int result = 0;
int i, j, k;
printf(“数字拆解\n”);
printf(“3 = 2+1 = 1+1+1 所以3有三种拆法\n”);
printf(“4 = 3 + 1 = 2 + 2 = 2 + 1 + 1 = 1 + 1 + 1 + 1”);
printf(“共五种\n”);
printf(“5 = 4 + 1 = 3 + 2 = 3 + 1 + 1”);
printf(” = 2 + 2 + 1 = 2 + 1 + 1 + 1 = 1 + 1 +1 +1 +1”);
printf(“共七种\n”);
printf(“依此类推,求 %d 有几种拆法?”, NUM);
// 初始化
for(i = 0; i < NUM; i++){
table[i][0] = 1; // 任何数以0以下的数拆解必只有1种
table[i][1] = 1; // 任何数以1以下的数拆解必只有1种
}
// 动态规划
for(i = 2; i <= NUM; i++){
for(j = 2; j <= i; j++){
if(i + j > NUM) // 大于 NUM
continue;
count = 0;
for(k = 1 ; k <= j; k++){
count += table[i-k][(i-k >= k) ? k : i-k];
}
table[i][j] = count;
}
}
// 计算并显示结果
for(k = 1 ; k <= NUM; k++)
result += table[NUM-k][(NUM-k >= k) ? k : NUM-k];
printf(“\n\nresult: %d\n”, result);
if(DEBUG) {
printf(“\n除错资讯\n”);
for(i = 0; i < NUM; i++) {
for(j = 0; j < NUM/2+1; j++)
printf(“%2d”, table[i][j]);
printf(“\n”);
}
}
return 0;
}
32.Algorithm Gossip: 得分排行
说明假设有一教师依学生座号输入考试分数,现希望在输入完毕后自动显示学生分数的排行,
当然学生的分数可能相同。
解法这个问题基本上要解不难,只要使用额外的一个排行阵列走访分数阵列就可以了,直接
使用下面的程式片段作说明:
for(i = 0; i < count; i++) {
juni[i] = 1;
for(j = 0; j < count; j++) {
if(score[j] > score[i])
juni[i]++;
}
}
printf(“得分\t排行\n”);
for(i = 0; i < count; i++)
printf(“%d\t%d\n”, score[i], juni[i]);
上面这个方法虽然简单,但是反覆计算的次数是n^2,如果n值变大,那么运算的时间就会拖长;
改变juni阵列的长度为n+2,并将初始值设定为0,如下所示:
接下来走访分数阵列,并在分数所对应的排行阵列索引元素上加1,如下所示:
将排行阵列最右边的元素设定为1,然后依序将右边的元素值加至左边一个元素,最后排行阵列
中的「分数+1」」就是得该分数的排行,如下所示:
这样的方式看起来复杂,其实不过在计算某分数之前排行的人数,假设89分之前的排行人数为x
人,则89分自然就是x+1了,这也是为什么排行阵列最右边要设定为1的原因;如果89分有y人,
则88分自然就是x+y+1,整个阵列右边元素向左加的原因正是如此。
如果分数有负分的情况,由于C/C++或Java等程式语言无法处理负的索引,所以必须加上一个
偏移值,将所有的分数先往右偏移一个范围即可,最后显示的时候记得减回偏移值就可以了。

include

include

define MAX 100

define MIN 0

int main(void) {
int score[MAX+1] = {0};
int juni[MAX+2] = {0};
int count = 0, i;
do {
printf(“输入分数,-1结束:”);
scanf(“%d”, &score[count++]);
} while(score[count-1] != -1);
count–;
for(i = 0; i < count; i++)
juni[score[i]]++;
juni[MAX+1] = 1;
for(i = MAX; i >= MIN; i–)
juni[i] = juni[i] + juni[i+1];
printf(“得分\t排行\n”);
for(i = 0; i < count; i++)
printf(“%d\t%d\n”, score[i], juni[score[i]+1]);
return 0;
}
33.Algorithm Gossip: 选择、插入、气泡排序
说明选择排序(Selection sort)、插入排序(Insertion sort)与气泡排序(Bubble sort)这三个
排序方式是初学排序所必须知道的三个基本排序方式,它们由于速度不快而不实用(平均与最
2
解法
选择排序
将要排序的对象分作两部份,一个是已排序的,一个是未排序的,从后端未排序部份选择一个
最小值,并放入前端已排序部份的最后一个,例如:
排序前:70 80 31 37 10 1 48 60 33 80
[1] 80 31 37 10 70 48 60 33 80 选出最小值1
[1 10] 31 37 80 70 48 60 33 80 选出最小值10
[1 10 31] 37 80 70 48 60 33 80 选出最小值31
[1 10 31 33] 80 70 48 60 37 80 ……
[1 10 31 33 37] 70 48 60 80 80 ……
[1 10 31 33 37 48] 70 60 80 80 ……
[1 10 31 33 37 48 60] 70 80 80 ……
[1 10 31 33 37 48 60 70] 80 80 ……
[1 10 31 33 37 48 60 70 80] 80 ……
插入排序
像是玩朴克一样,我们将牌分作两堆,每次从后面一堆的牌抽出最前端的牌,然后插入前面一
堆牌的适当位置,例如:
排序前:92 77 67 8 6 84 55 85 43 67
[77 92] 67 8 6 84 55 85 43 67 将77插入92前
[67 77 92] 8 6 84 55 85 43 67 将67插入77前
[8 67 77 92] 6 84 55 85 43 67 将8插入67前
[6 8 67 77 92] 84 55 85 43 67 将6插入8前
[6 8 67 77 84 92] 55 85 43 67 将84插入92前
[6 8 55 67 77 84 92] 85 43 67 将55插入67前
[6 8 55 67 77 84 85 92] 43 67 ……
[6 8 43 55 67 77 84 85 92] 67 ……
[6 8 43 55 67 67 77 84 85 92] ……
气泡排序法
顾名思义,就是排序时,最大的元素会如同气泡一样移至右端,其利用比较相邻元素的方法,
将大的元素交换至右端,所以大的元素会不断的往右移动,直到适当的位置为止。
基本的气泡排序法可以利用旗标的方式稍微减少一些比较的时间,当寻访完阵列后都没有发生
任何的交换动作,表示排序已经完成,而无需再进行之后的回圈比较与交换动作,例如:
排序前:95 27 90 49 80 58 6 9 18 50
27 90 49 80 58 6 9 18 50 [95] 95浮出
27 49 80 58 6 9 18 50 [90 95] 90浮出
27 49 58 6 9 18 50 [80 90 95] 80浮出
27 49 6 9 18 50 [58 80 90 95] ……
27 6 9 18 49 [50 58 80 90 95] ……
6 9 18 27 [49 50 58 80 90 95] ……
6 9 18 [27 49 50 58 80 90 95] 由于接下来不会再发生交换动作,排序提早结束
在上面的例子当中,还加入了一个观念,就是当进行至i与i+1时没有交换的动作,表示接下来的
i+2至n已经排序完毕,这也增进了气泡排序的效率。

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void selsort(int[]); // 选择排序

void insort(int[]);

// 插入排序

void bubsort(int[]); // 气泡排序
int main(void) {
int number[MAX] = {0};
int i;
srand(time(NULL));
printf(“排序前:”);
for(i = 0; i < MAX; i++) {
number[i] = rand() % 100;
printf(“%d “, number[i]);
}
printf(“\n请选择排序方式:\n”);
printf(“(1)选择排序\n(2)插入排序\n(3)气泡排序\n:”);
scanf(“%d”, &i);
switch(i) {
case 1:
selsort(number); break;
case 2:
insort(number); break;
case 3:
bubsort(number); break;
default:
printf(“选项错误(1..3)\n”);
}
return 0;
}
void selsort(int number[]) {
int i, j, k, m;
for(i = 0; i < MAX-1; i++) {
m = i;
for(j = i+1; j < MAX; j++)
if(number[j] < number[m])
m = j;
if( i != m)
SWAP(number[i], number[m])
printf(“第 %d 次排序:”, i+1);
for(k = 0; k < MAX; k++)
printf(“%d “, number[k]);
printf(“\n”);
}
}
void insort(int number[]) {
int i, j, k, tmp;
for(j = 1; j < MAX; j++) {
tmp = number[j];
i = j - 1;
while(tmp < number[i]) {
number[i+1] = number[i];
i–;
if(i == -1)
break;
}
number[i+1] = tmp;
printf(“第 %d 次排序:”, j);
for(k = 0; k < MAX; k++)
printf(“%d “, number[k]);
printf(“\n”);
}
}
void bubsort(int number[]) {
int i, j, k, flag = 1;
for(i = 0; i < MAX-1 && flag == 1; i++) {
flag = 0;
for(j = 0; j < MAX-i-1; j++) {
if(number[j+1] < number[j]) {
SWAP(number[j+1], number[j]);
flag = 1;
}
}
printf(“第 %d 次排序:”, i+1);
for(k = 0; k < MAX; k++)
printf(“%d “, number[k]);
printf(“\n”);
}
}
34.Algorithm Gossip: Shell 排序法 - 改良的插入排序
说明
插入排序法由未排序的后半部前端取出一个值,插入已排序前半部的适当位置,概念简单但速
度不快。
排序要加快的基本原则之一,是让后一次的排序进行时,尽量利用前一次排序后的结果,以加
快排序的速度,Shell排序法即是基于此一概念来改良插入排序法。
解法
Shell排序法最初是D.L Shell于1959所提出,假设要排序的元素有n个,则每次进行插入排序时
并不是所有的元素同时进行时,而是取一段间隔。
Shell首先将间隔设定为n/2,然后跳跃进行插入排序,再来将间隔n/4,跳跃进行排序动作,再来
间隔设定为n/8、n/16,直到间隔为1之后的最 后一次排序终止,由于上一次的排序动作都会将
固定间隔内的元素排序好,所以当间隔越来越小时,某些元素位于正确位置的机率越高,因此
最后几次的排序动作将 可以大幅减低。
举个例子来说,假设有一未排序的数字如右:89 12 65 97 61 81 27 2 61 98
数字的总数共有10个,所以第一次我们将间隔设定为10 / 2 = 5,此时我们对间隔为5的数字进行
排序,如下所示:
画线连结的部份表示 要一起进行排序的部份,再来将间隔设定为5 / 2的商,也就是2,则第二
次的插入排序对象如下所示:
再来间隔设定为2 / 2 = 1,此时就是单纯的插入排序了,由于大部份的元素都已大致排序过了,
所以最后一次的插入排序几乎没作什么排序动作了:
将间隔设定为n / 2是D.L Shell最初所提出,在教科书中使用这个间隔比较好说明,然而 Shell排
序法的关键在于间隔的选定,例如Sedgewick证明选用以下的间隔可以加 快Shell排序法的速度:
j 2 j j 2 j
j
后来还有人证明有其它的间隔选定法可以将Shell排序法的速度再加快;另外Shell排序法的概念
也可以用来改良气泡排序法。
实作
C

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void shellsort(int[]);
int main(void) {
int number[MAX] = {0};
int i;
srand(time(NULL));
printf(“排序前:”);
for(i = 0; i < MAX; i++) {
number[i] = rand() % 100;
printf(“%d “, number[i]);
}
shellsort(number);
return 0;
}
void shellsort(int number[]) {
int i, j, k, gap, t;
gap = MAX / 2;
while(gap > 0) {
for(k = 0; k < gap; k++) {
for(i = k+gap; i < MAX; i+=gap) {
for(j = i - gap; j >= k; j-=gap) {
if(number[j] > number[j+gap]) {
SWAP(number[j], number[j+gap]);
}
else
break;
}
}
}
printf(“\ngap = %d:”, gap);
for(i = 0; i < MAX; i++)
printf(“%d “, number[i]);
printf(“\n”);
gap /= 2;
}
}
35.Algorithm Gossip: Shaker 排序法 - 改良的气泡排序
说明
请看看之前介绍过的气泡排序法:
for(i = 0; i < MAX-1 && flag == 1; i++) {
flag = 0;
for(j = 0; j < MAX-i-1; j++) {
if(number[j+1] < number[j]) {
SWAP(number[j+1], number[j]);
flag = 1;
}
}
}
事实上这个气泡排序法已经不是单纯的气泡排序了,它使用了旗标与右端左移两个方法来改进
排序的效能,而Shaker排序法使用到后面这个观念进一步改良气泡排序法。
解法
在上面的气泡排序法中,交换的动作并不会一直进行至阵列的最后一个,而是会进行至MAX-i-
1,所以排序的过程中,阵列右方排序好的元素会一直增加,使得左边排序的次数逐渐减少,如
我们的例子所示:
排序前:95 27 90 49 80 58 6 9 18 50
27 90 49 80 58 6 9 18 50 [95] 95浮出
27 49 80 58 6 9 18 50 [90 95] 90浮出
27 49 58 6 9 18 50 [80 90 95] 80浮出
27 49 6 9 18 50 [58 80 90 95] ……
27 6 9 18 49 [50 58 80 90 95] ……
6 9 18 27 [49 50 58 80 90 95] ……
6 9 18 [27 49 50 58 80 90 95]
方括号括住的部份表示已排序完毕,Shaker排序使用了这个概念,如果让左边的元素也具有这
样的性质,让左右两边的元素都能先排序完成,如此未排序的元素会集中在中间,由于左右两
边同时排序,中间未排序的部份将会很快的减少。
方法就在于气泡排序的双向进行,先让气泡排序由左向右进行,再来让气泡排序由右往左进行,
如此完成一次排序的动作,而您必须使用left与right两个旗标来记录左右两端已排序的元素位
置。
一个排序的例子如下所示:
排序前:45 19 77 81 13 28 18 19 77 11
往右排序:19 45 77 13 28 18 19 77 11 [81]
向左排序:[11] 19 45 77 13 28 18 19 77 [81]
往右排序:[11] 19 45 13 28 18 19 [77 77 81]
向左排序:[11 13] 19 45 18 28 19 [77 77 81]
往右排序:[11 13] 19 18 28 19 [45 77 77 81]
向左排序:[11 13 18] 19 19 28 [45 77 77 81]
往右排序:[11 13 18] 19 19 [28 45 77 77 81]
向左排序:[11 13 18 19 19] [28 45 77 77 81]
如上所示,括号中表示左右两边已排序完成的部份,当left > right时,则排序完成。
实作
C

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void shakersort(int[]);
int main(void) {
int number[MAX] = {0};
int i;
srand(time(NULL));
36.排序法 - 改良的选择排序
说明
选择排序法的概念简单,每次从未排序部份选一最小值,插入已排序部份的后端,其时间主要
花费于在整个未排序部份寻找最小值,如果能让搜寻最小值的方式加 快,选择排序法的速率也
就可以加快,Heap排序法让搜寻的路径由树根至最后一个树叶,而不是整个未排序部份,因而
称之为改良的选择排序法。
解法
Heap排序法使用Heap Tree(堆积树),树是一种资料结构,而堆积树是一个二元树,也就是每
一个父节点最多只有两个子节点(关于树的详细定义还请见资料结构书籍),堆积树的 父节点
若小于子节点,则称之为最小堆积(Min Heap),父节点若大于子节点,则称之为最大堆积(Max
Heap),而同一层的子节点则无需理会其大小关系,例如下面就是一个堆积树:
可以使用一维阵列来储存堆积树的所有元素与其顺序,为了计算方便,使用的起始索引是1而不
是0,索引1是树根位置,如果左子节点储存在阵列中的索引为s,则其父节点的索引为s/2,而右
子节点为s+1,就如上图所示,将上图的堆积树转换为一维阵列之后如下所示:
首先必须知道如何建立堆积树,加至堆积树的元素会先放置在最后一个树叶节点位置,然后检
查父节点是否小于子节点(最小堆积),将小的元素不断与父节点交换,直到满足堆积树的条件
为止,例如在上图的堆积加入一个元素12,则堆积树的调整方式如下所示:
建立好堆积树之后,树根一定是所有元素的最小值,您的目的就是:
将最小值取出
然后调整树为堆积树
不断重复以上的步骤,就可以达到排序的效果,最小值的取出方式是将树根与最后一个树叶节
点交换,然后切下树叶节点,重新调整树为堆积树,如下所示:
调整完毕后,树根节点又是最小值了,于是我们可以重覆这个步骤,再取出最小值,并调整树
为堆积树,如下所示:
如此重覆步骤之后,由于使用一维阵列来储存堆积树,每一次将树叶与树根交换的动作就是将
最小值放至后端的阵列,所以最后阵列就是变为已排序的状态。
其实堆积在调整的过程中,就是一个选择的行为,每次将最小值选至树根,而选择的路径并不
是所有的元素,而是由树根至树叶的路径,因而可以加快选择的过程, 所以Heap排序法才会被
称之为改良的选择排序法。

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void createheap(int[]);
void heapsort(int[]);
int main(void) {
int number[MAX+1] = {-1};
int i, num;
srand(time(NULL));
printf(“排序前:”);
for(i = 1; i <= MAX; i++) {
number[i] = rand() % 100;
printf(“%d “, number[i]);
}
printf(“\n建立堆积树:”);
createheap(number);
for(i = 1; i <= MAX; i++)
printf(“%d “, number[i]);
printf(“\n”);
heapsort(number);
printf(“\n”);
return 0;
}
void createheap(int number[]) {
int i, s, p;
int heap[MAX+1] = {-1};
for(i = 1; i <= MAX; i++) {
heap[i] = number[i];
s = i;
p = i / 2;
while(s >= 2 && heap[p] > heap[s]) {
SWAP(heap[p], heap[s]);
s = p;
p = s / 2;
}
}
for(i = 1; i <= MAX; i++)
number[i] = heap[i];
}
void heapsort(int number[]) {
int i, m, p, s;
m = MAX;
while(m > 1) {
SWAP(number[1], number[m]);
m–;
p = 1;
s = 2 * p;
while(s <= m) {
if(s < m && number[s+1] < number[s])
s++;
if(number[p] <= number[s])
break;
SWAP(number[p], number[s]);
p = s;
s = 2 * p;
}
printf(“\n排序中:”);
for(i = MAX; i > 0; i–)
printf(“%d “, number[i]);
}
}
37.Algorithm Gossip: 快速排序法(一)
说明快速排序法(quick sort)是目前所公认最快的排序方法之一(视解题的对象而定),虽然
2
错的。
快速排序法的基本精神是在数列中找出适当的轴心,然后将数列一分为二,分别对左边与右边
数列进行排序,而影响快速排序法效率的正是轴心的选择。
这边所介绍的第一个快速排序法版本,是在多数的教科书上所提及的版本,因为它最容易理解,
也最符合轴心分割与左右进行排序的概念,适合对初学者进行讲解。

解法这边所介绍的快速演算如下:将最左边的数设定为轴,并记录其值为

s

廻圈处理:
令索引 i 从数列左方往右方找,直到找到大于 s 的数
令索引 j 从数列左右方往左方找,直到找到小于 s 的数
如果 i >= j,则离开回圈
如果 i < j,则交换索引i与j两处的值
将左侧的轴与 j 进行交换
对轴左边进行递回
对轴右边进行递回
透过以下演算法,则轴左边的值都会小于s,轴右边的值都会大于s,如此再对轴左右两边进行
递回,就可以对完成排序的目的,例如下面的实例,*表示要交换的数,[]表示轴:
[41] 24 76* 11 45 64 21 69 19 36*
[41] 24 36 11 45* 64 21 69 19* 76
[41] 24 36 11 19 64* 21* 69 45 76
[41] 24 36 11 19 21 64 69 45 76
21 24 36 11 19 [41] 64 69 45 76
在上面的例子中,41左边的值都比它小,而右边的值都比它大,如此左右再进行递回至排序完
成。

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void quicksort(int[], int, int);
int main(void) {
int number[MAX] = {0};
int i, num;
srand(time(NULL));
printf(“排序前:”);
for(i = 0; i < MAX; i++) {
number[i] = rand() % 100;
printf(“%d “, number[i]);
}
quicksort(number, 0, MAX-1);
printf(“\n排序后:”);
for(i = 0; i < MAX; i++)
printf(“%d “, number[i]);
printf(“\n”);
return 0;
}
void quicksort(int number[], int left, int right) {
int i, j, s;
if(left < right) {
s = number[left];
i = left;
j = right + 1;
while(1) {
// 向右找
while(i + 1 < number.length && number[++i] < s) ;
// 向左找
while(j -1 > -1 && number[–j] > s) ;
if(i >= j)
break;
SWAP(number[i], number[j]);
}
number[left] = number[j];
number[j] = s;

quicksort(number, left, j-1);

// 对左边进行递回

quicksort(number, j+1, right); // 对右边进行递回
}
}
38.Algorithm Gossip: 快速排序法(二)
说明在快速排序法(一)中,每次将最左边的元素设为轴,而之前曾经说过,快速排序法的
加速在于轴的选择,在这个例子中,只将轴设定为中间的元素,依这个元素作基准进行比较,
这可以增加快速排序法的效率。

解法在这个例子中,取中间的元素s作比较,同样的先得右找比s大的索引

i,然后找比s小的

索引 j,只要两边的索引还没有交会,就交换 i 与 j 的元素值,这次不用再进行轴的交换了,
因为在寻找交换的过程中,轴位置的元素也会参与交换的动作,例如:

41

24

76

11

45

64

21

69

19

36

首先left为0,right为9,(left+right)/2 = 4(取整数的商),所以轴为索引4的位置,比较的元素是
45,您往右找比45大的,往左找比45小的进行交换:

41 24 76* 11
41 24 36 11
41 24 36 11
[41 24 36 11

[45] 64 21 69 19 *36
45* 64 21 69 19* 76
19 64* 21* 69 45 76
19 21] [64 69 45 76]

完成以上之后,再初别对左边括号与右边括号的部份进行递回,如此就可以完成排序的目的。

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void quicksort(int[], int, int);
int main(void) {
int number[MAX] = {0};
int i, num;
srand(time(NULL));
printf(“排序前:”);
for(i = 0; i < MAX; i++) {
number[i] = rand() % 100;
printf(“%d “, number[i]);
}
quicksort(number, 0, MAX-1);
printf(“\n排序后:”);
for(i = 0; i < MAX; i++)
printf(“%d “, number[i]);
printf(“\n”);
return 0;
}
void quicksort(int number[], int left, int right) {
int i, j, s;
if(left < right) {
s = number[(left+right)/2];
i = left - 1;
j = right + 1;
while(1) {
while(number[++i] < s) ; // 向右找
while(number[–j] > s) ; // 向左找
if(i >= j)
break;
SWAP(number[i], number[j]);
}

quicksort(number, left, i-1);

// 对左边进行递回

quicksort(number, j+1, right); // 对右边进行递回
}
}
39.Algorithm Gossip: 快速排序法(三)
说明
之前说过轴的选择是快速排序法的效率关键之一,在这边的快速排序法的轴选择方式更加快了
快速排序法的效率,它是来自演算法名书 Introduction to Algorithms 之中。
解法
先说明这个快速排序法的概念,它以最右边的值 s作比较的标准,将整个数列分为三个部份,
一个是小于s的部份,一个是大于s的部份,一个是未处理的部份,如下所示 :
在排序的过程中,i 与 j 都会不断的往右进行比较与交换,最后数列会变为以下的状态:
然后将s的值置于中间,接下来就以相同的步骤会左右两边的数列进行排序的动作,如下所示:
整个演算的过程,直接摘录书中的虚拟码来作说明:
QUICKSORT(A, p, r)
if p < r
then q <- PARTITION(A, p, r)
QUICKSORT(A, p, q-1)
QUICKSORT(A, q+1, r)
end QUICKSORT
PARTITION(A, p, r)
x <- A[r]
i <- p-1
for j <- p to r-1
do if A[j] <= x
then i <- i+1
exchange A[i]<->A[j]
exchange A[i+1]<->A[r]
return i+1
end PARTITION
一个实际例子的演算如下所示:

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

int partition(int[], int, int);
void quicksort(int[], int, int);
int main(void) {
int number[MAX] = {0};
int i, num;
srand(time(NULL));
printf(“排序前:”);
for(i = 0; i < MAX; i++) {
number[i] = rand() % 100;
printf(“%d “, number[i]);
}
quicksort(number, 0, MAX-1);
printf(“\n排序后:”);
for(i = 0; i < MAX; i++)
printf(“%d “, number[i]);
printf(“\n”);
return 0;
}
int partition(int number[], int left, int right) {
int i, j, s;
s = number[right];
i = left - 1;
for(j = left; j < right; j++) {
if(number[j] <= s) {
i++;
SWAP(number[i], number[j]);
}
}
SWAP(number[i+1], number[right]);
return i+1;
}
void quicksort(int number[], int left, int right) {
int q;
if(left < right) {
q = partition(number, left, right);
quicksort(number, left, q-1);
quicksort(number, q+1, right);
}
}
40.Algorithm Gossip: 合并排序法
说明之前所介绍的排序法都是在同一个阵列中的排序,考虑今日有两笔或两笔以上的资料,
它可能是不同阵列中的资料,或是不同档案中的资料,如何为它们进行排序?
解法可以使用合并排序法,合并排序法基本是将两笔已排序的资料合并并进行排序,如果所
读入的资料尚未排序,可以先利用其它的排序方式来处理这两笔资料,然后再将排序好的这两
笔资料合并。
有人问道,如果两笔资料本身就无排序顺序,何不将所有的资料读入,再一次进行排序?排序
的精神是尽量利用资料已排序的部份,来加快排序的效率,小笔资料的 排序较为快速,如果小
笔资料排序完成之后,再合并处理时,因为两笔资料都有排序了,所有在合并排序时会比单纯
读入所有的资料再一次排序来的有效率。
那么可不可以直接使用合并排序法本身来处理整个排序的动作?而不动用到其它的排序方式?
答案是肯定的,只要将所有的数字不断的分为两个等分,直到最后剩一个数字为止,然后再反
过来不断的合并,就如下图所示:
不过基本上分割又会花去额外的时间,不如使用其它较好的排序法来排序小笔资料,再使用合
并排序来的有效率。
下面这个程式范例,我们使用快速排序法来处理小笔资料排序,然后再使用合并排序法处理合
并的动作。

include

include

include

define MAX1 10

define MAX2 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

int partition(int[], int, int);
void quicksort(int[], int, int);
void mergesort(int[], int, int[], int, int[]);
int main(void) {
int number1[MAX1] = {0};
int number2[MAX1] = {0};
int number3[MAX1+MAX2] = {0};
int i, num;
srand(time(NULL));
printf(“排序前:”);
printf(“\nnumber1[]:”);
for(i = 0; i < MAX1; i++) {
number1[i] = rand() % 100;
printf(“%d “, number1[i]);
}
printf(“\nnumber2[]:”);
for(i = 0; i < MAX2; i++) {
number2[i] = rand() % 100;
printf(“%d “, number2[i]);
}
// 先排序两笔资料
quicksort(number1, 0, MAX1-1);
quicksort(number2, 0, MAX2-1);
printf(“\n排序后:”);
printf(“\nnumber1[]:”);
for(i = 0; i < MAX1; i++)
printf(“%d “, number1[i]);
printf(“\nnumber2[]:”);
for(i = 0; i < MAX2; i++)
printf(“%d “, number2[i]);
// 合并排序
mergesort(number1, MAX1, number2, MAX2, number3);
printf(“\n合并后:”);
for(i = 0; i < MAX1+MAX2; i++)
printf(“%d “, number3[i]);
printf(“\n”);
return 0;
}
int partition(int number[], int left, int right) {
int i, j, s;
s = number[right];
i = left - 1;
for(j = left; j < right; j++) {
if(number[j] <= s) {
i++;
SWAP(number[i], number[j]);
}
}
SWAP(number[i+1], number[right]);
return i+1;
}
void quicksort(int number[], int left, int right) {
int q;
if(left < right) {
q = partition(number, left, right);
quicksort(number, left, q-1);
quicksort(number, q+1, right);
}
}
void mergesort(int number1[], int M, int number2[], int N, int number3[]) {
int i = 0, j = 0, k = 0;
while(i < M && j < N) {
if(number1[i] <= number2[j])
number3[k++] = number1[i++];
else
number3[k++] = number2[j++];
}
while(i < M)
number3[k++] = number1[i++];
while(j < N)
number3[k++] = number2[j++];
}
41.Algorithm Gossip: 基数排序法

说明在之前所介绍过的排序方法,都是属于「比较性」的排序法,也就是每次排序时

,都是

比较整个键值的大小以进行排序。
这边所要介绍的「基数排序法」(radix sort)则是属于「分配式排序」(distribution sort), 基数
排序法又称「桶子法」(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排
序的元素分配至某些「桶」中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时
间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率
高于其它的比较性排序法。
解法基数排序的方式可以采用LSD(Least sgnificant digital)或MSD(Most sgnificant digital),
LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
以LSD为例,假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:

0

1

2

3

4

5

6

7

8

9

81

65

39

43

14

55

28

93

22

73

接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:

0

1

2

3

4

5

6

7

8

9

28

39

14

22

43

55

65

73

81

93

接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最
高位数为止。
LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好,MSD的方
式恰与LSD相反,是由高位数为基底开始进行分配,其他的演 算方式则都相同。

include

include

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

int search(int[]);
int partition(int[], int, int);
void quicksort(int[], int, int);
int main(void) {
int number[MAX+1] = {0};
int i, find;
srand(time(NULL));
for(i = 1; i <= MAX; i++)
number[i] = rand() % 100;
quicksort(number, 1, MAX);
printf(“数列:”);
for(i = 1; i <= MAX; i++)
printf(“%d “, number[i]);
printf(“\n输入搜寻值:”);
scanf(“%d”, &number[0]);
if(find = search(number))
printf(“\n找到数值于索引 %d “, find);
else
printf(“\n找不到数值”);
printf(“\n”);
return 0;
}
int search(int number[]) {
int i, k;
k = number[0];
i = MAX;
while(number[i] != k)
i–;
return i;
}
int partition(int number[], int left, int right) {
int i, j, s;
s = number[right];
i = left - 1;
for(j = left; j < right; j++) {
if(number[j] <= s) {
i++;
SWAP(number[i], number[j]);
}
}
SWAP(number[i+1], number[right]);
return i+1;
}
void quicksort(int number[], int left, int right) {
int q;
if(left < right) {
q = partition(number, left, right);
quicksort(number, left, q-1);
quicksort(number, q+1, right);
}
}
43.Algorithm Gossip: 二分搜寻法(搜寻原则的代表)
说明如果搜寻的数列已经有排序,应该尽量利用它们已排序的特性,以减少搜寻比对的次数,
这是搜寻的基本原则,二分搜寻法是这个基本原则的代表。
解法在二分搜寻法中,从数列的中间开始搜寻,如果这个数小于我们所搜寻的数,由于数列
已排序,则该数左边的数一定都小于要搜寻的对象,所以无需浪费时间在左边的数;如果搜寻
的数大于所搜寻的对象,则右边的数无需再搜寻,直接搜寻左边的数。
所以在二分搜寻法中,将数列不断的分为两个部份,每次从分割的部份中取中间数比对,例如
要搜寻92于以下的数列,首先中间数索引为(0+9)/2 = 4(索引由0开 始 ):
[3 24 57 57 67 68 83 90 92 95]
由于67小于92,所以转搜寻右边的数列:
3 24 57 57 67 [68 83 90 92 95]
由于90小于92,再搜寻右边的数列,这次就找到所要的数了:
3 24 57 57 67 68 83 90 [92 95]

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void quicksort(int[], int, int);
int bisearch(int[], int);
int main(void) {
int number[MAX] = {0};
int i, find;
srand(time(NULL));
for(i = 0; i < MAX; i++) {
number[i] = rand() % 100;
}
quicksort(number, 0, MAX-1);
printf(“数列:”);
for(i = 0; i < MAX; i++)
printf(“%d “, number[i]);
printf(“\n输入寻找对象:”);
scanf(“%d”, &find);
if((i = bisearch(number, find)) >= 0)
printf(“找到数字于索引 %d “, i);
else
printf(“\n找不到指定数”);
printf(“\n”);
return 0;
}
int bisearch(int number[], int find) {
int low, mid, upper;
low = 0;
upper = MAX - 1;
while(low <= upper) {
mid = (low+upper) / 2;
if(number[mid] < find)
low = mid+1;
else if(number[mid] > find)
upper = mid - 1;
else
return mid;
}
return -1;
}
void quicksort(int number[], int left, int right) {
int i, j, k, s;
if(left < right) {
s = number[(left+right)/2];
i = left - 1;
j = right + 1;
while(1) {
while(number[++i] < s) ; // 向右找
while(number[–j] > s) ; // 向左找
if(i >= j)
break;
SWAP(number[i], number[j]);
}

quicksort(number, left, i-1);

// 对左边进行递回

quicksort(number, j+1, right); // 对右边进行递回
}
}
44.Algorithm Gossip: 插补搜寻法
说明
如果却搜寻的资料分布平均的话,可以使用插补( Interpolation)搜寻法来进行搜寻,在搜寻
的对象大于500时,插补搜寻法会比 二分搜寻法 来的快速。
解法
插补搜寻法是以资料分布的近似直线来作比例运算,以求出中间的索引并进行资料比对,如果
取出的值小于要寻找的值,则提高下界,如果取出的值大于要寻找的 值,则降低下界,如此不
断的减少搜寻的范围,所以其本原则与二分搜寻法是相同的,至于中间值的寻找是透过比例运
算,如下所示,其中K是指定要寻找的对象, 而m则是可能的索引值:
实作
C

include

include

include

define MAX 10

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void quicksort(int[], int, int);
int intsrch(int[], int);
int main(void) {
int number[MAX] = {0};
int i, find;
srand(time(NULL));
for(i = 0; i < MAX; i++) {
number[i] = rand() % 100;
}
quicksort(number, 0, MAX-1);
printf(“数列:”);
for(i = 0; i < MAX; i++)
printf(“%d “, number[i]);
printf(“\n输入寻找对象:”);
scanf(“%d”, &find);
if((i = intsrch(number, find)) >= 0)
printf(“找到数字于索引 %d “, i);
else
printf(“\n找不到指定数”);
printf(“\n”);
return 0;
}
int intsrch(int number[], int find) {
int low, mid, upper;
low = 0;
upper = MAX - 1;
while(low <= upper) {
mid = (upper-low)*
(find-number[low])/(number[upper]-number[low])
+ low;
if(mid < low || mid > upper)
return -1;
if(find < number[mid])
upper = mid - 1;
else if(find > number[mid])
low = mid + 1;
else
return mid;
}
return -1;
}
void quicksort(int number[], int left, int right) {
int i, j, k, s;
if(left < right) {
s = number[(left+right)/2];
i = left - 1;
j = right + 1;
while(1) {
while(number[++i] < s) ; // 向右找
while(number[–j] > s) ; // 向左找
if(i >= j)
break;
SWAP(number[i], number[j]);
}

quicksort(number, left, i-1);

// 对左边进行递回

quicksort(number, j+1, right); // 对右边进行递回
}
}
45.Algorithm Gossip: 费氏搜寻法
说明
二分搜寻法每次搜寻时,都会将搜寻区间分为一半,所以其搜寻时间为O(log(2)n),log(2)表示
以2为底的log值,这边要介绍的费氏搜寻,其利用费氏数列作为间隔来搜寻下一个数,所以区
间收敛的速度更快,搜寻时间为O(logn)。
解法
费氏搜寻使用费氏数列来决定下一个数的搜寻位置,所以必须先制作费氏数列,这在之前有提
过;费氏搜寻会先透过公式计算求出第一个要搜寻数的位置,以及其代 表的费氏数,以搜寻对
象10个数字来说,第一个费氏数经计算后一定是F5,而第一个要搜寻的位置有两个可能,例如
若在下面的数列搜寻的话(为了计算方便, 通常会将索引0订作无限小的数,而数列由索引1
开 始 ):
-infin; 1 3 5 7 9 13 15 17 19 20
如果要搜寻5的话,则由索引F5 = 5开始搜寻,接下来如果数列中的数小于指定搜寻值时,就往
左找,大于时就向右,每次找的间隔是F4、F3、F2来寻找,当费氏数为0时还没找到,就表示
寻找失败,如下所示:
由于第一个搜寻值索引F5 = 5处的值小于19,所以此时必须对齐数列右方,也就是将第一个搜
寻值的索引改为F5+2 = 7,然后如同上述的方式进行搜寻,如下所示:
至于第一个搜寻值是如何找到的?我们可以由以下这个公式来求得,其中n为搜寻对象的个数:
Fx + m = n
Fx <= n
也就是说Fx必须找到不大于n的费氏数,以10个搜寻对象来说:
Fx + m = 10
取Fx = 8, m = 2,所以我们可以对照费氏数列得x = 6,然而第一个数的可能位置之一并不是F6,
而是第x-1的费氏数,也就是F5 = 5。
如果数列number在索引5处的值小于指定的搜寻值,则第一个搜寻位置就是索引5的位置,如果
大于指定的搜寻值,则第一个搜寻位置必须加上m,也就是F5 + m = 5 + 2 = 7,也就是索引7的
位置,其实加上m的原因,是为了要让下一个搜寻值刚好是数列的最后一个位置。
费氏搜寻看来难懂,但只要掌握Fx + m = n这个公式,自己找几个实例算一次,很容易就可以理
解;费氏搜寻除了收敛快速之外,由于其本身只会使用到加法与减法,在运算上也可以加快。

include

include

include

define MAX 15

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void createfib(void);
int findx(int, int);

// 建立费氏数列
// 找x值

int fibsearch(int[], int); // 费氏搜寻
void quicksort(int[], int, int); // 快速排序
int Fib[MAX] = {-999};
int main(void) {
int number[MAX] = {0};
int i, find;
srand(time(NULL));
for(i = 1; i <= MAX; i++) {
number[i] = rand() % 100;
}
quicksort(number, 1, MAX);
printf(“数列:”);
for(i = 1; i <= MAX; i++)
printf(“%d “, number[i]);
printf(“\n输入寻找对象:”);
scanf(“%d”, &find);
if((i = fibsearch(number, find)) >= 0)
printf(“找到数字于索引 %d “, i);
else
printf(“\n找不到指定数”);
printf(“\n”);
return 0;
}
// 建立费氏数列
void createfib(void) {
int i;
Fib[0] = 0;
Fib[1] = 1;
for(i = 2; i < MAX; i++)
Fib[i] = Fib[i-1] + Fib[i-2];
}
// 找 x 值
int findx(int n, int find) {
int i = 0;
while(Fib[i] <= n)
i++;
i–;
return i;
}
// 费式搜寻
int fibsearch(int number[], int find) {
int i, x, m;
createfib();
x = findx(MAX+1,find);
m = MAX - Fib[x];
printf(“\nx = %d, m = %d, Fib[x] = %d\n\n”,
x, m, Fib[x]);
x–;
i = x;
if(number[i] < find)
i += m;
while(Fib[x] > 0) {
if(number[i] < find)
i += Fib[–x];
else if(number[i] > find)
i -= Fib[–x];
else
return i;
}
return -1;
}
void quicksort(int number[], int left, int right) {
int i, j, k, s;
if(left < right) {
s = number[(left+right)/2];
i = left - 1;
j = right + 1;
while(1) {
while(number[++i] < s) ; // 向右找
while(number[–j] > s) ; // 向左找
if(i >= j)
break;
SWAP(number[i], number[j]);
}

quicksort(number, left, i-1);

// 对左边进行递回

quicksort(number, j+1, right); // 对右边进行递回
}
}
46.Algorithm Gossip: 稀疏矩阵
说明
如果在矩阵中,多数的元素并没有资料,称此矩阵为稀疏矩阵(sparse matrix), 由于矩阵在程
式中常使用二维阵列表示,二维阵列的大小与使用的记忆体空间成正比,如果多数的元素没有
资料,则会造成记忆体空间的浪费,为 此,必须设计稀疏矩阵的阵列储存方式,利用较少的记
忆体空间储存完整的矩阵资讯。
解法
在这边所介绍的方法较为简单,阵列只储存矩阵的行数、列数与有资料的索引位置及其值,在
需要使用矩阵资料时,再透过程式运算加以还原,例如若矩阵资料如下 ,其中0表示矩阵中该
位置没有资料:
0 0 0 0 0 0
0 3 0 0 0 0
0 0 0 6 0 0
0 0 9 0 0 0
0 0 0 0 12 0
这个矩阵是5X6矩阵,非零元素有4个,您要使用的阵列第一列记录其列数、行数与非零元素个
数:
5 6 4
阵列的第二列起,记录其位置的列索引、行索引与储存值:
1 1 3
2 3 6
3 2 9
4 4 12
所以原本要用30个元素储存的矩阵资讯,现在只使用了15个元素来储存,节省了不少记忆体的
使用。
C

include

include

include

include

include

include

define N 5

int main(void) {
int arr1[N][N] = {

{1, 2, 3, 4,
{0, 6, 7, 8,

5},
9},

{0, 0, 10, 11, 12},
{0, 0, 0, 13, 14},
{0, 0, 0, 0, 15}};
int arr2[N*(1+N)/2] = {0};
int i, j, loc = 0;
printf(“原二维资料:\n”);
for(i = 0; i < N; i++) {
for(j = 0; j < N; j++) {
printf(“%4d”, arr1[i][j]);
}
printf(“\n”);
}
printf(“\n以列为主:”);
for(i = 0; i < N; i++) {
for(j = 0; j < N; j++) {
if(arr1[i][j] != 0)
arr2[loc++] = arr1[i][j];
}
}
for(i = 0; i < N*(1+N)/2; i++)
printf(“%d “, arr2[i]);
printf(“\n输入索引(i, j):”);
scanf(“%d, %d”, &i, &j);
loc = N*i - i*(i+1)/2 + j;
printf(“(%d, %d) = %d”, i, j, arr2[loc]);
printf(“\n”);
return 0;
}
49.Algorithm Gossip: 奇数魔方阵
说明
将1到n(为奇数)的数字排列在nxn的方阵上,且各行、各列与各对角线的和必须相同,如下所
示:
解法
填魔术方阵的方法以奇数最为简单,第一个数字放在第一行第一列的正中央,然后向右 (左)上
填,如果右(左)上已有数字,则向下填,如下图所示:
一般程式语言的阵列索引多由0开始,为了计算方便,我们利用索引1到n的部份,而在计算是向
右(左)上或向下时,我们可以将索引值除以n值,如果得到余数为1就向下,否则就往右(左)上 ,
原理很简单,看看是不是已经在同一列上绕一圈就对了。

include

include

define N 5

int main(void) {
int i, j, key;
int square[N+1][N+1] = {0};
i = 0;
j = (N+1) / 2;
for(key = 1; key <= N*N; key++) {
if((key % N) == 1)
i++;
else {
i–;
j++;
}
if(i == 0)
i = N;
if(j > N)
j = 1;
square[i][j] = key;
}
for(i = 1; i <= N; i++) {
for(j = 1; j <= N; j++)
printf(“%2d “, square[i][j]);
}
return 0;
}
50.Algorithm Gossip: 4N 魔方阵
说明
与 奇数魔术方阵 相同,在于求各行、各列与各对角线的和相等,而这次方阵的维度是4的倍
数。
解法
先来看看4X4方阵的解法:
简单的说,就是一个从左上由1依序开始填,但遇对角线不填,另一个由左上由16开始填,但只
填在对角线,再将两个合起来就是解答了;如果N大于2,则以 4X4为单位画对角线:
至于对角线的位置该如何判断,有两个公式,有兴趣的可以画图印证看看,如下所示:
左上至右下:j % 4 == i % 4
右上至左下:(j % 4 + i % 4) == 1

include

include

define N 8

int main(void) {
int i, j;
int square[N+1][N+1] = {0};
for(j = 1; j <= N; j++) {
for(i = 1; i <= N; i++){
if(j % 4 == i % 4 || (j % 4 + i % 4) == 1)
square[i][j] = (N+1-i) * N -j + 1;
else
square[i][j] = (i - 1) * N + j;
}
}
for(i = 1; i <= N; i++) {
for(j = 1; j <= N; j++)
printf(“%2d “, square[i][j]);
printf(“\n”);
}
return 0;
}
51.Algorithm Gossip: 2(2N+1) 魔方阵
说明方阵的维度整体来看是偶数,但是其实是一个奇数乘以一个偶数,例如6X6,其中 6=2X3,
我们也称这种方阵与单偶数方阵。
解法如果您会解奇数魔术方阵,要解这种方阵也就不难理解,首先我们令n=2(2m+1),并将整
个方阵看作是数个奇数方阵的组合,如下所示:
首先依序将A、B、C、D四个位置,依奇数方阵的规则填入数字,填完之后,方阵中各行的和
就相同了,但列与对角线则否,此时必须在A-D与C- B之间,作一些对应的调换,规则如下:
将A中每一列(中间列除外)的头m个元素,与D中对应位置的元素调换。
将A的中央列、中央那一格向左取m格,并与D中对应位置对调
将C中每一列的倒数m-1个元素,与B中对应的元素对调
举个实例来说,如何填6X6方阵,我们首先将之分解为奇数方阵,并填入数字,如下所示:
接下来进行互换的动作,互换的元素以不同颜色标示,如下:
由于m-1的数为0,所以在这个例子中,C-B部份并不用进行对调。

include

include

define N 6

define SWAP(x,y) {int t; t = x; x = y; y = t;}

void magic_o(int [][N], int);
void exchange(int [][N], int);
int main(void) {
int square[N][N] = {0};
int i, j;
magic_o(square, N/2);
exchange(square, N);
for(i = 0; i < N; i++) {
for(j = 0; j < N; j++)
printf(“%2d “, square[i][j]);
printf(“\n”);
}
return 0;
}
void magic_o(int square[][N], int n) {
int count, row, column;
row = 0;
column = n / 2;
for(count = 1; count <= n*n; count++) {

square[row][column] = count;

// 填A

square[row+n][column+n] = count + n*n; // 填B
square[row][column+n] = count + 2*n*n; // 填C
square[row+n][column] = count + 3*n*n; // 填D
if(count % n == 0)
row++;
else {
row = (row == 0) ? n - 1 : row - 1 ;
column = (column == n-1) ? 0 : column + 1;
}
}
}
void exchange(int x[][N], int n) {
int i, j;
int m = n / 4;
int m1 = m - 1;
for(i = 0; i < n/2; i++) {
if(i != m) {

for(j = 0; j < m; j++)
SWAP(x[i][j], x[n/2+i][j]);
for(j = 0; j < m1; j++)

// 处理规则 1
// 处理规则 2

SWAP(x[i][n-1-j], x[n/2+i][n-1-j]);
}
else { // 处理规则 3
for(j = 1; j <= m; j++)
SWAP(x[m][j], x[n/2+m][j]);
for(j = 0; j < m1; j++)
SWAP(x[m][n-1-j], x[n/2+m][n-1-j]);
}
}
}
  

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

老掉牙 河内塔 费式数列 巴斯卡三角形 三色棋 老鼠走迷官(一) 老鼠走迷官(二) 骑士走棋盘 八个皇后 八枚银币 生命游戏 字串核对 双色、三色河内塔 背包问题(Knapsack Problem) 数、运算 蒙地卡罗法求 PI Eratosthenes筛选求质数 超长整数运算(大数运算) 长 PI 最大公因数、最小公倍数、因式分解 完美数 阿姆斯壮数 最大访客数 中序式转后序式(前序式) 后序式的运算 关于赌博 洗扑克牌(乱数排列) Craps赌博游戏 约瑟夫问题(Josephus Problem) 集合问题 排列组合 格雷码(Gray Code) 产生可能的集合 m元素集合的n个元素子集 数字拆解 排序 得分排行 选择、插入、气泡排序 Shell 排序法 - 改良的插入排序 Shaker 排序法 - 改良的气泡排序 Heap 排序法 - 改良的选择排序 快速排序法(一) 快速排序法(二) 快速排序法(三) 合并排序法 基数排序法 搜寻 循序搜寻法(使用卫兵) 二分搜寻法(搜寻原则的代表) 插补搜寻法 费氏搜寻法 矩阵 稀疏矩阵 多维矩阵转一维矩阵 上三角、下三角、对称矩阵 奇数魔方阵 4N 魔方阵 2(2N+1) 魔方阵 堆叠、伫列 堆叠 - 使用阵列实作 堆叠 - 使用链结实作(C 语言动态记忆体宣告) 堆叠 - 使用 Java 作物件封装 伫列 - 使用阵列实作 伫列 - 使用链结实作(C语言动态记忆体宣告) 伫列 - 使用Java 作物件封装 其它 自产生程式(quine
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值