C语言解决《汉诺塔问题》!详细思路+源码分享
Posted 一起学编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言解决《汉诺塔问题》!详细思路+源码分享相关的知识,希望对你有一定的参考价值。
每天一个C语言小项目,提升你的编程能力!
汉诺塔问题源自印度一个古老的传说
印度教的“创造之神”梵天创造世界时做了 3 根金刚石柱,其中的一根柱子上按照从小到大的顺序摞着 64 个黄金圆盘。梵天命令一个叫婆罗门的门徒将所有的圆盘移动到另一个柱子上,移动过程中必须遵守以下规则:
(1)每次只能移动柱子最顶端的一个圆盘;
(2)每个柱子上,小圆盘永远要位于大圆盘之上。
在汉诺塔问题中,当圆盘个数不大于 3 时,多数人都可以轻松想到移动方案,随着圆盘数量的增多,汉诺塔问题会越来越难。也就是说,圆盘的个数直接决定了汉诺塔问题的难度,解决这样的问题可以尝试用分治算法,将移动多个圆盘的问题分解成多个移动少量圆盘的小问题,这些小问题很容易解决,从而可以找到整个问题的解决方案。
编译环境:Visual Studio 2019/2022,EasyX插件
这是经典问题汉诺塔的解题演示动画,代码如下:
#include <graphics.h>
#include <conio.h>
#include <stdio.h>
#define MAX 64 // 圆盘的最大数目
#define NULL 0
// 定义栈
struct STKNODE
int a[4];
;
struct STK
STKNODE* stack[MAX];
int top;
;
// 定义全局变量
STK s[3]; // 声明三个栈,分别代表一号二号三号钢针上圆盘的状态
int v = 5; // 调整速度
// 函数声明
void Initstk(STK* s); // 初始化栈
void Hannoi(int n, char a, char b, char c); // 汉诺塔递归
void start(); // 开始画面
void Move(int n, char a, char b); // 移动过程
int switchab(char a); // 返回钢针号
void adjust(); // 调整速度暂停
// 主函数
void main()
int n, ta[4] = 115, 500, 285, 518; // 第一个圆盘的位置
printf("尽量小于16\\n"); // 因为大于十六时就会显示有误,但程序可以正常运行
printf("请输入汉诺塔的层数(1~64):");
scanf("%d", &n);
STKNODE** p;
p = (STKNODE**)malloc(n * sizeof(STKNODE **)); // 声明一个元素为 n 个的动态 STKNODE 型指针数组
for (int i2 = 0; i2 < n; i2 ++)
p[i2] = (STKNODE *)malloc(sizeof(STKNODE)); // 为每一个指针申请空间
Initstk(&s[0]);
Initstk(&s[1]);
Initstk(&s[2]); // 将三个栈初始化
start(); // 呈现开始画面
setfillcolor(GREEN); // 圆盘的颜色
for (int i=0; i < n; i++)
ta[0] += 5;
ta[1] -= 20;
ta[2] -= 5;
ta[3] -= 20;
solidrectangle(ta[0], ta[1], ta[2], ta[3]); // 画出n个从大到小一次叠放的黄色圆盘
++s[0].top; // 进栈
for (int i1 = 0; i1 < 4; i1++)
p[i]->a[i1] = ta[i1];
s[0].stack[s[0].top] = p[i]; // 记录每个矩形的位置,top为圆盘的个数
Hannoi(n, 'a', 'b', 'c'); // 汉诺塔递归函数
system("pause");
printf("\\t\\t\\t\\tGame Over!\\n");
///
// 函数定义
// 汉诺塔的递归
void Hannoi(int n, char a, char b, char c)
if(n == 1)
Move(1, a, c);
else
Hannoi(n-1, a, c, b);
Move(n, a, c);
Hannoi(n-1, b, a, c);
// 栈的初始化
void Initstk(STK *s)
int i;
s->top = 0;
for (i = 0; i <= MAX; i++)
s->stack[++s->top] = NULL;
s->top = 0;
// 移动过程
void Move(int n, char a, char b)
int i3, i4 = 0, i5 = 0;
i3 = b - a; // 目的钢针与源钢针的位置差值
i4 = switchab(a); // 源钢针钢针号
i5 = switchab(b); // 目的钢针号
STKNODE *q1, *q0; // 两个中间结点用于源栈和目的栈间的值得传递,q1为目的栈,q0为源栈
q1 = (STKNODE *)malloc(sizeof(STKNODE));
q0 = (STKNODE *)malloc(sizeof(STKNODE));
// 源栈与目的栈值的传递
q0 = s[i4].stack[s[i4].top];
++s[i5].top; // 进栈
q1->a[0] = q0->a[0] + i3 * 200;
q1->a[1] = 500 - s[i5].top * 20;
q1->a[2] = q0->a[2] + i3 * 200;
q1->a[3] = 500 - s[i5].top * 20 + 18;
s[i5].stack[s[i5].top] = q1;
--s[i4].top; // 出栈
// 向上运动
while (q0->a[1] >= 100)
setfillcolor(GREEN);
solidrectangle(q0->a[0], q0->a[1], q0->a[2], q0->a[3]);
adjust(); // 调整函数
Sleep(10 * v); // 暂停(ms)
setfillcolor(WHITE);
solidrectangle(q0->a[0], q0->a[1], q0->a[2], q0->a[3]);
setlinecolor(RED);
line((q0->a[0] + q0->a[2]) / 2, q0->a[1], (q0->a[0] + q0->a[2]) / 2, q0->a[3]); // 重新画上被擦掉原有的红线
q0->a[1] -= 10;
q0->a[3] -= 10;
// 向左或右运动,与 i3 的正负有关
while (q0->a[2] != q1->a[2])
setfillcolor(GREEN);
solidrectangle(q0->a[0], q0->a[1], q0->a[2], q0->a[3]);
adjust();
Sleep(10 * v);
setfillcolor(WHITE);
solidrectangle(q0->a[0], q0->a[1], q0->a[2], q0->a[3]);
if (i3 < 0) // i3<0向左移
q0->a[0] -= 20;
q0->a[2] -= 20;
else // i3>0向右移
q0->a[0] += 20;
q0->a[2] += 20;
// 向下运动
while (q0->a[3] <= q1->a[3])
setfillcolor(GREEN);
solidrectangle(q0->a[0], q0->a[1], q0->a[2], q0->a[3]);
adjust();
Sleep(10 * v);
setfillcolor(WHITE);
solidrectangle(q0->a[0], q0->a[1], q0->a[2], q0->a[3]);
setlinecolor(RED);
if (q0->a[1] > 100) // 重画被擦掉的红线
line((q0->a[0] + q0->a[2]) / 2, q0->a[1], (q0->a[0] + q0->a[2]) / 2, q0->a[3]);
q0->a[1] += 10;
q0->a[3] += 10;
// 在目的钢针上的相应位置绘制出黄色矩形块
setfillcolor(GREEN);
solidrectangle(q1->a[0], q1->a[1], q1->a[2], q1->a[3]);
// 绘制开始界面
void start()
// 初始化画面大小
initgraph(800, 650);
// 背景设为白色
setbkcolor(WHITE);
// 用白色填充整个画面
cleardevice();
// 绘制彩虹,形成一道彩虹,摘自 easyx 帮助文档示例程序
float H, S, L;
H = 0; // 色相
S = 1; // 饱和度
L = 0.5f; // 亮度
setlinestyle(PS_SOLID, 2); // 设置线宽为 2
for(int r = 600; r > 544; r--)
H += 5;
setlinecolor( HSLtoRGB(H, S, L) );
circle(750, 900, r);
// 说明
settextstyle(50, 0, "华文楷体");
settextcolor(RED);
outtextxy(200, 150, "汉诺塔移动动画");
settextstyle(20, 0, "黑体");
outtextxy(600, 200, "BY:Ronald");
outtextxy(500, 200, "版本V1.1");
settextstyle(50, 0, "黑体");
settextcolor(GREEN);
outtextxy(200, 350, "随便按一个键开始吧!");
// 检测键盘敲击
getch();
// 清空开始界面
cleardevice();
// 绘制运动画面的的环境
setlinecolor(RED);
// 三根红色线段作为钢针
line(400, 110, 400, 500);
line(600, 110, 600, 500);
line(200, 110, 200, 500);
// 长方体形的底座
setfillcolor(LIGHTGRAY);
fillrectangle(80, 501, 720, 510);
// 暂停按钮
solidrectangle(360, 540, 440, 580);
settextstyle(30, 0, "黑体");
settextcolor(GREEN);
outtextxy(370, 550, "暂停");
settextstyle(20, 0, "宋体");
settextcolor(RED);
outtextxy(300, 580, "鼠标暂停后请按空格继续");
// 加速按钮
solidrectangle(160, 540, 240, 580);
settextstyle(30, 0, "黑体");
settextcolor(GREEN);
outtextxy(170, 550, "加速");
settextstyle(20, 0, "宋体");
settextcolor(RED);
outtextxy(170, 580, "请按 d");
// 减速按钮
solidrectangle(560, 540, 640, 580);
settextstyle(30, 0, "黑体");
settextcolor(GREEN);
outtextxy(570, 550, "减速");
settextstyle(20, 0, "宋体");
settextcolor(RED);
outtextxy(570, 580, "请按 a");
// 说明
settextstyle(50, 0, "宋体");
settextcolor(GREEN);
outtextxy(10, 10, "正在进行中请欣赏:");
// 判断目的钢针与源钢针的钢针号返回钢针号
int switchab(char a)
switch (a)
case 'a':
return 0;
case 'b':
return 1;
case 'c':
return 2;
default:
return 0;
// 调整函数,实现加速,减速,暂停
void adjust()
char f; // 接收键盘敲进去的按钮和鼠标点击时赋予的变化值
// 用 f 接受键盘的键入值
if(kbhit())
f = getch();
// 检测鼠标消息
if (MouseHit()==true)
// 接收鼠标消息
MOUSEMSG Mouse;
Mouse = GetMouseMsg();
// 响应鼠标消息
if (Mouse.x >= 360 && Mouse.x <= 440 && Mouse.y >= 540 && Mouse.y <= 580 && Mouse.mkLButton)
f = ' ';
if (Mouse.x >= 160 && Mouse.x <= 240 && Mouse.y >= 540 && Mouse.y <= 580 && Mouse.mkLButton)
f = 'd';
if (Mouse.x >= 560 && Mouse.x <= 640 && Mouse.y >= 540 && Mouse.y <= 580 && Mouse.mkLButton)
f = 'a';
// 作用于动画
switch(f)
// 暂停
case ' ':
// 用‘继续’覆盖‘暂停’
settextstyle(30, 0, "黑体");
settextcolor(GREEN);
outtextxy(370, 550, "继续");
getch();
// 继续后变回显示‘暂停’
settextstyle(30, 0, "黑体");
settextcolor(GREEN);
outtextxy(370, 550, "暂停");
break;
// 减速
case 'a':
// 当被点击时,‘减速’位置震动一下
setfillcolor(LIGHTGRAY);
solidrectangle(560, 540, 640, 580);
settextstyle(30, 0, "黑体");
settextcolor(GREEN);
outtextxy(575, 545, "减速");
Sleep(30);
// 减速
v++;
// 回原位
setfillcolor(LIGHTGRAY);
solidrectangle(560, 540, 640, 580);
settextstyle(30, 0, "黑体");
settextcolor(GREEN);
outtextxy(570, 550, "减速");
break;
// 加速
case 'd':
setfillcolor(LIGHTGRAY);
solidrectangle(160, 540, 240, 580);
settextstyle(30, 0, "黑体");
settextcolor(GREEN);
outtextxy(165, 545, "加速");
Sleep(30);
setfillcolor(LIGHTGRAY);
solidrectangle(160, 540, 240, 580);
settextstyle(30, 0, "黑体");
settextcolor(GREEN);
outtextxy(170, 550, "加速");
// 加速
v--;
// v 最小为1
if (v <= 0)
v = 1;
break;
default:
break;
f = 'r'; // f 初始化为 r
FlushMouseMsgBuffer(); // 清空鼠标消息
大家赶紧去动手试试吧!
此外,我也给大家分享我收集的其他资源,从最零基础开始的教程到C语言C++项目案例,帮助大家在学习C语言的道路上披荆斩棘!
整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!
欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!(↓↓↓↓↓↓)
以上是关于C语言解决《汉诺塔问题》!详细思路+源码分享的主要内容,如果未能解决你的问题,请参考以下文章