n!非递归算法的设计与实现
- 格式:doc
- 大小:364.21 KB
- 文档页数:12
汉诺塔问题的非递归算法设计及可视化实现彭伟【摘要】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个皇后,使得它们互相之间不能攻击。
即任意两个皇后都不能处于同一行、同一列或者同一斜线上。
二、问题分析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 语言中实现递归函数的非递归形式。
一种常见的方法是使用栈来模拟递归函数的调用过程。
在递归函数中,每次调用都会将函数的参数、局部变量和返回地址保存在栈中,而函数执行完毕后会从栈中取出这些信息以便返回上一层调用。
因此,我们可以自己创建一个栈来模拟这个过程。
首先,我们需要定义一个结构体来表示栈的节点,包括函数的参数、局部变量和返回地址等信息。
接着,我们可以创建一个栈,用来保存这些节点。
在非递归函数中,我们首先将函数的初始参数压入栈中,然后开始一个循环,每次循环中执行函数的一次递归调用。
在调用函数时,我们将函数的参数和局部变量保存在一个节点中,然后将这个节点压入栈中。
当函数执行完毕后,我们从栈中弹出这个节点,取出其中的参数和局部变量,以便返回上一层调用。
通过这种方式,我们可以在不使用递归的情况下实现递归函数的功能。
这样一来,我们可以减少系统资源的消耗,提高程序的效率。
当然,这种方法在一些情况下可能会增加代码的复杂性,但在需要避免递归调用的情况下,这是一种可行的替代方案。
总的来说,非递归实现递归函数的方法是使用栈来模拟函数调用的过程,将函数的参数、局部变量和返回地址保存在栈中,以便在函数执行完毕后能够返回上一层调用。
通过这种方式,我们可以在不消耗过多系统资源的情况下,实现递归函数的功能。
这种方法在一些情况下可能会增加代码的复杂性,但是在需要避免递归调用的情况下,是一种有效的解决方案。
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,将其设置为当前节点。
快速排序的非递归算法java语言快速排序是一种非常高效的排序算法,它基于分治的思想,通过将一个大问题分解为多个小问题并分别解决,最后将这些小问题的解合并起来得到整个问题的解。
快速排序的核心思想是通过选择一个基准元素,将比基准元素小的元素放在它的左边,将比基准元素大的元素放在它的右边,然后对左右两个子集分别进行递归排序。
快速排序的递归算法非常直观和容易理解,但是递归算法在处理大规模数据时会出现栈溢出的问题。
为了解决这个问题,我们可以使用非递归的方式来实现快速排序。
非递归的快速排序算法可以使用栈来模拟递归的过程,具体步骤如下:1.创建一个栈并将整个数组的起始索引和结束索引入栈。
2.当栈不为空时,重复以下步骤:-弹出栈顶索引对应的子数组。
-选择一个基准元素,将比基准元素小的元素放在它的左边,将比基准元素大的元素放在它的右边。
这里我们可以使用"partition"函数来实现。
-如果左子数组的长度大于1,将左子数组的起始索引和结束索引入栈。
-如果右子数组的长度大于1,将右子数组的起始索引和结束索引入栈。
3.当栈为空时,排序完成。
下面是使用Java语言实现的非递归快速排序算法:```javaimport java.util.Stack;public class QuickSort {public static void quickSort(int[] arr) {if (arr == null || arr.length == 0) {return;}Stack<Integer> stack = new Stack<>(); stack.push(0);stack.push(arr.length - 1);while (!stack.isEmpty()) {int end = stack.pop();int start = stack.pop();int pivotIndex = partition(arr, start, end); if (pivotIndex - 1 > start) {stack.push(start);stack.push(pivotIndex - 1);}if (pivotIndex + 1 < end) {stack.push(pivotIndex + 1);stack.push(end);}}}private static int partition(int[] arr, int start, int end) {int pivot = arr[end];int i = start - 1;for (int j = start; j < end; j++) {if (arr[j] <= pivot) {i++;swap(arr, i, j);}}swap(arr, i + 1, end);return i + 1;}private static void swap(int[] arr, int i, int j) { int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}public static void main(String[] args) {int[] arr = {9, 3, 5, 1, 7, 8, 2, 4, 6};quickSort(arr);for (int num : arr) {System.out.print(num + " ");}//输出结果: 1 2 3 4 5 6 7 8 9}}```在上述代码中,我们使用一个栈来模拟递归的过程。
快速排序的非递归算法
快速排序的非递归算法是一种利用栈实现的迭代算法。
以下是其中一种实现方式:
1.定义一个栈,用于存储需要进行排序的子数组的起始位置
和结束位置。
2.初始化栈,将整个数组的起始位置和结束位置入栈。
3.进入循环,直到栈为空: a. 弹出栈顶的起始位置和结束位
置。
b. 选取一个基准元素(通常选择起始位置或结束位置的元素)。
c. 将数组划分为两部分,使得比基准元素小的元素在左侧,比基准元素大的元素在右侧,并返回基准元素的位置。
d. 如果左侧部分有两个以上的元素,则将左侧部分的起始位置和结束位置入栈。
e. 如果右侧部分有两个以上的元素,则将右侧部分的起始位置和结束位置入栈。
4.排序完成。
这种非递归的快速排序算法可以提供更好的性能和较小的空间复杂度,因为它避免了递归调用带来的额外开销。
同时,它也可以避免递归调用导致的堆栈溢出的问题。
非递归无限分类非递归
非递归无限分类是一种处理层级结构数据的有效方法,它能够在不使用递归的情况下,对具有无限层级的数据进行分类。
这种方法在处理大量数据时具有更高的效率和更好的性能,因为它避免了递归调用带来的额外开销和潜在的栈溢出问题。
非递归无限分类的核心思想是使用迭代或循环结构来代替递归。
通常,我们可以使用一个栈或队列来模拟递归的过程。
在处理层级结构数据时,我们可以从根节点开始,将其子节点依次压入栈或队列中。
然后,我们可以不断从栈或队列中取出节点,并处理其子节点,直到栈或队列为空。
这种方法的关键在于如何正确地处理节点的层级关系。
通常,我们可以使用一个字段来记录每个节点所在的层级,以便在处理节点时能够正确地识别其父节点和子节点。
另外,我们还需要考虑如何处理节点的顺序,以确保分类结果的正确性。
非递归无限分类的优点在于它可以处理任意层级的数据,而不会受到递归深度的限制。
此外,由于它避免了递归调用,因此在处理大量数据时具有更高的效率和更好的性能。
然而,它的实现相对复杂,需要仔细设计算法和数据结构来确保分类结果的正确性。
总之,非递归无限分类是一种高效、灵活的处理层级结构数据的方法,它可以应用于各种需要处理无限层级数据的场景,如菜单管理、组织架构管理等。
在实际应用中,我们可以根据具体的需求和数据特点,选择合适的算法和数据结构来实现非递归无限分类。
递归函数可以通过使用栈(stack)数据结构来实现非递归版本。
递归函数在每次调用时,都会将当前的状态(如局部变量和参数)压入栈中,然后返回上一级的状态。
因此,我们可以模拟这个过程,使用栈来保存中间状态,然后一步步地处理,直到栈为空。
以下是一个简单的例子,说明如何将递归函数转化为非递归函数。
假设我们有一个递归函数,用于计算阶乘:c复制代码int factorial_recursive(int n) {if (n == 0) {return1;} else {return n * factorial_recursive(n - 1);}}这个递归函数可以转化为以下的非递归函数:c复制代码#include<stdio.h>#include<stdlib.h>int factorial_iterative(int n) {int stack[n+1]; // 创建一个栈,用于保存中间状态int top = 0; // 栈顶指针int result = 1; // 初始化结果为1// 将n到1的所有数压入栈中for (int i = 1; i <= n; i++) {stack[top++] = i;}// 从栈中取出数,计算阶乘while (top > 0) {result *= stack[--top];}return result;}int main() {int n = 5;printf("Factorial of %d is %d\n", n, factorial_iterative(n));return0;}以上代码中,我们首先创建了一个栈,并将需要计算的数(从1到n)压入栈中。
然后,我们从栈中取出数,并计算阶乘。
这样,我们就模拟了递归函数的行为,但是使用了非递归的方式实现。
需要注意的是,这种方法只适用于那些可以转化为迭代形式的递归函数。
快速排序非递归算法快速排序是一种常用的排序算法,其主要思想是通过分治的思想将待排序序列不断划分成较小的子序列,并分别对子序列进行排序,最终将所有子序列合并得到有序序列。
与递归版本不同,快速排序的非递归算法通过使用栈来模拟递归过程,实现排序过程的迭代。
快速排序的非递归算法主要包含以下几个步骤:1. 首先,我们需要选择一个枢轴元素(pivot),通常可以选择待排序序列的第一个元素作为枢轴元素。
2. 然后,我们需要将待排序序列分成两部分,一部分是小于等于枢轴元素的元素,另一部分是大于枢轴元素的元素。
这一步骤被称为划分(partition)操作。
3. 接下来,我们将划分得到的两个子序列分别压入栈中。
4. 从栈中取出一个子序列,对其进行划分操作,将划分得到的两个子序列压入栈中。
5. 重复步骤4,直到栈为空。
此时,所有的子序列都已经排好序。
下面我们通过一个例子来说明快速排序的非递归算法的具体步骤。
假设我们要对序列[6, 3, 8, 2, 9, 1]进行排序。
选择第一个元素6作为枢轴元素。
然后,进行划分操作,得到两个子序列[3, 2, 1]和[8, 9]。
将这两个子序列压入栈中。
从栈中取出子序列[8, 9],进行划分操作,得到两个子序列[8]和[9]。
由于子序列[8]和[9]已经有序,所以不需要继续划分,我们可以将其从栈中弹出。
接下来,从栈中取出子序列[3, 2, 1],进行划分操作,得到两个子序列[1, 2]和[3]。
将这两个子序列压入栈中。
从栈中取出子序列[1, 2],进行划分操作,得到两个子序列[1]和[2]。
由于子序列[1]和[2]已经有序,所以不需要继续划分,我们可以将其从栈中弹出。
从栈中取出子序列[3],由于只有一个元素,不需要划分,我们可以将其从栈中弹出。
栈为空,排序过程结束。
将所有的子序列合并,得到有序序列[1, 2, 3, 8, 9]。
通过上述例子可以看出,快速排序的非递归算法通过使用栈来模拟递归过程,实现了排序过程的迭代。
非递归方式通常是指使用循环结构而不是递归函数来解决问题的方法。
在计算机编程中,递归是一种常用的算法模式,它通过将问题分解为更小的子问题来解决。
然而,有时候使用非递归方式可能更有效或更适合某些编程语言或特定的问题。
以下是一个简单的例子,说明如何使用非递归方式实现二叉树遍历:假设我们有一个二叉树节点类定义如下:
我们可以使用非递归方式来实现前序遍历(根-左-右):
这个非递归版本的遍历算法使用了一个栈来保存节点。
在每一步迭代中,我们从栈中弹出一个节点,并将其值添加到结果列表中。
然后,我们将右子节点和左子节点(如果存在)依次压入栈中。
由于栈是后进先出的数据结构,所以右子节点会在左子节点之前被处理,这与前序遍历的顺序一致。
快速排序的⾮递归实现 机械的《数据结构——c语⾔版》排序⼀章,有的⽤递归,有的算法不⽤递归,因⽽对于递归的快排,萌⽣⾮递归之想。
查来资料,基本就是⽤堆栈(另⼀种常见转化⽅法是⽤while)代替,分享⼀下: ⾸先说明⼀下快速排序是对冒泡排序的改进。
为什么这么说呢?想⼀下冒泡排序,它把序列分成了两部分,前半部分⽆序,后半部分升序排列,并且后半部分的数都⼤于前半部的数。
由此可得到快速排序和冒泡排序的⼀些共同点:1. 都要经历n趟排序2. 每趟排序要经历O(n)次⽐较3. 都是后半部分元素⽐前半部⼤⽽不同之处就在于冒泡排序的交换操作发⽣相邻的元素之间,即⼀趟排序可以要经过多次交换操作;快速排序的交换操作发⽣在间隔⽐较远的两个元素之间,⼀趟排序要经过交换操作次数会少⼀些。
下⾯给出快速排序的递归和⾮递归实现代码:1 #include<iostream>2 #include<vector>3 #include<stack>4 #include<cstdlib>5 #include<algorithm>6using namespace std;78/**把数组分为两部分,轴pivot左边的部分都⼩于轴右边的部分**/9 template <typename Comparable>10int partition(vector<Comparable> &vec,int low,int high){11 Comparable pivot=vec[low]; //任选元素作为轴,这⾥选⾸元素12while(low<high){13while(low<high && vec[high]>=pivot)14 high--;15 vec[low]=vec[high];16while(low<high && vec[low]<=pivot)17 low++;18 vec[high]=vec[low];19 }20//此时low==high21 vec[low]=pivot;22return low;23 }2425/**使⽤递归快速排序**/26 template<typename Comparable>27void quicksort1(vector<Comparable> &vec,int low,int high){28if(low<high){29int mid=partition(vec,low,high);30 quicksort1(vec,low,mid-1);31 quicksort1(vec,mid+1,high);32 }33 }3435/**使⽤栈的⾮递归快速排序**/36 template<typename Comparable>37void quicksort2(vector<Comparable> &vec,int low,int high){38 stack<int> st;39if(low<high){40int mid=partition(vec,low,high);41if(low<mid-1){42 st.push(low);43 st.push(mid-1);44 }45if(mid+1<high){46 st.push(mid+1);47 st.push(high);48 }49//其实就是⽤栈保存每⼀个待排序⼦串的⾸尾元素下标,下⼀次while循环时取出这个范围,对这段⼦序列进⾏partition操作50while(!st.empty()){51int q=st.top();52 st.pop();53int p=st.top();54 st.pop();55 mid=partition(vec,p,q);56if(p<mid-1){57 st.push(p);58 st.push(mid-1);59 }60if(mid+1<q){61 st.push(mid+1);62 st.push(q);63 }64 }65 }66 }6768int main(){69int len=1000000;70 vector<int> vec;71for(int i=0;i<len;i++)72 vec.push_back(rand());73 clock_t t1=clock();74 quicksort1(vec,0,len-1);75 clock_t t2=clock();76 cout<<"recurcive "<<1.0*(t2-t1)/CLOCKS_PER_SEC<<endl;7778//重新打乱顺序79 random_shuffle(vec.begin(),vec.end());8081 t1=clock();82 quicksort2(vec,0,len-1);83 t2=clock();84 cout<<"none recurcive "<<1.0*(t2-t1)/CLOCKS_PER_SEC<<endl;8586return0;87 }orisun@zcypc:~$ g++ quicksort.cpp -o qsorisun@zcypc:~$ ./qsrecurcive 0.38none recurcive 0.47可以看到⾮递归的算法⽐递归实现还要慢。
非递归阶乘c语言在我们的生活与工作中,经常要用到阶乘。
阶乘是指一个正整数n与小于等于n的所有正整数的乘积。
如4!表示4×3×2×1=24。
在c 语言中,我们可以使用递归或非递归的方法来实现阶乘。
本文将介绍一下如何实现非递归的阶乘程序,具体内容如下:一、程序设计思路非递归的阶乘程序相对于递归来说,在性能上有相对优势。
因为递归方式是将大问题不断分解为小问题,消耗了大量的时间和空间资源。
非递归方式则是将大问题按步骤解决。
所以,在一些性能要求较高的场景下,非递归的方式更为适宜。
而非递归的阶乘程序实现思路为:从1开始计算,每次乘以下一个自然数,直到乘到n为止。
这里要注意,n要不小于1。
二、程序实现方法以下是非递归的阶乘程序的具体实现方法:1.使用while循环语句来实现程序,步骤如下:(1)首先需要定义一个变量n,表示需要求阶乘的自然数,由于阶乘的定义域为正整数,因此n需要是正整数类型的变量。
(2)初始化阶乘结果为1,即fact=1;(3)在while循环中,每次将阶乘结果fact乘以当前自然数i,并将i自增;(4)循环结束条件为i<=n,即已经乘到了n;(5)当循环结束时,fact中存储的就是n的阶乘。
将fact输出即可。
以下是具体代码实现:```#include<stdio.h>int main() {int n,i,fact=1;printf("请输入要求阶乘的自然数n:");scanf("%d",&n);i=1;while(i<=n){fact*=i;i++;}printf("阶乘结果为:%d",fact);return 0;}```2.另一种常用的实现方法是使用for循环语句,具体实现方法如下:(1)需定义一个变量n,表示需要求阶乘的自然数,n需要是正整数类型的变量。
(2)初始化阶乘结果为1,即fact=1;(3)使用for循环进行计算,每次将阶乘结果fact乘以当前自然数i;(4)循环条件为i<=n,即已经乘到了n;(5)当循环结束时,fact中存储的就是n的阶乘。
二叉排序树非递归建立算法摘要:1.二叉排序树基本概念2.非递归建立算法原理3.非递归建立算法实现4.算法优势与局限正文:一、二叉排序树基本概念二叉排序树,又称二叉查找树或二叉搜索树,是一种特殊的二叉树。
其基本性质如下:1.若左子树不空,则左子树上所有结点的值均小于它的根结点的值;2.若右子树不空,则右子树上所有结点的值均大于它的根结点的值;3.左、右子树也分别为二叉排序树;4.没有键值相等的结点。
二、非递归建立算法原理非递归建立算法的中序遍历过程如下:1.首先输出根结点的左子树中的左子树;2.接着输出根结点;3.最后输出根结点的右子树。
这个过程可以确保遍历到二叉排序树的每一个结点。
三、非递归建立算法实现以下是用Python实现的非递归建立二叉排序树的示例代码:```pythonclass Node:def __init__(self, key):self.left = Noneself.right = Noneself.val = keydef newNode(key):if not isinstance(key, int):raise ValueError("Key must be an integer") return Node(key)def inorder_traversal(root):if root is None:returninorder_traversal(root.left)print(root.val, end=" ")inorder_traversal(root.right)def build_binary_search_tree(arr):if not arr:return Nonemid = len(arr) // 2root = newNode(arr[mid])root.left = build_binary_search_tree(arr[:mid])root.right = build_binary_search_tree(arr[mid+1:])return rootif __name__ == "__main__":arr = [12, 4, 6, 8, 10, 14, 16, 18]root = build_binary_search_tree(arr)print("Inorder traversal of the built tree:")inorder_traversal(root)```四、算法优势与局限非递归建立二叉排序树的优势在于避免了递归算法容易导致的栈溢出问题,同时具有可读性和实用性。
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语言理论知识,这样才能使理论和实践相互促进,在这两方面都有所提高。
与此同时,我也认识到了查阅资料和团队的重要性。
资料为我们提供了很好的知识,我们没事时应该多翻阅相关资料使得我们的能力更进一步,当自己看不懂时可以和同学讨论,不仅增加了彼此的友谊同时而且使我们对知识的理解更深。
通过本次课程设计,我对非递归算法和进位都有了更深的了解,和更加熟练的应用。
虽然过去编写程序也经常用到递归,但是当时根本就不了解递归算法和非递归算法的优缺点,现在知道大多数程序采用递归算法来分析,而采用非递归算法来实现,因为递归算法容易溢出,非递归算法更节省空间。
在上机实践中,我发现了自己的基础还不是很扎实。
有些代码自己还是不能准确地写出来,查看资料的时候有的代码看不懂,有时候还会因为空间分配等问题造成程序错误,但是经过多次实践,一些小的错误自己已经可以很容易解决了,遇到一些较难的问题时,我还是要查看教材和其他的资料来帮助自己解决问题。
这种习惯极好地补充了我在程序设计中不足的知识。
这使我更深刻地体会到,不管学习那种编译语言,不仅要动脑,更要动手去做。
在以后的学习中,我会更加注重实践操作能力的培养,让自己的各方面能力都有所提高。