算法_章节递归算法第三章读书笔记
递归算法的实现机制
利用计算机系统内部机能,自动实现调用过程
子程序的内部实现原理
子程序调用的一般形式
callA调用A结束后能从系统获得返回的地址,按照地址执行下一条指令
callA多次,系统每次保存一个地址,获得返回释放
callA嵌套B,先保存A地址,再保存B地址,然后先返回B,再返回A地址;保存多个地址,用栈的方式
模块化:内部参数;交流:全局变量,参数
被调用时局部变量和返回地址一同存储在栈顶,调用结束从栈顶撤出
值的回传
pascal语言中的值参按照值传送
pascal语言中的变参按照地址传送
变参回传的两种方式
- 两次值传方式,指定类型,分配存储空间,开始将实参值传给变参,返回时将变参值传给实参
- 地址传送方式,在内部将变参设置成地址,将实参地址给变参,对变参操作变参对相应实参操作
下面的递归中本书:变参值的回传采用第一种方式,
函数的值不能直接回传的原因:???
措施:借助全局变量,通过栈实现回传:造成栈结构不一致,调用操作次序????
子程序调用的内部操作
-
在执行调用,系统至少应该
-
返回地址进栈,栈顶为被调子程序局部变量开辟空间
-
为被调子程序准备数据,计算实参值,并且赋给对应栈顶实参
-
将指令流转入被调子程序入口处
-
在执行返回操作时
-
若有变参或是函数,将其值保存在回传变量中
-
从栈顶取出返回地址
-
按照返回地址返回
-
若有变参或者势函数从回传变量中取出保存的值传送给相应的变量或位置上
递归过程的内部实现原理
自己调用自己
每一次递归视为调用自身代码的复制件,系统采用代码共享的方式而不是把代码复制一份到自己的系统,调用要做的事情,递归调用也要做。
递归转非递归
发挥递归表示直观易于证明算法正确的特点
克服由于使用递归而带来总开销增加的不足
流程:
在设计算法的初级阶段使用递归
一旦所设计的算法被证明是正确的且是一个好算法时消去递归
把代码翻译成与之等价的只使用迭代的算法
作进一步改进提高代码效率
下面是直接递归的一组规则,间接递归就是在它基础上做修改
翻译:把递归过程中递归调用的地方用等价的代码来代替,并且对return进行处理
规则如下
- 在过程开始的地方插入说明为栈的代码并且将它初始化为空。在一般情况下,这个栈用来存放参数,局部变量和函数的值,递归调用的函数地址也入栈
- 将标号L赋给第一条可执行语句,然后,对于每一处递归调用都用两组执行下面的规则的指令来代替
- 将所有的参数和局部变量的值存入栈,栈顶指针作为一个全程变量看待
- 建立第i个标号为L,并将i入栈,这个i的值用来计算返回地址。此标号放在规则7所描述的程序段中,
- 计算这次调用的各个实参的值,并且将这些值赋给相应的形参
- 插入一条无条件跳转语句,调到过程开始部分
- 如果此过程是函数则对递归过程中含有此次函数的调用的语句作如下处理,将该语句的此次函数调用部分用从栈顶取回该函数的代码来代替,其他部分代码照抄,并用4中的标号给主公语句
return
8.如果栈为空则执行正常返回
9. 否则将所有输出的参数的当前值赋给栈顶的那些相应的变量
10. 如果栈中有返回地址标号的下表,就插入一条次下表从栈中退出的代码,并且把这个小标值赋给一个未使用的变量
11.从栈中退出所有的局部
二叉树先根遍历迭代法
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* preorderTraversal(struct TreeNode* root, int* returnSize){
int*res=malloc(sizeof(int)*1000);
*returnSize=0;
if(root==NULL)return res;
struct TreeNode*stk[200];
int stk_top=0;
struct TreeNode*node=root;
while(stk_top!=0||node!=NULL){
while(node!=NULL){
res[(*returnSize)++]=node->val;
stk[stk_top++]=node;
node=node->left;
}
node=stk[--stk_top]->right;
}
return res;
}
思考:
- 在leetcode编写过程中想起来递归转非递归的方法,结合书上代码理解了下过程
- 递归出口肯定不变,这里在这个迭代过程用不到直接写
- 然后是压栈,把所有左结点压栈,这是开始的模块
- 然后是弹栈,将栈顶弹出,这是最后那个模块
- 确认当前的状态,对应中间那个模块
- 然后是思考,对应规则7和书上的例子是第二种情况不一样,对应第一种是函数调用的情况,程序又跑到L1,这里在外层套循环
- -最后返回
写一个求数组中最大元素的过程
#include<stdio.h>
//递归方法
int max1(int A[],int n,int i){
int j,k;
if(i<n-1){
j=max1(A,n,i+1);
if(A[i]>j)k=A[i];
else k=j;
}else{
k=A[n-1];
}
return k;
}
void main()
{
int n;
scanf("%d",&n);
int arr[n];
for(int i = 0;i < n;i++)
{
scanf("%d",&arr[i]);
}
int max=max1(arr,n,0);
printf("%d ",max);
}
思考:如果是求从索引为i到n的最大元素的话,那么
- 如果i和n一样的话就是n到n的最大元素为本身
- 那么我们分两类,
- 如果i<n则i和j后面的最大值比较,如果i大则最大值为它自己,否则为j,它的最后一层是最后两项作比较
- 如果为i=n则k=n;
- 如果求索引的话,显然
考虑消去递归
思考:
-
在i不断增加的过程中总会有最后1项的最大值是它本身,那么我们要取得一个数来给到j,这必须是一个值,所以它并不能归为i<n之中,应该返回一个值,而else就可以让前面的递归无所顾忌,让else给它配值
-
i与i后面的最大值比较,当最下层时候,i知道,后面的最大值呢,是A[n-1]那么此时调用是i=n-1,不符合之前i<n的形式所以要分类。
递归算法设计
简单01背包问题
#include<stdio.h>
int knap(int goods[],int n,int m){
//程序出口
if(n==1){
if(goods[n-1]!=m)return 0;
else return 1;
}
else {
//递推关系
if(goods[n-1]<m){
//如果有解
if(knap(goods,n-1,m-goods[n-1])){
return knap(goods,n-1,m-goods[n-1]);
}else{
return knap(goods,n-1,m);
}
}
else{
return knap(goods,n-1,m);
}
}
}
int main(){
int n;
scanf("%d",&n);
int goods[n];
for(int i=0;i<n;i++){
scanf("%d",&goods[i]);
}
int m;
scanf("%d",&m);
printf("结果:%d",knap(goods,n,m));
}
n阶Hanoi 塔问题
void move(int* A,int *ASize,int* B,int *BSize,int*C,int*CSize,int n){
//判断出口情况
//如果Asize=n=1
// A->B,A->C,B->C;
// 如果是2的话
// A->B;A->C;B->C;
//都满足递推关系;所以用n=1出口;否则两个出口
if(n==1){//这里是优先级问题+动态数组和静态数组:区别问题+扩展问题
// C[*CSize++]=A[*ASize--];
C[*CSize++]=A[*ASize-1];
(*ASize)--;
}else{
//下面的每一line它们的size都不一样因为是传值
move(A,ASize,C,CSize,B,BSize,n-1);
move(A,ASize,B,BSize,C,CSize,1);//明显是递归出口可以替换为C[*CSize++]=A[*ASize--];
move(B,BSize,A,ASize,C,CSize,n-1);
}
}
void hanota(int* A, int ASize, int* B, int BSize, int** C, int* CSize){
B=malloc(sizeof(int)*ASize);
BSize=0;
*C=malloc(sizeof(int)*ASize);
*CSize=0;
//传值:符合逻辑,改变确切的值
//注意n应该传ASize,而ASize应该传地址;符合逻辑
move(A,&ASize,B,&BSize,*C,CSize,ASize);
}
//递归每一次都改变了ASIZe等的值所以直接给就可以;方便
上面的注释中://这里是优先级问题+动态数组和静态数组:区别问题+扩展问题
参考我的博文:优先级问题;
动态数组和静态数组:区别问题+扩展问题
顺便复习下代码运行过程
递归关系式的计算
递归算法的时间复杂度分析
递归算法的设计思想,算法的时间复杂度计算方法可以采用递归方式然后求得
求数列之中的最大值,递归方法