线性结构实验 —— 堆栈、队列(汉诺塔的非递归实现)详细过程
线性结构实验 —— 堆栈、队列(汉诺塔的非递归实现)
一、 实验目的
- 熟练掌握堆栈、队列的两种存储结构实现方式及操作。
- 练习使用堆栈、队列结构解决问题的能力。
- 通过算法分析掌握不同存储结构的操作特点。
二、 实验内容和要求
问题描述
汉诺塔的非递归实现借助堆栈以非递归(循环)方式求解汉诺塔的问题(n, a, b, c),即将N个盘子从起始柱(标记为“a”)通过借助柱(标记为“b”)移动到目标柱(标记为“c”),并保证每个移动符合汉诺塔问题的要求。
输入格式
输入为一个正整数N,即起始柱上的盘数。
输出格式
每个操作(移动)占一行,按柱1 -> 柱2的格式输出。
输入样例
3
输出样例:
a -> c a -> b c -> b a -> c b -> a b -> c a -> c
三、算法分析
1. 主流程设计
int main { 输入起始柱上的盘数; 通过借助柱移动盘子到目标柱; 输出盘子从起始柱通过借助柱移动到目标柱的详细步骤; return 0; }
2. ADT定义
struct Node //将栈内元素放入一个结构体中 { char start; //起始柱 char mid; //借助柱 char goal; //目标柱 int num; //起始柱上的盘数 }; typedef int Position; //栈顶位置 typedef struct Node ElementType; //将堆栈的元素类型具体化 //typedef enum { false, true } bool; /*堆栈的顺序表定义*/ typedef struct SNode* PtrToSNode; struct SNode { ElementType* data; //存储元素的数组 Position Top; //堆栈的栈顶指针 int Maxsize; //堆栈的最大容量 }; typedef PtrToSNode Stack; Stack CreateStack(); bool IsFull(Stack S); bool push(Stack S, int n, char a, char b, char c); bool IsEmpty(Stack S); ElementType pop(Stack S);
3. 移动流程分析
设计思路:首先起始柱上除了底盘的其他盘子,需要通过目标柱移动到借助柱上,将此时的借助柱看作“目标柱”,将此时的目标柱看作“借助柱”压栈,栈满,不再执行下一次压栈(之后也是如此),然后将起始柱的底盘移动到目标柱上,最后将借助柱上的盘子通过起始柱移动到目标柱。
while(堆栈不为空) { 弹出栈顶元素; if (元素的num为1) 直接输出; else /*移动柱子相当于移动柱子上在最上面的盘子*/ 当最底下的大盘到达目标柱,而其余的在借助柱时,压栈将借助柱和目标柱调换位置,达到盘子到达目标柱的目的; 当只剩下一个盘子没有到目标柱时,压栈将目标柱和起始柱调换位置,达到盘子到达目标柱的目的; 将盘子除了最底下的大盘移动到借助柱时,压栈将此时的借助柱作为目标柱,目标柱作为借助柱; } 当操作数处理完毕后,运算符栈弹空;
4. 算法示例
当 n=3 即只有三个盘子时,如下为具体移动步骤图画展示:
下图为堆栈内的详细情况:
5. 算法示例(假设盘子的个数为3)
/*借助栈的非递归实现*/#include <stdio.h> #include<stdlib.h> struct Node //将栈内元素放入一个结构体中 { char start; //起始柱 char mid; //借助柱 char goal; //目标柱 int num; //起始柱上的盘数 }; typedef int Position; //栈顶位置 typedef struct Node ElementType; //将堆栈的元素类型具体化 //typedef enum { false, true } bool; /*堆栈的顺序表定义*/ typedef struct SNode* PtrToSNode; struct SNode { ElementType* data; //存储元素的数组 Position Top; //堆栈的栈顶指针 int Maxsize; //堆栈的最大容量 }; typedef PtrToSNode Stack; bool push(Stack S, int n, char a, char b, char c); ElementType pop(Stack S); /* 生成空堆栈,其最大长度为 Maxsize */ Stack CreateStack() { Stack S; S = (Stack)malloc(sizeof(struct SNode)); S->Maxsize = 1000; S->data = (struct Node*)malloc(S->Maxsize * sizeof(struct Node)); S->Top = -1; return S; } /* 判断堆栈S是否已满。 若S中元素个数等于 Maxsize 时返回 TRUE 否则返回 FALSE */ bool IsFull(Stack S) { return (S->Top == S->Maxsize - 1); } /* 将元素压入堆栈。 若堆栈已满,返回 FALSE 否则将数据元素插入到堆栈 S 栈顶,返回 TRUE */ bool push(Stack S, ElementType X) { if (IsFull(S)) { printf("Full Stack.\n"); return false; } else { /*S->Top++; S->data[S->Top].start = a; S->data[S->Top].mid = b; S->data[S->Top].goal = c; S->data[S->Top].num = n;*/ S->data[++(S->Top)] = X; return true; } } /* 判断堆栈 S 是否为空。 若是返回 TRUE 否则返回 FALSE */ bool IsEmpty(Stack S) { return (S->Top == -1); } /* 删除并返回栈顶元素。 若堆栈为空,返回错误信息 否则将栈顶数据元素从堆栈中删除并返回 */ ElementType pop(Stack S) { if (IsEmpty(S)) { printf("Empty Stack.\n"); ElementType e; e.num = -1; return e; } else return S->data[S->Top--]; } /*借助栈的非递归实现*/ int main() { int n; scanf_s("%d", &n); Stack S; S = CreateStack(); struct Node m; //相当于ElementType m; m.start = 'a'; m.mid = 'b'; m.goal = 'c'; m.num = n; push(S, m); //将初始状态压栈 while (S->Top >= 0) //堆栈不空 { m = pop(S); //弹出栈顶元素 if (m.num == 1) { printf("%c -> %c\n", m.start, m.goal); } else { /* n-1个盘,从辅助柱,借助源柱,移动到目标柱 */ //push(S, m.num - 1, m.mid, m.start, m.goal); push(S, S->data[S->Top]); S->data[S->Top].num = m.num - 1; S->data[S->Top].start = m.mid; S->data[S->Top].mid = m.start; S->data[S->Top].goal = m.goal; /* 最后一个盘,从源柱,借助辅助柱,移动到目标柱 */ //push(S, 1, m.start, m.mid, m.goal); push(S, S->data[S->Top]); S->data[S->Top].num = 1; S->data[S->Top].start = m.start; S->data[S->Top].mid = m.mid; S->data[S->Top].goal = m.goal; /* n-1个盘,从源柱,借助目标柱,移动到辅助柱 */ //push(S, m.num - 1, m.start, m.goal, m.mid); push(S, S->data[S->Top]); S->data[S->Top].num = m.num - 1; S->data[S->Top].start = m.start; S->data[S->Top].mid = m.goal; S->data[S->Top].goal = m.mid; } } return 0; }
/*借助栈的递归实现*/ int main() { int n; scanf_s("%d", &n); Stack S; S = CreateStack(); struct Node m; push(S, n, 'a', 'b', 'c'); //将初始状态压栈 while (S->Top >= 0) { m = pop(S); if (m.num == 1) { printf("%c -> %c\n", m.start, m.goal); } else { /* n-1个盘,从借助柱,借助起始柱,移动到目标柱 */ push(S, m.num - 1, m.mid, m.start, m.goal); /* 最后一个盘,从起始柱,借助借助柱,移动到目标柱 */ push(S, 1, m.start, m.mid, m.goal); /* n-1个盘,从起始柱,借助目标柱,移动到借助柱 */ push(S, m.num - 1, m.start, m.goal, m.mid); } } return 0; }
算法分析、堆栈分析图等均为原创作品,欢迎指正!