妙趣横生的C语言算法如何实现?

99ANYc3cd6
预计阅读时长 22 分钟
位置: 首页 C语言 正文

汉诺塔 - 递归的优雅与传说

趣味点: 这是一个古老的印度传说,世界中心有一根宝石柱,上面套着64个大小不同的金环,僧侣们按照“一次只能移动一个金环,且大盘不能放在小盘上”的规则,日夜不停地移动金环,传说当所有金环都移到另一根柱子上时,世界就会毁灭,这个算法完美地展示了递归思想的简洁和威力,其步骤数量是 2^64 - 1,是一个天文数字。

妙趣横生的算法(c语言实现)
(图片来源网络,侵删)

问题: 有三根柱子(A, B, C),开始时A柱上有n个大小不一的盘子,大盘在下,小盘在上,目标是将所有盘子移动到C柱上,移动时必须遵守以下规则:

  1. 一次只能移动一个盘子。
  2. 每次移动都是将柱子最上面的盘子移到另一根柱子的最上面。
  3. 大盘子不能放在小盘子上面。

C语言实现:

#include <stdio.h>
// 函数声明
void hanoi(int n, char from_rod, char to_rod, char aux_rod);
int main() {
    int n = 4; // 假设有4个盘子
    printf("汉诺塔移动步骤 (n=%d):\n", n);
    hanoi(n, 'A', 'C', 'B'); // 将n个盘子从A柱移到C柱,B柱作为辅助
    return 0;
}
/**
 * @brief 汉诺塔递归函数
 * @param n       要移动的盘子数量
 * @param from_rod 源柱子
 * @param to_rod   目标柱子
 * @param aux_rod  辅助柱子
 */
void hanoi(int n, char from_rod, char to_rod, char aux_rod) {
    // 基本情况:如果只有一个盘子,直接移动
    if (n == 1) {
        printf("将盘子 1 从 %c 移动到 %c\n", from_rod, to_rod);
        return;
    }
    // 递归步骤:
    // 1. 将上面的 n-1 个盘子从 from_rod 移动到 aux_rod (使用 to_rod 作为辅助)
    hanoi(n - 1, from_rod, aux_rod, to_rod);
    // 2. 将最底下的第 n 个盘子从 from_rod 移动到 to_rod
    printf("将盘子 %d 从 %c 移动到 %c\n", n, from_rod, to_rod);
    // 3. 将刚才移到 aux_rod 上的 n-1 个盘子,从 aux_rod 移动到 to_rod (使用 from_rod 作为辅助)
    hanoi(n - 1, aux_rod, to_rod, from_rod);
}

妙趣解析: 这个算法的精髓在于“大事化小,小事化了”,移动 n 个盘子的问题被巧妙地分解成了三个更小的子问题:

  1. 移动 n-1 个盘子。
  2. 移动 1 个盘子。
  3. 再次移动 n-1 个盘子。

这个过程不断递归,直到问题简单到可以直接解决(n=1),代码的逻辑和问题的描述几乎一模一样,这就是递归的魅力所在。


生命游戏 - 简单规则下的复杂涌现

趣味点: 由英国数学家约翰·康威发明,是一个“零玩家游戏”,你只需要设定一个初始状态,然后游戏就会按照简单的规则自行演化,展现出无法预测的、复杂而美丽的模式,从滑翔机到振荡器,再到能自我复制的“滑翔机枪”,它完美地展示了“复杂源于简单”的哲学思想。

问题: 在一个无限的二维网格上,每个格子有两种状态:“存活”或“死亡”,每个格子都与其周围的8个邻居互动,演化规则如下:

  1. 存活规则: 任何存活的细胞,如果活邻居少于2个(孤独)或多于3个(过度拥挤),它都会在下一代死亡。
  2. 死亡规则: 任何死亡的细胞,如果恰好有3个活邻居,它会在下一代复活(模拟繁殖)。

C语言实现: 为了在计算机中模拟,我们需要一个有限的网格。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h> // for sleep()
#define WIDTH 40
#define HEIGHT 20
#define GENERATIONS 100
// 打印网格
void print_grid(bool grid[HEIGHT][WIDTH]) {
    for (int i = 0; i < HEIGHT; i++) {
        for (int j = 0; j < WIDTH; j++) {
            printf(grid[i][j] ? "■ " : "· ");
        }
        printf("\n");
    }
    printf("\n");
}
// 计算邻居存活数量
int count_neighbors(bool grid[HEIGHT][WIDTH], int x, int y) {
    int count = 0;
    for (int i = -1; i <= 1; i++) {
        for (int j = -1; j <= 1; j++) {
            if (i == 0 && j == 0) continue; // 跳过自身
            int nx = (x + i + HEIGHT) % HEIGHT; // 使用模运算实现边界环绕
            int ny = (y + j + WIDTH) % WIDTH;
            if (grid[nx][ny]) count++;
        }
    }
    return count;
}
void life_game() {
    bool current[HEIGHT][WIDTH] = {0};
    bool next[HEIGHT][WIDTH] = {0};
    // --- 初始化一个有趣的模式 ("滑翔机") ---
    // 滑翔机
    int glider_x = 1, glider_y = 1;
    current[glider_x][glider_y + 1] = 1;
    current[glider_x + 1][glider_y + 2] = 1;
    current[glider_x + 2][glider_y] = 1;
    current[glider_x + 2][glider_y + 1] = 1;
    current[glider_x + 2][glider_y + 2] = 1;
    // 振荡器
    int blinker_x = 10, blinker_y = 10;
    current[blinker_x][blinker_y] = 1;
    current[blinker_x][blinker_y + 1] = 1;
    current[blinker_x][blinker_y + 2] = 1;
    printf("初始状态:\n");
    print_grid(current);
    for (int gen = 0; gen < GENERATIONS; gen++) {
        // 1. 计算下一代状态
        for (int i = 0; i < HEIGHT; i++) {
            for (int j = 0; j < WIDTH; j++) {
                int neighbors = count_neighbors(current, i, j);
                if (current[i][j]) {
                    // 存活规则
                    next[i][j] = (neighbors == 2 || neighbors == 3);
                } else {
                    // 死亡/复活规则
                    next[i][j] = (neighbors == 3);
                }
            }
        }
        // 2. 更新网格
        for (int i = 0; i < HEIGHT; i++) {
            for (int j = 0; j < WIDTH; j++) {
                current[i][j] = next[i][j];
            }
        }
        // 3. 打印并暂停
        printf("第 %d 代:\n", gen + 1);
        print_grid(current);
        sleep(1); // 暂停1秒,方便观察
    }
}
int main() {
    life_game();
    return 0;
}

妙趣解析: 生命游戏的魅力在于其涌现性,简单的局部规则(只看邻居)导致了复杂的全局行为,你输入一堆随机或有序的“活细胞”,观察它们如何演化、碰撞、形成稳定的结构或周期性的振荡,这就像我们宇宙的规律,简单的物理定律演化出了生命和智慧,在C语言中实现它,需要小心处理边界条件(这里用了环绕边界),并确保在计算下一代时,不能直接修改当前正在计算的网格。


约瑟夫环 - 数学与生存游戏

趣味点: 一个著名的古罗马传说故事,一群士兵被包围,决定通过一种自杀式的方式来决定谁去投降,他们排成一个圈,从某个指定的人开始报数,数到 k 的那个人就自杀,然后从下一个人重新开始报数,直到所有人都自杀,问题是:给定了 nk,你站哪个位置才能活到最后?

问题: n 个人围成一圈,编号从 0n-1,从第 0 个人开始报数,数到 k-1 的那个人出局,然后从下一个人重新开始报数,直到所有人都出局,求最后剩下的人的初始编号。

C语言实现: 这个问题可以用递归和循环两种方式解决,循环解法更高效。

#include <stdio.h>
#include <stdlib.h>
/**
 * @brief 使用循环解决约瑟夫环问题
 * @param n 总人数
 * @param k 报数的步长
 * @return 最后剩下的人的初始编号
 */
int josephus(int n, int k) {
    int winner = 0; // 当 n=1 时,胜利者是 0
    // 从 2 个人开始,递推到 n 个人
    for (int i = 2; i <= n; i++) {
        winner = (winner + k) % i;
    }
    return winner;
}
int main() {
    int n, k;
    printf("请输入总人数 n: ");
    scanf("%d", &n);
    printf("请输入报数步长 k: ");
    scanf("%d", &k);
    int survivor = josephus(n, k);
    printf("在 %d 个人中,每次数到 %d 的人,最后活下来的人是初始编号为 %d 的那个人,\n", n, k, survivor);
    // 一个有趣的例子
    printf("\n一个经典的例子:41个人,数到3的人出局,最后是第 %d 号活下来,\n", josephus(41, 3));
    return 0;
}

妙趣解析: 这个算法的解法非常巧妙,它不是去模拟每一次淘汰,而是通过数学递推直接找到答案。f(n, k) 表示 n 个人时胜利者的位置,它的递推关系是 f(n, k) = (f(n-1, k) + k) % n

  • 含义:n-1 个人时,胜利者的位置是 f(n-1, k),现在我们增加了一个人(变成了 n 个人),相当于整个序列向后移动了 k 个位置,所以新的胜利者位置是 f(n-1, k) + k,因为是环,所以要取模 n
  • 基础: 当只有1个人时 (n=1),胜利者显然是位置 0。 代码中的 for 循环就是从基础开始,一步步计算出 n 个人的结果,这种不模拟过程而直接计算结果的思路,是算法优化的一个典范。

这三个算法各有千秋:

  • 汉诺塔 展示了递归的优雅与力量,将复杂问题分解为同类子问题。
  • 生命游戏 展示了简单规则如何催生复杂系统,是涌现性的绝佳例子。
  • 约瑟夫环 展示了数学建模递推思想的威力,让我们跳脱出模拟的束缚。

希望这些例子能让你感受到算法的魅力!算法不仅仅是解决编程问题的工具,更是逻辑思维、数学之美和创造力的结晶。

-- 展开阅读全文 --
头像
dede s网站地图如何调取文章?
« 上一篇 02-09
dede js判断当前页面
下一篇 » 02-09

相关文章

取消
微信二维码
支付宝二维码

目录[+]