笔者按:曾经刚开始学习数据结构和算法时,总会为简洁隽永的递归代码而惊叹,也想写出如此优雅的代码,但是思考过程真的实属不易!!!那时候递归都会尽量用显式栈来规避。
生活中的递归!
首先,对递归要有一个类似盗梦空间或者平行世界的认识,就像现实世界和你的梦境,当你开始进入梦境,这两个世界完全隔离(平行世界和平行万物)除了你的(思想、记忆),思想记忆可以看成从现实世界传到梦境里的(传入参数),而梦中惊醒又可以看成噩梦惊醒,将噩梦里的记忆经历带回现实世界(返回参数),如果梦里的自己又开始做梦进入第三个空间,就这样逐层递进,然后逐层归还,这大概就是递归的“奥义”!!!这就是我们生活中的递归!
总的原则:
在树的各种算法中,我们要确立这样的思想,一个节点既代表节点本身,又代表以该节点为节点的一棵树,适用于父节点的算法流程同样适用于它,哪怕它是空节点!!!对于空节点,it's time to return!!!
1.WarmUp:
先看一个简单的:树的高度求法——叶子结点高度为1,然后逐层加一;某个节点的高度是其左子节点高度和右子节点高度的较大值+1,对于叶子结点下的空节点返回0。(还记得extended binary tree扩展二叉树的定义吗,这个定义使得我们的算法可以处理空节点的情况,也就是空节点和普通节点都是同等的)
public int height() {
// TODO Auto-generated method stub
return height(this.root);
}
//递归调用height求当前节点的高度
public int height(BinaryNode<T> p)
{
if(p==null)
return 0;
int lcount=height(p.left);
int rcount=height(p.right);
return (lcount>=rcount)?lcount+1:rcount+1;
}
2再来看看二叉树的遍历
中序(根)遍历、先序(根)遍历、后根(序)遍历、层次遍历。
举个栗子瞧瞧看:
2.1先序(根)遍历
遍历的先后如图:
需要注意的是蓝线描述的是访问节点的先后过程,左子树遍历完后直接回到当前递归层访问的根节点,然后再去遍历该根节点的右子树。
遍历过程就是一个深度优先搜索的过程,借用栈的非递归写法:
/*用非递归先根遍历以node为根节点的(子)树
*在二叉树先序遍历非递归算法中,先将根结点压栈,在栈不为空的时候执行循环:让栈顶元素p出栈,访问栈顶元素p,
*如果p的右孩子不为空,则让其右孩子先进栈,如果p的左孩子不为空,则再让其左孩子进栈
*(注意:进栈顺序一定是先右孩子,再左孩子)。
*/
public void preOrderNoRecursive(BinaryNode<T> node)
{
LinkedStack<BinaryNode<T>> stack=new LinkedStack<BinaryNode<T>>();
BinaryNode<T> p=node;
//停止while循环的条件是栈为空并且p指向null
while(!stack.isEmpty() || p!=null)
{
while(p!=null)
{
System.out.print(p.data);
stack.push(p);//入栈操作表示访问该节点
p=p.left;
}
if(!stack.isEmpty())
{
p=stack.getTop();
stack.pop();
p=p.right;
}
}
}
递归思路:访问到某个节点时就直接打印或者保存记录,然后以左子节点作为递归函数的参数,再以右子节点作为递归函数的参数,当然遇到空节点就return。
public void preOrder() {
//先根次序遍历:访问根节点,遍历左子树,遍历右子树 TODO Auto-generated method stub
System.out.print("先根次序遍历二叉树");
preOrder(root);
System.out.println();
}
public void preOrder(BinaryNode<T> p)
{
if(p!=null)
{
System.out.print(p.data.toString()+" ");
preOrder(p.left);
preOrder(p.right);
}
else
return;
}
2.2中序(根)遍历
递归思路:以左子节点作为递归函数的参数,然后访问到某个节点时就直接打印或者保存记录,再以右子节点作为递归函数的参数,当然遇到空节点就return。
非递归中序遍历:
//用非递归中根遍历以node为根节点的(子)树
public void inOrderNoRecursive(BinaryNode<T> node)
{
System.out.println("非递归中根遍历: ");
LinkedStack<BinaryNode<T>> stack=new LinkedStack<BinaryNode<T>>();
BinaryNode<T> p=node;
while(!stack.isEmpty() || p!=null )
{
if(p!=null)
{
stack.push(p);
p=p.left;
}
else
{
p=stack.pop();
System.out.print(p.data+" ");
p=p.right;
}
}
}
public void inOrder() {
//中根次序遍历:遍历左子树,访问根节点,遍历右子树 TODO Auto-generated method stub
System.out.print("中根次序遍历二叉树");
inOrder(root);
System.out.println();
}
public void inOrder(BinaryNode<T> p)
{
if(p!=null)
{
preOrder(p.left);
System.out.print(p.data.toString()+" ");
preOrder(p.right);
}
}
2.3后序(根)遍历
递归思路:以左子节点作为递归函数的参数,再以右子节点作为递归函数的参数,然后访问到某个节点时就直接打印或者保存记录。当然遇到空节点就return。
@Override
public void postOrder() {
// 后根次序遍历:遍历左子树,遍历右子树,访问根节点TODO Auto-generated method stub
System.out.print("后根次序遍历二叉树");
postOrder(root);
System.out.println();
}
public void postOrder(BinaryNode<T> p)
{
if(p!=null)
{
postOrder(p.left);
postOrder(p.right);
System.out.print(p.data.toString()+" ");
}
}
非递归版本这里提一下不详细写了:
//对于非递归后根遍历,使用顺序栈比
public void postOrderNoRecursive(BinaryNode<T> node)
{
System.out.println("非递归后根遍历: ");
SeqStack<BinaryNode<T>> stack=new SeqStack<BinaryNode<T>>();
BinaryNode<T> p=node;
int[] tag=new int[this.count()];
//停止while循环的条件是栈为空并且p指向null
while(!stack.isEmpty() || p!=null)
{
while(p!=null)
{
stack.push(p);
tag[stack.getTopIndex()]=0;
p=p.left;
}
while(!stack.isEmpty() && tag[stack.getTopIndex()]==1)
{
System.out.print(stack.pop());
}
if(!stack.isEmpty())
{
p=stack.getTop();
tag[stack.getTopIndex()]=1;
p=p.right;
}
}
}
2.4层次遍历
public void levelOrder(BinaryNode<T> node) {
// TODO Auto-generated method stub
LinkedQueue<BinaryNode<T>> q=new LinkedQueue<BinaryNode<T>>();
BinaryNode<T> p=node;
q.enqueue(p);
while(!q.isEmpty())
{
p=q.dequeu();
System.out.print(p.data.toString());
if(p.left!=null)
q.enqueue(p.left);
if(p.right!=null)
q.enqueue(p.right);
}
}
@Override
public void levelOrder()
{
levelOrder(this.root);
}
3.树节点的增删查改
3.1查询——如果当前节点里的值不是目标值则先查询左子节点,再查询右子节点。
public BinaryNode<T> search(T key) {
// TODO Auto-generated method stub
return search(this.root,key);
}
public BinaryNode<T> search(BinaryNode<T> p,T key)
{
if(p==null||key==null)
return null;
if(p.data.equals(key))
return p;
BinaryNode<T> find=search(p.left,key);
if(find==null)
find=search(p.right,key);
return find;
}
3.2在以p为根节点的树中查询node节点的位置——如果当前节点里的值不是目标值则先查询以左子节点为根节点的树里是否有目标值,再查询以右子节点为根节点的树里是否有目标值。
public BinaryNode<T> getParent(BinaryNode<T> node) {
// TODO Auto-generated method stub
return getParent(this.root,node);
}
//查找以p为根的子树中节点node的父节点
public BinaryNode<T> getParent(BinaryNode<T> p,BinaryNode<T> node)
{
if(p==null)
return null;
if(p.left==node||p.right==node)
return p;
BinaryNode<T> find=getParent(p.left,node);
if(find==null)
find=getParent(p.right,node);
return find;
}
3.3删除
//插入x作为节点p的左(右)子节点,如果p节点已经存在对应的左(右)节点,则原左(右)节点作为插入节点的左(右)子节点
@Override
public BinaryNode<T> insertChild(BinaryNode<T> p, T x, boolean leftChild) {
// TODO Auto-generated method stub
if(p==null || x==null)
return null;
BinaryNode<T> new_node=new BinaryNode<T>(x);
if(leftChild)
{
if(p.left==null)
p.left=new_node;
else
{
new_node.left=p.left;
p.left=new_node;
}
}
else
{
if(p.right==null)
p.right=new_node;
else
{
new_node.right=p.right;
p.right=new_node;
}
}
return new_node;
}
//删除节点p的左(右)子节点,这里规定将以p的左(右)子节点为根节点的树全部删去
@Override
public void removeChild(BinaryNode<T> p, boolean leftChild) {
// TODO Auto-generated method stub
if(p!=null)
{
if(leftChild)
p.left=null;
else
p.right=null;
}
}
@Override
public void removeAll() {
// TODO Auto-generated method stub
this.root=null;
}