面试题(十二)谈谈递归

来源:互联网 发布:淘宝卖家寄快递的软件 编辑:程序博客网 时间:2024/04/28 02:54

递归程序简洁,但是不是很好理解,当自己写的时候,每个人的递归设计可能还不一样,

递归程序的设计首先是接口的设计,明白每一层递归:

向下请求什么?

向上交付什么?

什么时候截止?

参数怎么传递?

下面分析几种递归的模式

一、自顶向下,逐层收拢,上层需要下层的结果

代码(1)

int feb(int n){if (n==1||n==2){return 1;}elsereturn feb(n-1)+feb(n-2);}

这是最简单的斐波那契数列的递归形式,这是典型的自顶向下,递归的的开头是截止条件,上层需要下层的结果,然后每一层都处于等待状态,最后达到截止条件,逐层向上收拢交付。

代码(2)

Node *mergeLinkListRC(Node *pHead1,Node *pHead2){if (pHead1==NULL){return pHead2;}if (pHead2==NULL){return pHead1;}Node *pCur=NULL;if (pHead1->nData<pHead2->nData){pCur=pHead1;pCur->next=mergeLinkListRC(pHead1->next,pHead2);}else{pCur=pHead2;pCur->next=mergeLinkListRC(pHead1,pHead2->next);}return pCur;}

这是合并两个有序链表的递归写法,比代码1看起来复杂一点,开头的两个判断仍然是截止条件,当递归调用自己的时候,必然会发生一个事情,就是参数的改变,同时如果本身有返回值,则需要利用这个返回值向上层交付,如果没有返回值,则可能是以引用参数传递的形式,让下层去改变这个引用

对于代码( 2 ),要将两个有序链表合并,每层函数的参数是两个头节点,返回值是这两个头结点应该在新链表中靠前的那个节点,同时还要改变这个靠前节点的next域;而改变这个next域需要下层提供一个结果,于是同样的问题,不一样的参数,让下一递归去解决,当到达截止条件的时候,逐层交付,那些处于等待状态的next域依次被改变;

代码中有个pCur指针,这个指针是个中间变量,用来保存那些需要改变next域的节点地址,同时这个pCur也作为一个返回值。这个递归是很巧妙的;


二、自顶向下,将问题的规模减小后直接return自己

这种类型的递归,与第一种相比,逻辑上缺少了收缩的过程,每一层的任务是将问题的规模减小

代码(3)

int binarySearch(int *pArray,int toFind,int i,int j){if (i>j){return -1;}if (i==j){if (pArray[i]==toFind){return i;}elsereturn -1;}if (i<j){int x=(i+j)/2;if (pArray[x]<toFind){return binarySearch(pArray,toFind,x+1,j);}else if(pArray[x]>toFind){return binarySearch(pArray,toFind,i,x-1);}else{return x;}}}
这是二分查找的递归形式,这里是递归的另一种形式,将问题的规模减小,让下层去解决,上层并不执行任何东西,凡是看到return XXX(参数变化),这种大多是如此;


三、自上而下,依次递减规模,带for循环

代码(4)

void test(char *pStr,int index){int nLen=strlen(pStr);if (index==nLen-1){printf("%s\n",pStr);}for (int i=index;i<nLen;i++){swap(&pStr[i],&pStr[index]);test(pStr,index+1);swap(&pStr[i],&pStr[index]);}}

这是一个打印全排列的小程序,可以打印出一个字符串的全排列,这个代码的核心在于每次test结束时候字符串的排列是否会改变?

分成两个问题:

如果内层test不改变,那么外层test不改变,这个很明显;

问题变成如果内层test改变,那么在哪儿改变?这与外层test矛盾

所以test对pStr并不改变,得到这个结论,这个递归就容易理解了,这段程序就是一个规模递减的递归调用

四、二叉树类型的

二叉树与递归有着天然的切合度

代码(5)

void createBinaryTree(int *pArray,int nSize,int index,Node *&pCur){if (index>nSize-1){pCur=NULL;return;}else{if (2*index+1>nSize-1)//是叶子{pCur=new Node;pCur->left=NULL;pCur->right=NULL;pCur->nData=pArray[index];return;}else if (2*index+2>nSize-1)//只有左节点{pCur=new Node;pCur->nData=pArray[index];pCur->left=NULL;pCur->right=NULL;createBinaryTree(pArray,nSize,2*index+1,pCur->left);}else{pCur=new Node;pCur->nData=pArray[index];pCur->left=NULL;pCur->right=NULL;createBinaryTree(pArray,nSize,2*index+1,pCur->left);createBinaryTree(pArray,nSize,2*index+2,pCur->right);}}}void traverse(Node *pRoot){if (pRoot==NULL){return;}else{cout<<pRoot->nData<<'\t';traverse(pRoot->left);traverse(pRoot->right);}}

第一段是代码是利用数组创建一颗二叉树,传递进去的是一个二叉树的根节点地址

createBinaryTree(int *pArray,int nSize,int index,Node *&pCur)  

index是数组的标号,pCur是当前节点的地址,在递归代码中我们经常要传递指针,但是要考虑清楚我们是不是应该传递引用

原则是这样的,如果我们仅仅通过这个指针进行取值操作,那么没必要引用,如果要改变这个指针的指向,比如让他指向一个新的节点,代码中有类似  pCur=new Node  这样的语句,那么就要传递指针的引用

第二段代码是最最普通的二叉树的递归遍历,属于规模递减形式的,很容易理解。






0 0
原创粉丝点击