【解递归】二叉树遍历的递归与非递归写法启发下的对非线性增长递归程序的非递归写法总结(上)
验证了一下在图书馆脑洞出来的算法实现的正确性
当谈到一个程序的递归与非递归写法时,一般首先想到数据结构中二叉树三种遍历对应的递归与非递归写法。当递归程序中只有一次对自身的调用,也就是线性增长时,很容易解递归。例如二分查找的递归与非递归写法。
int find(int a[],int key,int low,int high){//a为升序序列 if(low<=high){ int mid=(low+high)/2; if(a[mid]==key) return mid; else if(a[mid]<key) return find(a,key,mid+1,high); else return find(a,key,low,mid-1); } return -1; } int find_2(int a[],int key,int low,int high){//a为升序序列 int i=low,j=high; int mid=0; while (i<=j){ mid=(low+high)/2; if(a[mid]==key)return mid; else if(a[mid]<key) i=mid+1; else j=mid-1; } return -1; }
而程序中有多次自身调用时,例如二叉树的遍历、快速排序的递归写法,均为二次调用,此时递归非线性增长,故需要引入数据结构--栈用来保存现场,实现程序的非递归写法。
为了有个二叉树做测试,用递归写了个先序和中序序列确定一颗二叉树:
class node{ public char e; public node left,right; } class child{ public int left_low,left_high; public int right_low,right_high; } public child findInOrder(char in[],int root,int low,int high){ for(int i=low;i<=high;i++){ if(in[i]==root){ child child=new child(); child.left_low=low;child.left_high=i-1; child.right_low=i+1;child.right_high=high; return child; } } return null; } public void PreInBuild(node parent,char pre[],int p_low,char in[],int in_low,int in_high){ if (p_low<pre.length){ parent.e=pre[p_low]; child child= findInOrder(in,pre[p_low],in_low,in_high); if (child.left_low<=child.left_high) { parent.left=new node(); PreInBuild(parent.left, pre, p_low + 1 , in, child.left_low, child.left_high); } if (child.right_low<=child.right_high) { parent.right=new node(); PreInBuild(parent.right, pre, p_low + child.left_high - child.left_low + 2, in, child.right_low, child.right_high); } } }
char pre[]={'a','b','d','e','h','c','f','g','i','k','j'}; char in[]={'d','b','h','e','a','f','c','i','k','g','j'};
对应的先序遍历:
public void preOrder(node tree){ if(tree!=null){ System.out.print(tree.e+""); preOrder(tree.left); preOrder(tree.right); } }
非递归写法:
public void preOrder_2(node tree){ node p=tree; Stack<node>stack=new Stack<>(); while (p!=null||!stack.empty()){ while (p!=null){ System.out.print(p.e+""); stack.push(p); p=p.left; } if(!stack.empty()){ p=stack.pop().right; } } }
接着让我们看一下快排的递归写法:
1 public int patition(int a[],int low,int high){ 2 if(low<high&&high<a.length) { 3 int key = a[high]; 4 while (low < high) { 5 while (low < high && a[low] <= key) 6 low++; 7 a[high] = a[low]; 8 while (low < high && a[high] >= key) 9 high--; 10 a[low] = a[high]; 11 } 12 a[low] = key; 13 return low; 14 } 15 return -1; 16 }
public void quickSort(int a[],int low,int high){ if(low<high&&high<a.length){ int mid=patition(a,low,high); quickSort(a,low,mid-1); quickSort(a,mid+1,high); } }
形式上与二叉树先序遍历的递归写法相似,所以将二叉树节点视为对场景的保存,出栈的也就是我们以线性方式实现非线性时忽略的部分,(在二叉树中是每个节点的右子树,故在快排中是partition后key值的右侧。我们要对右半的起止下标压栈,故有:
class SharpShot{ public int low,high; } public void quickSort_2(int a[],int low,int high){ Stack<SharpShot>stack=new Stack<>(); int i=low,j=high; while (i<j||!stack.empty()){ while (i<j) { int mid = patition(a, i, j); SharpShot shot=new SharpShot(); shot.low=mid+1; shot.high=j; stack.push(shot); j=mid-1; } if(!stack.empty()){ SharpShot shot=stack.pop(); i=shot.low; j=shot.high; } } }
由此可见递归生成k叉树,k>2时大佬们自己体会吧……