【算法作业】实验四:逆波兰表达式求值 & Fibonacci数列的尾递归与非递归程序

简介: 【算法作业】实验四:逆波兰表达式求值 & Fibonacci数列的尾递归与非递归程序

第一题:逆波兰表达式求值


1.题目

掌握递归的基本语法和思想,利用递归程序实现逆波兰表达式,并分析算法复杂度。


2.问题分析与算法设计思路

这里实现了对多位数整的操作,运算仅包含四则运算。输入中使用.来将不同的操作数隔开,使用@表示表达式的结束。


使用栈的实现方法在另一篇博客中写过,请参考:计算后缀表达式-算法与数据结构-栈的运用-C++语言实现。这里我们将使用递归的方法来实现(虽然也用到了栈,但仅仅是为了从尾部开始读取字符而已)。


思路:

每个操作符将需要两个操作数,可以为表达式构建一颗递归树,每个操作数可能就是表达式中的一个数字,也可能要由一个子表达式(递归就来了)计算得到。


以输入3.5.2.-*7.+@为例,构建递归树:


image.png


注意:

由于我是从表达式的尾部开始读取字符,而减法、除法运算并不满足交换律,因此需要额外调整一下。


遇到问题:

再调试过程中遇到了一个很奇怪的问题,我现在仍没有弄清楚原理,我将它记录在问答社区:有个带函数的表达式前加负号无效。在下面的代码中,采用了避免该问题的写法。


3.算法实现

//逆波兰表达式——递归 
#include<iostream>
#include<cstdio>
#include<stack>
using namespace std;
stack<char> s;
int get_num(){//从栈中返回一个整型数字 
  char t=0;
  int sum=0;
  for(int i=1; ! s.empty(); i *= 10){
  t = s.top();
  if('0' <= t && t <= '9'){
    s.pop();
    sum += (t - '0') * i;
  }
  else break;
  }
  //cout<<"sum "<<sum<<endl;
  return sum;
}
int nbl(char x){//对逆波兰表达式求值 
  if(x == '+' || x == '-' || x == '*' || x == '/'){
  s.pop();
  if(x == '+'){
    int t = nbl(s.top()) + nbl(s.top());
    cout<<"t: "<<t<<endl;
    return t;
  }
  else if(x == '-'){
    int t = (nbl(s.top()) - nbl(s.top()));
    t = -t;
    cout<<"t: "<<t<<endl;
    return t;
  }
  else if(x == '*'){
    int t = nbl(s.top()) * nbl(s.top());
    cout<<"t: "<<t<<endl;
    return t;
  }
  else if(x == '/'){
    int t = (int)(1 / ((double)nbl(s.top()) / nbl(s.top())));
    cout<<"t: "<<t<<endl;
    return t;
  }
  }
  else if(x == '.'){
  s.pop();
  return get_num();
  }
}
int main(){
  char t='0';
  while(true){
  cin >> t;
  if(t == '@') break;
  s.push(t);
  }
  if(s.empty()){
  cout<<"表达式为空"<<endl;
  }
  else cout<<nbl(s.top());
  return 0;
}
/*
测试数据
3.5.2.-*7.+@
10.28.30./*7.-6.+@
1000.1000./5.-6.*@
*/

代码更新(9月19日):

改变了函数nbl()中不同四则运算分支的代码写法,这样看起来更加简洁和明确,调试时查看中间结果也更加的方便。


//逆波兰表达式——递归 
#include<iostream>
#include<cstdio>
#include<stack>
using namespace std;
stack<char> s;
int get_num(){//从栈中返回一个整型数字 
  char t=0;
  int sum=0;
  for(int i=1; ! s.empty(); i *= 10){
  t = s.top();
  if('0' <= t && t <= '9'){
    s.pop();
    sum += (t - '0') * i;
  }
  else break;
  }
  return sum;
}
int nbl(char x){//对逆波兰表达式求值 
  s.pop(); 
  if(x == '+' || x == '-' || x == '*' || x == '/'){
  if(x == '+'){
    int a = nbl(s.top());
    int b = nbl(s.top());
    return a + b;
  }
  else if(x == '-'){
    int a = nbl(s.top());
    int b = nbl(s.top());
    return b - a;
  }
  else if(x == '*'){
    int a = nbl(s.top());
    int b = nbl(s.top());
    return a * b;
  }
  else if(x == '/'){
    int a = nbl(s.top());
    int b = nbl(s.top());
    return b / a;
  }
  }
  else if(x == '.'){
  return get_num();
  }
}
int main(){
  char t='0';
  while(true){
  cin >> t;
  if(t == '@') break;
  s.push(t);
  }
  if(s.empty()){
  cout<<"表达式为空"<<endl;
  }
  else cout<<nbl(s.top());
  return 0;
}

4.运行结果

输出的t为每次四则运算得到的中间结果。


image.png


5.算法分析

算法的时间复杂度应为:o ( n ) o(n)o(n),其中 n 为输入表达式的长度。


第二题:Fibonacci数列的尾递归与非递归程序


1.题目

将Fibonacci数列的递归求解方法转为尾递归求解;借助栈实现递归转非递归程序。


2.问题分析与算法设计思路

主要是学会可递归问题的多种写法。


3.算法实现

一、尾递归

关于使用尾递归,可以参考一下:尾递归实现斐波那契数


//斐波那契尾递归实现
#include<iostream>
using namespace std;
int fbnq(int n, int a, int b){ //b表示当前值,a表示前一项的值。其实类似于循环迭代,n控制循环次数 
  if(n == 1) return b;
  return fbnq(n-1, b, a+b);
}
int main(){
  int n = 0;
  cout<<"求斐波那契数列的第多少项?"<<endl;
  while(n < 1){
  cin >> n;
  }
  cout<<fbnq(n, 0, 1);
  return 0;
}


二、使用栈的非递归


//斐波那契使用栈非递归实现 
#include<iostream>
#include<stack>
using namespace std;
stack<int> s;
int main(){
  int n = 0;
  int a = 0;
  int b = 0;
  cout<<"求斐波那契数列的第多少项?"<<endl;
  while(n < 1){
  cin >> n;
  }
  s.push(0);
  s.push(1);
  for(int i = 0; i < n - 1; i++){
  a = s.top();
  s.pop();
  b = s.top();
  s.pop();
  s.push(a);
  s.push(a+b);
  }
  b = s.top();
  cout<<b;
  return 0;
}

4.运行结果

image.png


5.算法分析

算法复杂度为:o ( n ) o(n)o(n)。


这里的递归写法时间开销要比非递归大一些(即使不考虑递归操作本身的原因),因为会存在重复计算。例如递归计算f ( 5 ) f(5)f(5),则需要计算f ( 4 ) f(4)f(4)和f ( 3 ) f(3)f(3),而计算f ( 4 ) f(4)f(4)中又计算了一次f ( 3 ) f(3)f(3)。这个问题据说可以用记忆化进行优化,但我还没有实操尝试过。

相关文章
|
1月前
|
存储 算法 C语言
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
56 1
|
16天前
|
算法 调度
作业调度算法_先来先服务算法_短作业优先算法_高响应比优先算法
本文介绍了作业调度算法,包括先来先服务(FCFS)、短进程优先(SJF)和高响应比优先(HRRN)算法。通过分析进程的到达时间和所需CPU服务时间,计算进程的开始时间、完成时间、平均周转时间和平均带权周转时间,以评估不同算法的性能。FCFS适合长作业,SJF适合短作业,而HRRN则综合了两者的优点。
44 12
|
1月前
|
算法
算法设计与分析作业
这篇文章是关于算法设计与分析的作业,其中包含了两个算法实现:一个是使用分治算法实现的十进制大整数相乘(包括加法、减法和乘法函数),并进行了正确性和健壮性测试;另一个是使用快速排序思想实现的分治查找第K小元素的程序,并分析了其平均和最坏时间复杂度。
算法设计与分析作业
|
1月前
|
算法 Java 测试技术
算法分析(蛮力法与减治算法应用实验报告)
这篇文章是关于算法分析的实验报告,介绍了如何使用蛮力法解决背包问题,并通过伪代码和Java代码实现,同时分析了其时间效率;还介绍了基于减治法思想实现的二叉查找树的插入与查找,同样提供了伪代码、Java源代码实现和时间效率分析,最后展示了测试结果截图。
算法分析(蛮力法与减治算法应用实验报告)
|
1月前
|
存储 算法
读《趣学算法》:重开算法之门,神奇的兔子数列(斐波那契数列)
本文通过《趣学算法》中的斐波那契数列问题,探讨了算法的递归实现、时间复杂度分析,并展示了如何通过迭代和优化存储空间来改进算法,最终将时间复杂度从指数级降低到线性级,并将空间复杂度从线性级降低到常数级。
46 0
读《趣学算法》:重开算法之门,神奇的兔子数列(斐波那契数列)
|
1月前
|
DataWorks 算法 调度
B端算法实践问题之配置脚本以支持blink批处理作业的调度如何解决
B端算法实践问题之配置脚本以支持blink批处理作业的调度如何解决
27 1
|
1月前
|
机器学习/深度学习 算法 Java
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
这篇文章介绍了基于贪婪技术思想的Prim算法和Dijkstra算法,包括它们的伪代码描述、Java源代码实现、时间效率分析,并展示了算法的测试用例结果,使读者对贪婪技术及其应用有了更深入的理解。
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
|
1月前
|
算法 Java 测试技术
算法设计(动态规划实验报告) 基于动态规划的背包问题、Warshall算法和Floyd算法
这篇文章介绍了基于动态规划法的三种算法:解决背包问题的递归和自底向上实现、Warshall算法和Floyd算法,并提供了它们的伪代码、Java源代码实现以及时间效率分析。
算法设计(动态规划实验报告) 基于动态规划的背包问题、Warshall算法和Floyd算法
|
1月前
|
算法
【算法】递归、搜索与回溯——汉诺塔
【算法】递归、搜索与回溯——汉诺塔
|
1月前
|
算法 搜索推荐
算法设计 (分治法应用实验报告)基于分治法的合并排序、快速排序、最近对问题
这篇文章是关于分治法应用的实验报告,详细介绍了如何利用分治法实现合并排序和快速排序算法,并探讨了使用分治法解决二维平面上的最近对问题的方法,包括伪代码、源代码实现及时间效率分析,并附有运行结果和小结。