二叉树三种遍历递归及非递归实现&层次遍历(Java版)

  1. 二叉树基本概念
    二叉树是每个结点至多有两颗子树的树,子树有左右之分,其次序不能任意颠倒。
  2. 几种特殊二叉树
    1) 满二叉树:高度为h,并且含有2h-1个结点的二叉树。即树的每一层都是满的(每层结点数为2(h-1)) 。若对满二叉树编号,从上自下,从左至右,根节点编号为1,则对于编号为i 的结点,如果有双亲,则双亲为⌊i/2⌋,如果有左孩子,左孩子为2i,如果有右孩子,右孩子为2i+1.如下图所示:
    满二叉树.png

    2) 完全二叉树:若1个高度为h,含有n个结点的二叉树,按照从上自下,从左至右的顺序对结点进行编号,1~n的结点与满二叉树一一对应,则该二叉树为完全二叉树。如下图所示:
    完全二叉树.png

    3) 二叉排序树:左子树上所有关键字均小于根节点关键字,右子树所有结点关键字均大于根节点关键字。左子树和右子树又各是一颗二叉排序树。
    4) 平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过1。
  3. 二叉树遍历
    二叉树定义
 public class TreeNode {
     int val;
     TreeNode left;
     TreeNode right;
     TreeNode(int x) { val = x; }
 }

非递归实现借助栈,前序、中序、后序是相对于根节点而言,根据根节点输出的位置,根、左、右为前序遍历;左、根、右为中序遍历;左、右、根为后序遍历。
1) 前序遍历(PreOrder):
如果二叉树为空,则什么也不做;否则:
a)访问根节点;
b)先序遍历左子树;
c)先序遍历右子树。
递归实现:

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> list = new ArrayList<>();
    if(root == null)
        return list;
    helper(root, list);
    return list;
}
public void helper(TreeNode root, List<Integer> list) {
    list.add(root.val);
    if(root.left != null) 
        helper(root.left, list);
    if(root.right != null)
        helper(root.right, list);
}

常规版非递归实现:先将根节点入栈,然后弹出(访问根节点),然后将其右结点入栈,再将其左结点入栈(栈是先进后出,我们需要先访问左结点,所以先将右结点入栈),然后弹出左结点,对左结点也进行同样的操作(右结点先入栈,左结点入栈),直至栈为空并且访问完了所有结点。

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> list = new ArrayList<>();
    if(root == null)
        return list;
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    while(!stack.isEmpty()) {
        root = stack.pop();
        list.add(root.val);
        if(root.right != null)
            stack.push(root.right);
        if(root.left != null)
            stack.push(root.left);
    }
    return list;
}

简单版非递归实现:与上一种方式的不同是在访问根节点的时机不相同。常规方式是在出栈时访问根节点,将其写入list中,而简单方式是入栈的同时将值写入list,所以我们只要保证按照前序遍历的顺序去遍历二叉树即可。

    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Deque<TreeNode> stack = new ArrayDeque<>();
        TreeNode node = root;
        while(node != null || !stack.isEmpty()) {
            if(node != null) {
                stack.push(node);
                list.add(node.val);
                node = node.left;
            }else {
                node = stack.pop();
                node = node.right;
            }
        }
        return list;
    }

2) 中序遍历(InOrder):
如果二叉树为空,则什么也不做;否则:
a)中序遍历左子树;
b)访问根节点;
c)中序遍历右子树。
递归实现:

     public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null)
            return list;
        helper(root, list);
        return list;
    }
    public void helper(TreeNode root, List<Integer> list) {

        if(root.left != null)
            helper(root.left, list);
        list.add(root.val);
        if(root.right != null)
            helper(root.right, list);
    }

常规版非递归实现:从根节点依次遍历左结点,如果左结点没有被访问过,则入栈,当没有左结点或左结点被访问过的时候,弹出栈顶元素,将其写入list,并将其右结点入栈。重复上述操作。

    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Map<TreeNode, Integer> map = new HashMap<>();
        Stack<TreeNode> stack = new Stack<>();
        if(root == null) 
            return list;
        stack.push(root);
        while(!stack.isEmpty()) {
            root = stack.peek();
            while(root.left != null) {
                if(map.containsKey(root.left))
                    break;
                stack.push(root.left);
                root = root.left;
            }
            root = stack.pop();
            list.add(root.val);
            map.put(root, 1);
            if(root.right != null)
                stack.push(root.right);
               
        }
        return list;
    }

简单版非递归实现:从根节点依次将左结点入栈,当没有左结点的时候,弹出栈顶元素,将其写入list,并将其右结点入栈。重复上述操作。与常规方法相比,省去了查看左子树是否被访问过的步骤,对每个结点,都是先遍历其左子树,所以当访问到该结点的时候,可以确保其左子树已经被访问过了,只需要访问其本身和其右结点即可。

    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Deque<TreeNode> stack = new ArrayDeque<>();
        TreeNode node = root;
        while(node!=null || !stack.isEmpty()) {
            if(node != null) {
                stack.push(node);
                node = node.left;
            }else {
                node = stack.pop();
                list.add(node.val);
                node = node.right;
            }
        }
        return list;
    }

3) 后序遍历(PostOrder):
如果二叉树为空,则什么也不做;否则:
a)后序遍历左子树;
b)后序遍历右子树;
c)访问根节点。
递归实现:

    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null)
            return list;
        helper(root, list);
        return list;
    }
    public void helper(TreeNode root, List<Integer> list) {

        if(root.left != null)
            helper(root.left, list);

        if(root.right != null)
            helper(root.right, list);
        list.add(root.val);
    }

常规版非递归实现:将根节点左子树入栈,当访问到叶子结点,出栈,查看该叶子结点的父节点是否有右结点,有的话,如果右结点未访问则将其入栈,当一个节点的左右结点都已经访问过或者不包含左右结点,则出栈。

    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Map<TreeNode, Integer> map = new HashMap<>();
        Stack<TreeNode> stack = new Stack<>();
        if(root == null)
            return list;
        stack.push(root);
        while(!stack.isEmpty()) {
            root = stack.peek();
            if(root.left == null && root.right == null) {//不含左右结点时,出栈
                root = stack.pop();
                list.add(root.val);
                map.put(root, 1);
            }else if((root.left!=null && root.right == null && map.containsKey(root.left))||(root.right != null && root.left == null && map.containsKey(root.right)) || (root.left != null && root.right != null && map.containsKey(root.left) && map.containsKey(root.right))){//包含子节点,但是子节点被访问过,出栈
                root = stack.pop();
                list.add(root.val);
                map.put(root, 1);
            }else {
                while(root.left != null) {
                    if(map.containsKey(root.left)) {
                        break;
                    }
                    stack.push(root.left);
                    root = root.left;
                }
                if(root.right != null) {
                   if(map.containsKey(root.right)) {
                        break;
                    }
                    stack.push(root.right);
                }
            }
        }
        return list;
    }

简单版非递归实现:后序遍历的输出顺序是左、右、根,当我们采用先序遍历的方法,但是先遍历右子树,实现的效果是根、右、左,刚好和后序遍历的结果想法,所以我们通过add(0, node)的方式将顺序反序,达到我们想要的效果。

public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Deque<TreeNode> stack = new ArrayDeque<>();
        TreeNode node = root;
        while(node != null || !stack.isEmpty()) {
            if(node != null) {
                stack.push(node);
                list.add(0, node.val);
                node = node.right;
            }else {
                node = stack.pop();
                node = node.left;
            }
        }
        return list;
    }
  1. 层次遍历:将二叉树按层输出,借助队列实现。借助null来标记一层的结束。当读取到的结点不是null,将该结点的左右结点入队列,当读到null,如果此时队列不空,则继续入null标志新的一层的结尾。
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();
        if(root == null)
            return list;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        queue.add(null);
        int num = 0;
        List<Integer> tempList = new ArrayList<>();
        list.add(tempList);
        while(!queue.isEmpty()) {
            TreeNode temp = queue.poll();
            if(temp != null) {
                list.get(num).add(temp.val);
                if(temp.left != null)
                    queue.add(temp.left);
                if(temp.right != null)
                    queue.add(temp.right);
            }else {
                if(!queue.isEmpty()) {
                    num++;
                    tempList = new ArrayList<>();
                    list.add(tempList);
                    queue.add(null);
                }
            }
        }
        return list;
    }

参考:
Preorder, Inorder, and Postorder Iteratively Summarization
二叉树的几种遍历递归与非递归java实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345