栈与递归

合集下载

递归的概念递归过程与递归工作栈递归与回溯广义表

递归的概念递归过程与递归工作栈递归与回溯广义表
else { Hanoi ( n-1, A, C, B ); cout << " move " << A << " to " << C << endl; Hanoi ( n-1, B, A, C );
} }
递归过程与递归工作栈
递归过程在实现时,需要自己调用自己。 层层向下递归,退出时的次序正好相反:
}
递归找含x值的结点
f
x

fff
问题的解法是递归的
例如,汉诺塔(Tower of Hanoi)问题的解法: 如果 n = 1,则将这一个盘子直接从 A 柱移到
C 柱上。否则,执行以下三步: ① 用 C 柱做过渡,将 A 柱上的 (n-1) 个盘子移 到 B 柱上: ② 将 A 柱上最后一个盘子直接移到 C 柱上; ③ 用 A 柱做过渡,将 B 柱上的 (n-1) 个盘子移 到 C 柱上。
递归调用
n! (n-1)! (n-2)!
1! 0!=1
返回次序
主程序第一次调用递归过程为外部调用;
递归过程每次递归调用自己为内部调用。
它们返回调用它的过程的地址不同。
递归工作栈
每一次递归调用时,需要为过程中使用的 参数、局部变量等另外分配存储空间。
每层递归调用需分配的空间形成递归工作 记录,按后进先出的栈组织。
while ( n >= 0 ) { cout << "value " << A[n] << endl;
n--;
} }
递归与回溯 常用于搜索过程
n皇后问题 在 n 行 n 列的国际象棋棋盘上,

必备算法:递归!无论你是前端开发,还是后端开发,都需要掌握它!

必备算法:递归!无论你是前端开发,还是后端开发,都需要掌握它!

必备算法:递归!⽆论你是前端开发,还是后端开发,都需要掌握它!递归是⼀种⾮常重要的算法思想,⽆论你是前端开发,还是后端开发,都需要掌握它。

在⽇常⼯作中,统计⽂件夹⼤⼩,解析xml⽂件等等,都需要⽤到递归算法。

它太基础太重要了,这也是为什么⾯试的时候,⾯试官经常让我们⼿写递归算法。

本⽂呢,将跟⼤家⼀起深⼊挖掘⼀下递归算法~什么是递归?递归,在计算机科学中是指⼀种通过重复将问题分解为同类的⼦问题⽽解决问题的⽅法。

简单来说,递归表现为函数调⽤函数本⾝。

在知乎看到⼀个⽐喻递归的例⼦,个⼈觉得⾮常形象,⼤家看⼀下:❝递归最恰当的⽐喻,就是查词典。

我们使⽤的词典,本⾝就是递归,为了解释⼀个词,需要使⽤更多的词。

当你查⼀个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第⼆个词,可惜,第⼆个词⾥仍然有不懂的词,于是查第三个词,这样查下去,直到有⼀个词的解释是你完全能看懂的,那么递归⾛到了尽头,然后你开始后退,逐个明⽩之前查过的每⼀个词,最终,你明⽩了最开始那个词的意思。

❞来试试⽔,看⼀个递归的代码例⼦吧,如下:递归的特点实际上,递归有两个显著的特征,终⽌条件和⾃⾝调⽤:✿⾃⾝调⽤:原问题可以分解为⼦问题,⼦问题和原问题的求解⽅法是⼀致的,即都是调⽤⾃⾝的同⼀个函数。

✿终⽌条件:递归必须有⼀个终⽌的条件,即不能⽆限循环地调⽤本⾝。

结合以上demo代码例⼦,看下递归的特点:递归与栈的关系其实,递归的过程,可以理解为出⼊栈的过程的,这个⽐喻呢,只是为了⽅便读者朋友更好理解递归哈。

以上代码例⼦计算sum(n=3)的出⼊栈图如下:为了更容易理解⼀些,我们来看⼀下函数sum(n=5)的递归执⾏过程,如下:✿计算sum(5)时,先sum(5)⼊栈,然后原问题sum(5)拆分为⼦问题sum(4),再⼊栈,直到终⽌条件sum(n=1)=1,就开始出栈。

✿ sum(1)出栈后,sum(2)开始出栈,接着sum(3)。

✿最后呢,sum(1)就是后进先出,sum(5)是先进后出,因此递归过程可以理解为栈出⼊过程啦~递归的经典应⽤场景哪些问题我们可以考虑使⽤递归来解决呢?即递归的应⽤场景⼀般有哪些呢?✿阶乘问题✿⼆叉树深度✿汉诺塔问题✿斐波那契数列✿快速排序、归并排序(分治算法体现递归)✿遍历⽂件,解析xml⽂件递归解题思路解决递归问题⼀般就三步曲,分别是:✿第⼀步,定义函数功能✿第⼆步,寻找递归终⽌条件✿第⼆步,递推函数的等价关系式这个递归解题三板斧理解起来有点抽象,我们拿阶乘递归例⼦来喵喵吧~1、定义函数功能定义函数功能,就是说,你这个函数是⼲嘛的,做什么事情,换句话说,你要知道递归原问题是什么呀?⽐如你需要解决阶乘问题,定义的函数功能就是n的阶乘,如下:2、寻找递归终⽌条件递归的⼀个典型特征就是必须有⼀个终⽌的条件,即不能⽆限循环地调⽤本⾝。

栈的应用场景

栈的应用场景

栈的应用场景栈是一种常见的数据结构,它的特点是后进先出(Last In First Out,LIFO)。

栈的应用场景非常广泛,从计算机科学到日常生活都可以见到其身影。

本文将介绍栈在不同领域的应用场景。

1.计算机算法在计算机算法中,栈经常被用于实现递归函数、表达式求值、括号匹配等操作。

递归函数的调用过程实际上是一个栈的过程,每当一个函数调用另一个函数时,系统会将当前函数的状态信息压入栈中,待调用的函数执行完毕后再从栈中弹出上一个函数的状态信息继续执行。

表达式求值中,栈可以用于存储操作数和运算符,通过弹出栈中的元素进行计算,最终得到表达式的结果。

括号匹配中,栈可以用于判断左右括号是否匹配。

2.编译器和操作系统编译器和操作系统也是栈的常用应用场景。

在编译器中,栈用于存储函数调用的参数、局部变量和返回地址等信息。

每当函数调用时,编译器会将相关信息压入栈中,函数执行结束后再从栈中弹出相关信息。

操作系统中的函数调用、中断处理等过程也经常使用栈来保存现场信息,保证程序的正确执行。

3.网络协议在网络协议中,栈被广泛应用于网络数据的传输和处理。

TCP/IP协议栈是一个典型的例子,它将网络层、传输层、应用层等不同的协议通过栈的形式依次封装,完成数据的传输和处理。

数据包从应用层一直传输到网络层,以栈的形式不断压入和弹出,确保数据的准确传递和处理。

4.浏览器的前进后退功能在浏览器中,前进和后退功能是栈应用的典型场景。

当我们浏览网页时,每当点击一个链接或者输入一个网址,浏览器会将当前的URL 压入栈中。

当我们点击“后退”按钮时,浏览器会从栈中弹出上一个URL,完成页面的后退操作。

同样地,当我们点击“前进”按钮时,浏览器会从栈中弹出下一个URL,完成页面的前进操作。

5.撤销和恢复操作在各种应用程序中,栈可用于实现撤销和恢复操作。

例如,在文字编辑器中,当我们对文字进行修改后,可以将修改前的状态信息压入栈中,以备将来的撤销操作。

栈的出队顺序

栈的出队顺序

栈的出队顺序一、栈的出队顺序——先进后出的数据结构二、栈的基本操作——入栈和出栈栈的基本操作包括入栈和出栈。

入栈是指将元素添加到栈的顶部,出栈是指将栈顶的元素移除。

入栈和出栈是栈的两个基本操作,它们是栈的核心功能。

通过这两个操作,我们可以实现对栈中元素的添加和删除。

三、栈的应用——逆波兰表达式求值逆波兰表达式是一种不需要括号来标识优先级的数学表达式表示方法。

在逆波兰表达式中,操作符位于操作数的后面,这样可以避免使用括号来改变运算的顺序。

逆波兰表达式求值是栈的一个典型应用场景。

通过使用栈来保存操作数,我们可以按照逆波兰表达式的顺序依次计算出结果。

四、栈的应用——括号匹配括号匹配是栈的另一个重要应用场景。

在编程中,经常需要对括号进行匹配判断,以确保代码的正确性。

使用栈可以方便地实现对括号的匹配判断。

当遇到左括号时,将其入栈;当遇到右括号时,与栈顶元素进行匹配判断。

如果匹配成功,则将栈顶元素出栈;如果匹配失败,则表明括号不匹配。

五、栈的应用——浏览器的前进和后退功能浏览器的前进和后退功能是栈的又一个典型应用。

当我们在浏览器中点击前进按钮时,当前页面的URL将被压入栈中;当我们点击后退按钮时,栈顶元素将被弹出并打开对应的页面。

通过使用栈来保存浏览历史记录,我们可以方便地实现浏览器的前进和后退功能。

六、栈的应用——实现递归递归是一种常见的编程技巧,它可以简化代码的实现。

在递归过程中,每一次递归调用都会创建一个新的栈帧,用于保存函数的局部变量和返回地址。

通过使用栈来保存每个栈帧,我们可以实现递归的执行。

七、栈的应用——系统调用和中断处理在操作系统中,系统调用和中断处理是栈的重要应用场景。

当发生系统调用或中断时,当前的程序状态将被保存到栈中,包括程序计数器、寄存器的值和局部变量等。

通过使用栈来保存这些信息,操作系统可以在中断处理或系统调用结束后恢复程序的执行。

八、栈的应用——迷宫求解迷宫求解是一个经典的问题,可以通过使用栈来解决。

数据结构实验报告2栈、队列、递归程序设计

数据结构实验报告2栈、队列、递归程序设计
计算机科学与技术(本科)《数据结构》实验报告
日期:学号:姓名:
实验名称:实验报告二栈、队列、递归程序设计
实验目的与要求:
2.1栈和队列的基本操作
(1)正确理解栈的先进后出的操作特点,建立初始栈,通过相关操作显示栈底元素。
(2)程序中要体现出建栈过程和取出栈底元素后恢复栈的入栈过程,按堆栈的操作规则打印结果栈中的元素
{
return(s->top==-1);
}
//---出栈函数
int Pop(SeqStack *&s,ElemType &e)
{
if (s->top==-1)
return 0;
e=s->data[s->top];
s->top--;
return 1;
}
//---初始队列函数
void InitQueue(SqQueue *&q)
q->rear=(q->rear+1)%MaxSize;
q->elem[q->rear]=e;
return 1;
}
//---出队列函数
int OutQueue(SqQueue *&q,ElemType &e)
{
if (q->front==q->rear) //队空
return 0;
q->front=(q->front+1)%MaxSize;
printf("(10)栈为%s,",(StackEmpty(s)?"空":"非空"));
printf("队列为%s\n",(QueueEmpty(q)?"空":"非空"));

栈的应用及特性

栈的应用及特性

栈的应用及特性栈是计算机科学中一种非常重要的数据结构,具有广泛的应用和独特的特性。

下面将详细介绍栈的应用及特性。

一、栈的应用:1. 函数调用:在程序执行过程中,函数的调用和返回通常采用栈进行管理。

当一个函数被调用时,函数的参数和局部变量被压入栈中,函数执行完毕后,这些信息会被弹出栈恢复到调用函数的状态。

2. 表达式求值:在编程语言中,栈可用于表达式求值、中缀表达式转换为后缀表达式等相关操作。

通过利用栈的先进后出特性,可以方便地实现这些功能。

3. 递归算法:递归算法中的递归调用也可以通过栈来实现。

当算法需要递归调用时,将函数和相关变量的信息压入栈中,等到递归结束后,再从栈中弹出恢复状态。

4. 括号匹配:栈也常用于判断表达式中的括号是否匹配。

遍历表达式,遇到左括号时压入栈,遇到右括号时弹出栈顶元素,如果匹配则继续,不匹配则判定为括号不匹配。

5. 浏览器的前进后退:浏览器的前进后退功能可以使用栈实现。

每次浏览一个网页时,将该网页的URL压入栈中,点击后退按钮时,再从栈中弹出上一个URL,即可实现返回上一个网页的功能。

6. 撤销操作:在图形界面软件中,通常会有撤销操作。

使用栈可以将每一步操作的状态依次压入栈中,当用户需要撤销时,再从栈中弹出最近的状态,恢复到之前的操作状态。

二、栈的特性:1. 先进后出:栈是一种后进先出(LIFO)的数据结构,即最新添加的元素最先被访问或者删除。

这一特性使得栈能够方便地实现函数调用和返回等操作。

2. 只能操作栈顶元素:由于栈的特性,只能访问或者修改栈顶元素,无法直接访问或者修改栈中的其他元素。

需要先将栈顶元素弹出后,才能访问或者修改下一个栈顶元素。

3. 顺序存储结构:栈可以使用数组或者链表实现。

使用数组实现时,需要指定栈的最大容量,而使用链表实现时,没有容量限制。

4. 操作复杂度:栈的插入和删除操作只涉及栈顶元素,所以其操作复杂度为O(1)。

但是栈的搜索和访问操作需要从栈顶开始遍历,所以其操作复杂度为O(n)。

链表逆序的三种方法

链表逆序的三种方法

链表逆序的三种方法链表是一种常用的数据结构,由一个个节点通过指针连接而成。

在实际编程中,经常需要对链表进行逆序操作,以满足特定需求。

本文将介绍链表逆序的三种常用方法,分别是迭代法、递归法以及使用栈的方法。

迭代法:迭代法是一种比较直观的逆序方法,通过调整节点之间的指针指向来实现。

具体步骤如下:1. 定义三个指针,分别为当前节点(cur)、前一个节点(prev)和下一个节点(next)。

2. 将当前节点的下一个节点保存到next指针中,以免链表断开。

3. 将当前节点的next指针指向前一个节点,完成逆序操作。

4. 将当前节点赋值给prev指针,以备下一次迭代使用。

5. 将next指针赋值给cur指针,继续下一次迭代。

若next指针为空,则说明已到达链表尾部,逆序完成。

递归法:递归法是一种更为简洁的逆序方法,通过递归调用实现链表逆序。

具体步骤如下:1. 首先判断链表是否为空或只有一个节点,若是则无需逆序,直接返回。

2. 若链表有多个节点,则递归调用逆序函数对除第一个节点外的子链表进行逆序。

3. 将头节点(首节点)的指针指向调用逆序函数后的新链表的尾节点。

4. 将尾节点的指针指向头节点,使得整个链表逆序完成。

使用栈的方法:栈是一种后进先出(LIFO)的数据结构,可以利用栈的特性进行链表逆序操作。

具体步骤如下:1. 遍历链表,将链表中的节点依次压入栈中。

2. 弹出栈中的节点,按照出栈顺序重新构建链表。

弹出的第一个节点是原链表的尾节点,成为逆序链表的头节点。

3. 将每个弹出的节点的next指针指向下一个被弹出的节点,完成逆序操作。

4. 最后一个被弹出的节点成为逆序链表的尾节点,将其next指针置为空,表示逆序链表的尾部。

以上是三种常见的链表逆序方法。

在实际应用中,可以根据具体情况选择合适的方法来实现链表逆序。

迭代法适合逆序链表并保持链表结构的情况;递归法适用于逆序链表不要求保持原结构的情况;使用栈的方法适用于逆序链表并重新构建链表结构的情况。

递归的替代算法

递归的替代算法

递归是一种强大的编程技术,它允许函数在其定义中调用自身。

然而,递归并不是解决所有问题的最佳方法。

有时,使用迭代(即循环)或其他算法可能会更有效,更简洁,甚至更快。

下面是一些可以替代递归的算法和方法。

1. 迭代
迭代是递归的一种常见替代方法。

在许多情况下,递归函数可以通过使用循环(如for 循环或while循环)来重写。

迭代通常比递归更容易理解和调试,并且对于某些问题,迭代可能比递归更有效。

2. 动态规划
动态规划是一种用于解决递归问题的强大技术。

它通过将问题的解决方案存储在一个表(或其他数据结构)中,避免了递归中的重复计算。

这种方法通常比递归更快,因为它避免了不必要的重复计算。

3. 尾递归优化
尾递归是一种特殊的递归形式,其中递归调用是函数体中的最后一个操作。

一些编程语言(如Haskell)对尾递归进行了优化,使其具有与迭代相同的效率。

然而,并非所有编程语言都支持这种优化,因此尾递归可能并不总是最佳选择。

4. 栈模拟
对于某些递归问题,可以通过使用栈来模拟递归调用栈。

这种方法允许我们以迭代的方式模拟递归过程,从而避免了递归的深度限制。

5. 非递归数据结构
某些数据结构(如栈、队列、树等)可以以非递归方式实现。

使用这些数据结构可以避免需要递归的算法。

总的来说,选择递归还是其他算法取决于具体的问题和上下文。

在某些情况下,递归可能是最简洁和最直接的方法。

然而,在其他情况下,使用迭代、动态规划或其他技术可能会更有效、更简洁或更快。

因此,了解多种解决问题的方法是非常重要的。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

栈与递归的关系
一、引言
栈是一种重要的线性结构。

从数据结构角度看,栈也是线性表,其特殊性在于栈的基本操作是线性表操作的子集,它们是操作受限的线性表。

栈是限定仅在表尾进行插入或删除操作的线性表。

栈还有一个重要应用是在程序设计语言中实现递归。

一个直接调用自己或通过一系列的调用语句间接地调用直接的函数,称做递归函数。

二、栈与递归的关系:
递归在实现过程中是借助于栈来实现的。

高级语言的函数调用,每次调用,系统都要自动为该次调用分配一系列的栈空间用于存放此次调用的相关信息:返回地址,局部变量等。

这些信息被称为工作记录(或活动记录)。

而当函数调用完成时,就从栈空间内释放这些单元,但是,在该函数没有完成前,分配的这些单元将一直保存着不被释放。

递归函数的实现,也是通过栈来完成的。

在递归函数没有到达递归出口前,都要不停地执行递归体,每执行一次,就要在工作栈中分配一个工作记录的空间给该“层”调用存放相关数据,只有当到达递归出口时,即不再执行函数调用时,才从当前层返回,并释放栈中所占用的该“层”工作记录空间。

请大家注意,递归调用时,每次保存在栈中的是局部数据,即只在当前层有效的数据,到达下一层时上一层的数据对本层数据没有任
何影响,一切从当前调用时传过来的实在参数重新开始。

由此可见,从严老师P版教材中,利用栈将递归向非递归转化时所采用的方法,实质是用人工写的语句完成了本该系统程序完成的功能,即:栈空间中工作记录的保存和释放。

可以参照以上的分析来理解递归函数的运行过程。

三、对栈与递归的分析
1、栈的定义
栈(Stack)是限制仅在表的一端进行插入和删除运算的线性表。

(1)通常称插入、删除的这一端为栈顶(Top),另一端称为栈底(Bottom)。

(2)当表中没有元素时称为空栈。

(3)栈为后进先出(Last In First Out)的线性表,简称为LIFO表。

栈的修改是按后进先出的原则进行。

每次删除(退栈)的总是当前栈中"最新"的元素,即最后插入(进栈)的元素,而最先插入的是被放在栈的底部,要到最后才能删除。

2、栈的基本运算
(1)InitStack(S)
构造一个空栈S。

(2)StackEmpty(S)
判栈空。

若S为空栈,则返回TRUE,否则返回FALSE。

(3)StackFull(S)
判栈满。

若S为满栈,则返回TRUE,否则返回FALSE。

注意:
该运算只适用于栈的顺序存储结构。

(4)Push(S,x)
进栈。

若栈S不满,则将元素x插入S的栈顶。

(5)Pop(S)
退栈。

若栈S非空,则将S的栈顶元素删去,并返回该元素。

(6)StackTop(S)
取栈顶元素。

若栈S非空,则返回栈顶元素,但不改变栈的状态。

递归的概念及递归算法的结构
1、所谓的递归,是指函数在执行过程中自己调用了自己或者说某种数据结构在定义时又引用了自身。

这两种情况都可理解为递归。

比如:
void fun()
{
..
fun()
..
}//fun
以上函数fun就是一个递归函数。

而针对于各种数据结构中的递归结构就更多了,如单链表,广义表,树。

在这些递归结构中,具有一个相同的特征:其中的某个域的数据类型是其结点类型本身!
2、递归算法的大致结构为:
a、递归出口
b、递归体
一个递归算法,当其问题求解的规模越来越小时必定有一个递归出口,就是不再递归调用的语句。

递归体则是每次递归时执行的语句序列。

比如以下简要描述的递归函数中:
f(n)=1 (当n=0时)
f(n)=n*f(n-1) (当n>0时)
这个递归函数,实际是求n的阶乘。

当n=0时,不再递归调用,而当其值置为1;当n>0时,就执行n*f(n-1),这是递归调用。

从整体上理解递归算法的大致结构有利于我们在设计递归算法时,从总体上把握算法的正确性。

3、适合于用递归实现的问题类型
必须具有两个条件的问题类型才能用递归方法求得:
1、规模较大的一个问题可以向下分解为若干个性质相同的规模较小的问题,而这些规模较小的问题仍然可以向下分解。

2、当规模分解到一定程度时,必须有一个终止条件,不得无限分解。

由此可见适合于递归实现的问题类型有:
1、函数定义是递归的。

如阶乘,FIB数列。

2、数据结构递归的相关算法。

如:树结构。

3、解法是递归的。

如:汉诺塔问题。

4、递归算法的设计
从递归算法的结构来分析,进行递归算法的设计时,无非要解决两个问题:递归出口和递归体。

即要确定何时到达递归出口,何时执行递归体,执行什么样的递归体。

递归算法算法设计的关键是保存每一层的局部变量并运用这些局部变量。

由此,递归算法的设计步骤可从以下三步来作:
1、分析问题,分解出小问题;
2、找出小问题与大问题之间的关系,确定递归出口;
3、用算法语言写出来。

四、通过实例理解栈与递归的关系:
很多实际递归定义的。

对于这些问题很容易写出求解它们的递归算法。

1.计算阶乘函数的递归算法如下:
f3
│↓r└①┌

┈┘
┈┘
f2



②┌

┈┘
┈┘
f1





┌┈

┈┘
f0
→┐


t└t└
r1┌


←┐

┈┐
6⑥

t1




←┐

┈┐
2⑤

t1




←┐┈

└┈

1④



┈┘
1
图3-5(a) f(3)执行递归调用─返回次序
top →调

f2



f2



f1



f0



f1



f2

top



f3
后图3-5(b) 相应的工作栈状态变化
递归调用——返回的控制与非递归过程的控制并无本质区别,同样可由一个工作栈实现。

对上述过程f,返回位置r应设在内、外层return语句这间。

为了区别返回到哪一级递归,可在返回位置进栈的同时将该次调用的参数一起保存。

图3-5(b)所示为与(a)相应的工作栈状态变化过程。

2.hanoi塔问题
当执行三个盘的时候:
第一次实现的过程
调用函数(通过递归实现),如下是栈的变化:
当三个盘的时候实现的过程
运行示意图(其中一部分)
3.迷宫问题
对应栈的变化:
还有八皇后问题和背包等问题都能够很好的解释栈与递归
的关系。

五、结论
递归在实现过程中是借助于栈来实现的。

高级语言的函数调用,每次调用,系统都要自动为该次调用分配一系列的栈空间用于存放此次调用的相关信息:返回地址,局部变量等。

这些信息被称为工作记录(或活动记录)。

而当函数调用完成时,就从栈空间内释放这些单元,但是,在该函数没有完成前,分配的这些单元将一直保存着不被释放。

递归函数的实现,也是通过栈来完成的。

在递归函数没有到达递归出口前,都要不停地执行递归体,每执行一次,就要在工作栈中分配一个工作记录的空间给该“层”调用存放相关数据,只有当到达递归出口时,即不再执行函数调用时,才从当前层返回,并释放栈中所占用的该“层”工作记录空间。

请大家注意,递归调用时,每次保存在栈中的是局部数据,即只在当前层有效的数据,到达下一层时上一层的数据对本层数据没有任何影响,一切从当前调用时传过来的实在参数重新开始。

六、参考文献:
1.数据结构(严蔚敏吴伟民)清华大学出版
2.c程序设计教程(谭浩强)清华大学出版。

相关文档
最新文档