n!非递归算法的设计与实现
汉诺塔问题的非递归算法设计及可视化实现

汉诺塔问题的非递归算法设计及可视化实现彭伟【摘要】This essay introduces the classic recursive algorithm of the famous Hanoi,and then carries out further analysis and study on the algorithm based on the binary recursive tree to get a non-recursive solution without using the stack technology.Finally,designing procedures of development environment are visualized in NET,using recursive and non-recursive algorithm respectively to solve Hanoi of specified scale,with the moving effects of disc being dynamically simulated.%讨论了汉诺塔问题的经典递归算法,并基于二叉递归树对算法进行研究,得出了一种不使用堆栈技术的非递归解法,最后在.NET可视化开发环境下设计程序,分别用递归与非递归算法求解指定规模的汉诺塔问题,动态模拟了求解过程中盘片的移动效果。
【期刊名称】《武汉船舶职业技术学院学报》【年(卷),期】2011(010)006【总页数】6页(P55-59,72)【关键词】汉诺塔;二叉树;递归,非递归;可视化;模拟【作者】彭伟【作者单位】武汉城市职业学院,湖北武汉430064【正文语种】中文【中图分类】TP301汉诺塔游戏最早于19世纪出现在欧洲,它展示了一项正在婆罗门寺庙进行的任务:在创世之初,牧师被授予一个铜盘,上面有3根钻石针,在第1根针上叠放着64个碟片,每一个都比它下面的稍小一些,这位牧师被安排了一项任务,那就是将所有的碟片从第1根针移到第3根针,但要遵循的规则是:一次只能移动一个碟片,并且不允许将任何一个碟片放在比它小的碟片上面。
数据结构课程设计报告-迷宫求解(递归与非递归)

《数据结构》课程设计迷宫求解班级:学号:姓名:指导老师:迷宫求解1、问题描述输入一个任意大小的迷宫数据,用递归和非递归两种方法求出一条走出迷宫的路径,并将路径输出。
2、设计思路从入口出发,按某一方向向前探索,若能走通并且未走过,即某处可以到达,则到达新点,否则试探下一个方向;若所有的方向均没有通路,则沿原路返回前一点,换下一个方向再继续试探,直到找到一条通路,或无路可走又返回入口点。
在求解过程中,为了保证在到达某一点后不能向前继续行走(无路)时,能正确返回前一点以便继续从下一个方向向前试探,则需要用一个栈(递归不需要)保存所能够到达的每一点的下标及从该点前进的方向。
设迷宫为m行n列,利用maze[m][n]来表示一个迷宫,maze[i][j]=0或1;其中:0表示通路,1表示不通,当从某点向下试探时,中间点有四个方向可以试探,而四个角点有两个方向,其他边缘点有三个方向,为使问题简单化,用maze[m+2][n+2]来表示迷宫,而迷宫的四周的值全部为1,这样做使问题简单了,每个点的试探方向全部为4,不用再判断当前点的试探方向有几个。
3、数据结构设计在上述表示迷宫的情况下,每个点有4个方向去试探,如当前点的坐标(x,y),与其相邻的4个点的坐标都可根据与该点的相邻方位而得到。
因为出口在(m,n),因此试探顺序规定为:从当前位置向前试探的方向为从正东沿顺时针方向进行。
为了简化问题,方便求出新点的坐标,将从正东开始沿顺时针进行的4个方向的坐标增量放在一个结构数组move[4]中,在move数组中,每个元素有两个域组成,x为横坐标增量,y为纵坐标增量。
这样对move设计会很方便地求出从某点(x,y)按某一方向v(0<=v<=3)到达的新点(i,j)的坐标:i=x+move[v].x;j=y+move[v].y;当到达了某点而无路可走时需返回前一点,再从前一点开始向下一个方向继续试探。
因此,压入栈中的不仅是顺序到达的各点的坐标,而且还要有从前一点到达本点的方向。
n皇后问题非递归回溯算法

n皇后问题非递归回溯算法一、问题描述n皇后问题是一个经典的回溯算法问题,其目标是在一个n*n的棋盘上放置n个皇后,使得它们互相之间不能攻击。
即任意两个皇后都不能处于同一行、同一列或者同一斜线上。
二、问题分析1. 回溯算法思路回溯算法是一种通过穷举所有可能情况来找到所有解的算法。
在遍历过程中,如果发现当前状态不符合要求,则回溯到上一个状态进行下一步尝试。
2. 非递归实现传统的n皇后问题解法大多采用递归实现,但是递归实现会存在栈溢出等问题。
因此,我们可以采用非递归实现方式来避免这些问题。
三、算法设计1. 状态表示我们可以用一个数组board来表示当前棋盘状态,其中board[i]表示第i行皇后所在的列数。
2. 状态转移在每一行中,我们依次尝试将皇后放置在每一个位置上。
如果当前位置不符合要求,则继续尝试下一个位置;如果当前位置符合要求,则将该位置标记为已占用,并将当前状态入栈进入下一层搜索。
当搜索到第n层时,说明找到了一组解,将该解保存并回溯到上一层继续搜索。
3. 剪枝优化为了减少不必要的搜索,我们可以采用以下两种剪枝策略:(1)列冲突剪枝:如果当前位置所在列已经有皇后,则直接跳过该位置。
(2)斜线冲突剪枝:如果当前位置所在的左上、右上斜线已经有皇后,则直接跳过该位置。
四、代码实现1. 初始化首先,我们需要定义一个栈来保存状态,并将第一行的所有位置都尝试一遍。
同时,我们还需要定义一个二维数组visited来保存哪些列和哪些斜线已经被占用。
```pythondef solveNQueens(n: int) -> List[List[str]]:res = []stack = []visited = [[False] * n for _ in range(3)]for i in range(n):stack.append([i])visited[0][i] = Truevisited[1][i - 0 + n - 1] = Truevisited[2][i + 0] = True```2. 回溯搜索在搜索过程中,我们不断取出栈顶状态进行扩展。
非递归算法

非递归算法什么是非递归算法?非递归算法是指在计算机程序开发过程中,不使用递归的算法。
递归是指一个自身定义中包含对自身的引用的函数或过程,它可以方便地处理某些问题,但也有缺点,例如产生许多不必要的重复计算,当递归深度过大时容易导致栈溢出等等。
非递归算法常用于对较大数据集的处理,以及在处理树和图等数据结构时。
非递归算法的特点非递归算法具有以下几个特点:1. 显式地控制计算过程非递归算法通过自己的堆栈管理计算过程,这为程序员提供了明确的控制,可以在需要的时候随时中断和恢复计算过程,以及对计算过程进行调试和分析。
这使得非递归算法具有更好的可读性和可维护性。
2. 避免了递归深度过大的问题在递归算法中,深度过大的调用链常导致栈内存的耗尽,从而导致程序的崩溃。
而在非递归算法中,由于递归调用被替换成了循环结构,因此不会产生调用链太长的问题。
3. 速度更快在含有大量数据的情况下,递归算法的效率通常低于循环算法。
而在非递归算法中,通过循环结构的重复执行,可以更快地完成计算。
非递归算法的应用非递归算法适用于解决一些常见问题,例如查找、排序、遍历和优化等等。
下面将对一些常见的应用进行介绍。
1. 查找在查找算法中,非递归算法广泛用于二分查找。
二分查找是一种思想简单、效率高的查找算法。
它的原理是:在一个已排好序的数组中,首先确定一个中间值,再将待查找数据与中间值比较,如果相等就返回,如果小于中间值就查找左半边,否则查找右半边。
通过不断缩小查找的区间,最终可以找到目标元素。
2. 排序在排序算法中,非递归算法可以使用迭代实现快速排序、归并排序以及选择排序,这些算法在大数据集上效果显著。
相比递归算法,非递归算法能够避免频繁的函数调用、内存占用增多的问题,同时也能更好地控制算法的执行。
3. 遍历树和图是数据结构中的两个经典问题,需要对其进行遍历。
遍历算法中,深度优先搜索(DFS)和广度优先搜索(BFS)是两种常见的算法。
在处理大规模图像等需要处理大量节点和连通性的情况下,非递归算法可以更好地应对,而递归算法则容易栈溢出。
c语言递归函数的非递归实现

c语言递归函数的非递归实现递归函数是指在函数内部调用自身的函数,这种函数调用方式能够简化问题的表达和解决。
但是在实际编程中,递归函数可能会消耗大量的系统资源,因此有时候需要将递归函数改写为非递归形式,以减少资源消耗。
接下来将介绍如何在C 语言中实现递归函数的非递归形式。
一种常见的方法是使用栈来模拟递归函数的调用过程。
在递归函数中,每次调用都会将函数的参数、局部变量和返回地址保存在栈中,而函数执行完毕后会从栈中取出这些信息以便返回上一层调用。
因此,我们可以自己创建一个栈来模拟这个过程。
首先,我们需要定义一个结构体来表示栈的节点,包括函数的参数、局部变量和返回地址等信息。
接着,我们可以创建一个栈,用来保存这些节点。
在非递归函数中,我们首先将函数的初始参数压入栈中,然后开始一个循环,每次循环中执行函数的一次递归调用。
在调用函数时,我们将函数的参数和局部变量保存在一个节点中,然后将这个节点压入栈中。
当函数执行完毕后,我们从栈中弹出这个节点,取出其中的参数和局部变量,以便返回上一层调用。
通过这种方式,我们可以在不使用递归的情况下实现递归函数的功能。
这样一来,我们可以减少系统资源的消耗,提高程序的效率。
当然,这种方法在一些情况下可能会增加代码的复杂性,但在需要避免递归调用的情况下,这是一种可行的替代方案。
总的来说,非递归实现递归函数的方法是使用栈来模拟函数调用的过程,将函数的参数、局部变量和返回地址保存在栈中,以便在函数执行完毕后能够返回上一层调用。
通过这种方式,我们可以在不消耗过多系统资源的情况下,实现递归函数的功能。
这种方法在一些情况下可能会增加代码的复杂性,但是在需要避免递归调用的情况下,是一种有效的解决方案。
python汉诺塔非递归算法

python汉诺塔非递归算法如何使用Python编写非递归的汉诺塔算法首先,让我们回顾一下汉诺塔问题的背景。
汉诺塔是一个经典的数学问题,涉及到递归和栈的使用。
问题的目标是将一组不同大小的圆盘从一个柱子移动到另一个柱子,其中有三个柱子可供选择。
在移动过程中,您必须遵守以下规则:1. 您只能移动一个圆盘,并且只能将较小的圆盘放在较大的圆盘上。
2. 您只能在三个柱子之间移动圆盘。
3. 将所有圆盘从一个柱子移动到另一个柱子上是成功的。
使用递归算法可以很容易地解决这个问题。
然而,递归算法在处理大量圆盘时可能会导致递归深度过大,从而消耗大量的内存和计算时间。
因此,我们需要采用非递归的方法来解决这个问题。
接下来,让我们一步一步地介绍如何使用Python编写非递归的汉诺塔算法:步骤1: 定义一个Stack类首先,我们需要定义一个Stack类来模拟栈的行为。
在Python中,可以使用列表来实现这个类。
我们可以使用列表的append()方法将元素添加到栈顶,使用pop()方法从栈顶取出元素,使用isEmpty()方法检查栈是否为空,以及使用size()方法获取栈的大小。
下面是Stack类的代码实现:class Stack:def __init__(self):self.items = []def isEmpty(self):return len(self.items) == 0def push(self, item):self.items.append(item)def pop(self):return self.items.pop()def size(self):return len(self.items)步骤2: 定义一个非递归的汉诺塔函数接下来,我们需要定义一个非递归的汉诺塔函数。
该函数的输入参数包括圆盘的数量、起始柱子、目标柱子和辅助柱子。
函数的实现思路如下:- 首先,将所有的圆盘按照倒序从起始柱子压入起始栈。
快速排序的非递归实现

快速排序的非递归实现快速排序是一种常见的排序算法,其时间复杂度为O(nlogn),在实际应用中经常被使用。
快速排序的非递归实现可以避免递归调用带来的额外开销,提高算法效率。
下面将详细介绍快速排序的非递归实现。
一、快速排序简介快速排序是一种基于比较的排序算法,它通过把待排数组分割成两部分,其中一部分的所有元素都比另一部分小,然后再对这两部分递归地进行同样的操作,直到整个序列有序。
快速排序的核心思想是选取一个基准元素(pivot),将待排数组中小于等于pivot的元素放到pivot左边,大于pivot的元素放到pivot右边。
这个过程称为划分(partition)。
划分完成后,我们就得到了以pivot为界限的两个子序列。
然后再对这两个子序列递归地进行划分和排序操作,最终得到整个序列有序。
二、非递归实现原理在传统的递归实现中,每次切割都会产生新的函数调用栈,并且需要保存每次调用时需要处理的数据和状态信息。
这些额外开销会占用大量内存和CPU资源,导致算法效率低下。
非递归实现通过使用栈来模拟递归调用过程,避免了函数调用栈的开销。
每次划分操作时,将左右子序列的起始和结束位置入栈,然后从栈中取出一个位置范围进行划分操作。
当栈为空时,排序完成。
三、非递归实现步骤1. 定义一个栈来保存待处理的子序列范围。
2. 将整个序列的起始和结束位置入栈。
3. 循环执行以下操作:a. 从栈中取出一个子序列范围进行划分操作。
b. 将划分后的左右子序列的起始和结束位置入栈(如果存在)。
4. 当栈为空时,排序完成。
四、非递归实现代码下面是快速排序的非递归实现代码:```pythondef quick_sort(array):stack = [(0, len(array) - 1)]while stack:left, right = stack.pop()if left >= right:continuepivot = partition(array, left, right)stack.append((left, pivot - 1))stack.append((pivot + 1, right))def partition(array, left, right):pivot = array[left]while left < right:while left < right and array[right] >= pivot: right -= 1array[left] = array[right]while left < right and array[left] <= pivot: left += 1array[right] = array[left]array[left] = pivotreturn left五、总结快速排序的非递归实现可以避免递归调用带来的额外开销,提高算法效率。
二叉树非递归创建的算法

二叉树非递归创建的算法二叉树是一种非常常用的数据结构,在计算机科学领域有着广泛的应用。
创建二叉树的算法有递归和非递归两种方式。
本文将介绍一种非递归的二叉树创建算法。
在二叉树的创建过程中,递归算法是最常见的方式。
但递归算法会使用到系统的函数调用栈,当二叉树的规模较大时,递归算法可能会导致栈溢出的问题。
为了避免这个问题,我们可以使用非递归的方式来创建二叉树。
非递归创建二叉树的算法主要借助于栈这种数据结构。
栈是一种后进先出(LIFO)的数据结构,我们可以利用栈的特性来模拟递归的过程。
具体步骤如下:1. 创建一个空栈,并将根节点入栈。
2. 循环执行以下步骤,直到栈为空:1. 出栈一个节点,作为当前节点。
2. 读取输入的值,创建一个新节点,并将其作为当前节点的左子节点。
3. 将新节点入栈。
4. 读取输入的值,创建一个新节点,并将其作为当前节点的右子节点。
5. 将新节点入栈。
通过以上步骤,我们可以按照先序(根左右)的顺序创建二叉树。
在每次循环中,我们都将当前节点出栈,并根据输入的值创建新节点,并将其入栈。
这样,我们就可以在非递归的方式下完成二叉树的创建。
下面我们通过一个具体的例子来演示上述算法的执行过程。
假设我们要创建一个如下所示的二叉树:```1/ \2 3/ \ \4 5 6```我们可以按照以下步骤来创建这个二叉树:1. 创建一个空栈,并将根节点入栈。
2. 循环执行以下步骤,直到栈为空:1. 出栈一个节点,作为当前节点。
初始时,当前节点为根节点1。
2. 读取输入的值,创建一个新节点,并将其作为当前节点的左子节点。
此时读取到的值为2,创建一个值为2的新节点,并将其设置为当前节点的左子节点。
3. 将新节点入栈。
4. 读取输入的值,创建一个新节点,并将其作为当前节点的右子节点。
此时读取到的值为3,创建一个值为3的新节点,并将其设置为当前节点的右子节点。
5. 将新节点入栈。
6. 出栈一个节点,作为当前节点。
此时出栈的节点为2,将其设置为当前节点。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
n!非递归算法的设计与实现1 课题描述尽管递归算法是一种自然且合乎逻辑的解决问题的方式,但递归算法的执行效率通常比较差。
因此在求解许多问题时常采用递归算法来分析问题,用非递归方法来求解问题;另外一些程序不支持递归算法来求解问题,所以我们都会用非递归算法来求解问题。
本次课程设计主要内容是:用非递归算法实现n!的计算,由于计算机中数据的存储范围有限,而又要求出尽可能大的n的阶乘的值,用数组构造n的运算结果的存储结构,用栈的存储方式,最后输出n!的运算结果。
本次课程设计的目的是:通过本次课程设计,可以使大家了解缓存中数据的存储范围,提高自学能力,增强团队合作意识。
2 需求分析本次n!非递归算法的课程设计中主要用到的知识有:数组、函数、栈,选择条件中的结构语句(if…else),和循环结构语句中的语句while()语句、do…while()语句和for()语句,选择语句if的运用。
对n!的非递归的算法,主要是运用非递归的算法实现n的阶乘。
限制条件:(1).要求的n必须是整数;(2). n的范围;(3). 数据类型和表数范围。
递归和非递归算法是相通的,递归是一种直接或间接调用自身的算法,而非递归不调用自身函数递推采用的是递归和归并法,而非递推只采用递归法。
递推法一般容易溢出,所以一般都采用递推法分析,而用非递推法设计程序。
将n定义为float型,便于查看n是否为整数;本次试验分为两个模块:(1).当n小于都等于12时,实现阶乘的模块m(n): 直接用sum*=i;实现求n的阶乘,相对简单,容易就算。
(2).当n大于12时,如果用long型结果就会溢出,所以实现阶乘需调用的模块f(n): 采用数组存放计算的结果,用队列输出运行结果。
由于计算结果较大,将结果除以数组最大存储位数,将高位结果存放在数组的起始地址上,将低位的结果存放在数组的末端地址上,最后采用队列输出运行结果。
(3).模块调用关系如图3.1所示图3.1 模块调用图4.1定义存储结构和部分代码#include<stdio.h>#define N 10000 /*12!=479001600;*/ //定义数组的长度为10000#define size 100000 //定义size,用于规定数组的最大存储位数void f(float n){ //当n大于12时调用函数f()long int a[N],i,j,length,k,up; //定义变量 a[],i,j,length,k,up}void m(float n){ ///当n小于等于12时调用函数m()long int i; //定义变量iint sum; //定义变量sum,用于存放求得阶乘的结果}void f(float n){long int a[N],i,j,length,k,up; //i,j为计数器,length为数组存储的长度a[0]=1600;a[1]=4790;length=2; //12!=479001600,初始化f[0]和f[1]以便于求解大于12的阶乘for(j=13;j<=n;j++){for(k=0,up=0;k<length;k++)a[k]*=j;for(k=0;k<length;k++){a[k]+=up;up=a[k]/size; // 计算向高位进的数值a[k]=a[k]%size; //计算当前位的数值}if(up) //判断是否需追加数组长度{length+=1;a[length-1]=up; //将进位的值存放到数组最后一位上}}printf("%5d!=",(int)n); //输出nprintf("%d",a[length-1]); //输出高位的运行结果for(i=length-2;i>=0;i--) //将运算结果的第二个最高位到最低位的值输出printf("%d",a[i]); }4.2 流程图主函数流程图见图4.1图4.1 主函数流程图子函数f(n)的流程图见图4.2图4.2 子函数f(n)的流程图子函数m(n)的流程图见图4. 3图4. 3子函数m(n)的流程图5 程序编码#include<stdio.h>#define N 10000 /*12!=479001600;*/ //定义数组的存储单元数#define size 100000 //定义size,用于求解数组的最大存储位数void f(float n){long int a[N],i,j,length,k,up;a[0]=1600;a[1]=4790;length=2; //12!=479001600for(j=13;j<=n;j++){for(k=0,up=0;k<length;k++)a[k]*=j;for(k=0;k<length;k++){a[k]+=up;up=a[k]/size; //进位的数值a[k]=a[k]%size; //当前位的数值}if(up){length+=1;a[length-1]=up;}}printf("%5d!=",(int)n); //输出nprintf("%d",a[length-1]);for(i=length-2;i>=0;i--)printf("%d",a[i]);} /void m(float n) {long int i;int sum;if(n){for(i=1,sum=1;i<=n;i++)sum*=i;printf("%d!=%d",(int)n,sum);}else printf("0!=1");}void main(){int sum,flog=1,g;float n;while(flog==1) //根据标签的值判断循环是否结束{printf("请输入整数n:"); //输入要求阶乘的数nscanf("%f",&n);while(n<0||n>999||n!=(int)n) //判断n是否合法{printf("输入错误,请重输入整数n:"); //如果输入不合法,重新输入nscanf("%f",&n);}if(n>12)f(n); //如果n大于12调用函数f(n)elsem(n); //如果n小于等于12调用函数m(n)printf("\n\n"); //换行printf("是否需要继续计算,输入1继续计算,输入0结束: "); //是否继续求n阶乘fflush(stdin);scanf("%d",&g);printf("\n");if(g==1) flog=1;else flog=0;}}6 程序调试与测试(1)当n=-3时,结果如图6.1图6.1当n=-3时,运行结果(2)当n=0时结果如图6.2图6.2当n=0时,运行结果(3)当n=12时,结果如图6.,3图6.3当n=12时,运行结果7 结果分析在执行函数的过程中,对上述提到的各种情况做了判断和提示,如:输入负数,系统会提示“输入错误,请重新输入:”;输入大于999的数,系统会提示“输入错误,请重新输入:”;输入小数,系统会提示“输入错误,请重新输入:”。
本次设计的函数,能求出较大整数的阶乘,能实现循环运算和退出功能。
算法的时间复杂度为:当n<=12时,O(n)=n;当n>12时,O(n)=n*length*length;算法的空间复杂度为:当n<=12时,O(n)=3≈1;当n>12时,O(n)=length;8 总结本次开学前两周是课程设计,尽管比起这种自由的设计我更喜欢上课,但是我知道我们所学的知识都是为了应用,而课程设计就是一个很好的检验我们能力的平台。
本次课程设计让我受益匪浅,本次的课程设计主要是对所学过的知识和一些没接触过的知识进行结合运用,不仅是巩固了自己所学的知识,而且把知识与实践相结合,使得自己在自学方面有所提高。
这学期我所做的课程设计是n!的非递归算法的实现,在做本次课程设计的时候,自己也相继遇到了很多问题,很多自己的不足之处也暴露了出来,比如:刚开始自己写的代码只能算到12的阶乘,但是因为知道了自己哪里有不足,所以可以针对不足去弥补:翻阅资料、和同学探讨,使得学到的东西更深刻,更透彻,所以本次课程设计使我对非递归算法和进位有了更好的理解。
经过这段时间的上机实践学习,我对数据结构和C语言有了更进一步的认识和了解,要想学好它要重在实践,要通过不断地练习和上机编程才能熟练地掌握它。
当然,在上机的同时也要有一定的C语言理论知识,这样才能使理论和实践相互促进,在这两方面都有所提高。
与此同时,我也认识到了查阅资料和团队的重要性。
资料为我们提供了很好的知识,我们没事时应该多翻阅相关资料使得我们的能力更进一步,当自己看不懂时可以和同学讨论,不仅增加了彼此的友谊同时而且使我们对知识的理解更深。
通过本次课程设计,我对非递归算法和进位都有了更深的了解,和更加熟练的应用。
虽然过去编写程序也经常用到递归,但是当时根本就不了解递归算法和非递归算法的优缺点,现在知道大多数程序采用递归算法来分析,而采用非递归算法来实现,因为递归算法容易溢出,非递归算法更节省空间。
在上机实践中,我发现了自己的基础还不是很扎实。
有些代码自己还是不能准确地写出来,查看资料的时候有的代码看不懂,有时候还会因为空间分配等问题造成程序错误,但是经过多次实践,一些小的错误自己已经可以很容易解决了,遇到一些较难的问题时,我还是要查看教材和其他的资料来帮助自己解决问题。
这种习惯极好地补充了我在程序设计中不足的知识。
这使我更深刻地体会到,不管学习那种编译语言,不仅要动脑,更要动手去做。
在以后的学习中,我会更加注重实践操作能力的培养,让自己的各方面能力都有所提高。