数据结构和算法 (三)数据结构基础之树、二叉树

Java面试宝典之二叉树的实现

我们接着上一篇数据结构继续讲解。本章系数据结构之树与二叉树,从这章开始,我们就要介绍非线性结构了,这些内容理解起来比线性表稍难一些,我尽量写的通俗一些,如果读的过程中有任何问题,请按上述方式联系我!

一、树

树 形结构是一类重要的非线性结构。树形结构是结点之间有分支,并具有层次关系的结构。它非常类似于自然界中的树。树结构在客观世界中是大量存在的,例如家 谱、行政组织机构都可用树形象地表示。树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构;在数据库系统中,可用树来组织 信息;在分析算法的行为时,可用树来描述其执行过程。本章重点讨论二叉树的存储表示及其各种运算,并研究一般树和森林与二叉树的转换关系,*后介绍树的应 用实例。

二、二叉树

二叉树(BinaryTree)是n(n≥0)个结点的有限集,它或者是空集(n=0),或者由一个根结点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。关于更多概念,请大家自己上网查询,我们这里将用代码实现常见的算法。更多的概念,请访问:http://student.zjzk.cn/course_ware/data_structure/web/SHU/shu6.2.3.1.htm 。

 

面试中常考到树的前序,中序,后序和层序遍历,这篇博文就带你深度剖析一下二叉树的各类遍历算法的实现

 

二叉树的遍历主要有四种,前序、中序、后序和层序

 

遍历的实现方式主要是:递归和非递归

 

递归遍历的实现非常容易,非递归的实现需要用到栈,难度系数要高一点。

 

一、二叉树节点的定义

 

二叉树的每个节点由节点值、左子树和右子树组成。

 

1
2
3
4
5
6
7
class TreeNode{
public:
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
}

 

二、二叉树的遍历方式

 

前序遍历:先访问根节点,再访问左子树,*后访问右子树

 

中序遍历:先访问左子树,再访问根节点,*后访问右子树

 

后序遍历:先访问左子树,再访问右子树,*后访问根节点

 

层序遍历:每一层从左到右访问每一个节点。

 

举例说明:(以下面的二叉树来说明这四种遍历)

 

%title插图%num

 

前序遍历:ABDFGHIEC
中序遍历:FDHGIBEAC
后序遍历:FHIGDEBCA
层序遍历:ABCDEFGHI

 

1、二叉树的建立

首先,我们采用广义表建立二叉树(关于广义表的概念,请查看百科的介绍:http://baike.baidu.com/view/203611.htm)

我们建立一个字符串类型的广义表作为输入:

String  expression = “A(B(D(,G)),C(E,F))”;与该广义表对应的二叉树为:

%title插图%num

写代码前,我们通过观察二叉树和广义表,先得出一些结论:

  • 每当遇到字母,将要创建节点
  • 每当遇到“(”,表面要创建左孩子节点
  • 每当遇到“,”,表明要创建又孩子节点
  • 每当遇到“)”,表明要返回上一层节点
  • 广义表中“(”的数量正好是二叉树的层数

根据这些结论,我们基本就可以开始写代码了。首先建议一个节点类(这也属于一种自定义的数据结构)。

 

  1. package com.xtfggef.algo.tree;  
  2. public class Node {  
  3.     private char data;  
  4.     private Node lchild;  
  5.     private Node rchild;  
  6.     public Node(){  
  7.     }
  8.     public char getData() {  
  9.         return data;  
  10.     }
  11.     public void setData(char data) {  
  12.         this.data = data;  
  13.     }
  14.     public Node getRchild() {  
  15.         return rchild;  
  16.     }
  17.     public void setRchild(Node rchild) {  
  18.         this.rchild = rchild;  
  19.     }
  20.     public Node getLchild() {  
  21.         return lchild;  
  22.     }
  23.     public void setLchild(Node lchild) {  
  24.         this.lchild = lchild;  
  25.     }
  26.     public Node(char ch, Node rchild, Node lchild) {  
  27.         this.data = ch;  
  28.         this.rchild = rchild;  
  29.         this.lchild = lchild;  
  30.     }
  31.     public String toString() {  
  32.         return “” + getData();  
  33.     }
  34. }

根据广义表创建二叉树的代码如下: 

  1. public Node createTree(String exp) {  
  2.         Node[] nodes = new Node[3];  
  3.         Node b, p = null;  
  4.         int top = –1, k = 0, j = 0;  
  5.         char[] exps = exp.toCharArray();  
  6.         char data = exps[j];  
  7.         b = null;  
  8.         while (j < exps.length – 1) {  
  9.             switch (data) {  
  10.             case ‘(‘:  
  11.                 top++;
  12.                 nodes[top] = p;
  13.                 k = 1;  
  14.                 break;  
  15.             case ‘)’:  
  16.                 top–;
  17.                 break;  
  18.             case ‘,’:  
  19.                 k = 2;  
  20.                 break;  
  21.             default:  
  22.                 p = new Node(data, null, null);  
  23.                 if (b == null) {  
  24.                     b = p;
  25.                 } else {  
  26.                     switch (k) {  
  27.                     case 1:  
  28.                         nodes[top].setLchild(p);
  29.                         break;  
  30.                     case 2:  
  31.                         nodes[top].setRchild(p);
  32.                         break;  
  33.                     }
  34.                 }
  35.             }
  36.             j++;
  37.             data = exps[j];
  38.         }
  39.         return b;  
  40.     }

思路不难,结合上述的理论,自己断点走一遍程序就懂了!2、二叉树的递归遍历

二叉树的遍历有三种:先序、中序、后序,每种又分递归和非递归。递归程序理解起来有一定的难度,但是实现起来比较简单。对于上述二叉树,其:

a 先序遍历

A B D G C E F

b 中序遍历

D G B A E C F

c 后序遍历

G D B E F C A

先、中、后序递归遍历如下:

  1. /** 
  2.      * pre order recursive 
  3.      *  
  4.      * @param node 
  5.      */  
  6.     public void PreOrder(Node node) {  
  7.         if (node == null) {  
  8.             return;  
  9.         } else {  
  10.             System.out.print(node.getData() + ” “);  
  11.             PreOrder(node.getLchild());
  12.             PreOrder(node.getRchild());
  13.         }
  14.     }
  15.     /** 
  16.      * in order recursive 
  17.      *  
  18.      * @param node 
  19.      */  
  20.     public void InOrder(Node node) {  
  21.         if (node == null) {  
  22.             return;  
  23.         } else {  
  24.             InOrder(node.getLchild());
  25.             System.out.print(node.getData() + ” “);  
  26.             InOrder(node.getRchild());
  27.         }
  28.     }
  29.     /** 
  30.      * post order recursive 
  31.      *  
  32.      * @param node 
  33.      */  
  34.     public void PostOrder(Node node) {  
  35.         if (node == null) {  
  36.             return;  
  37.         } else {  
  38.             PostOrder(node.getLchild());
  39.             PostOrder(node.getRchild());
  40.             System.out.print(node.getData() + ” “);  
  41.         }
  42.     }

二叉树的递归遍历实现起来很简单,关键是非递归遍历有些难度,请看下面的代码:3、二叉树的非递归遍历

先序非递归遍历:

 

复制代码
 public void myPreOrderNoRecursive(Node node) {
        Node[] nodes = new Node[CAPACITY];  //创建栈
        int top = -1;                   //栈顶下标
        Node temp;                    //临时变量
        if (node == null) {
            return;
        }
        temp = node;      
        top++;        //将根节点压入栈中
        nodes[top] = temp;
       //循环遍历栈中元素,一个个取出处理,然后再把右节点和左节点压入栈
        while (top > -1) {
            temp = nodes[top];
            top--;
            mPreOrder2 += temp.getData();
            if (temp.getRchild() != null) {
                top++;
                nodes[top] = temp.getRchild();
            }

            if (temp.getLchild() != null) {
                top++;
                nodes[top] = temp.getLchild();
            }
        }

    }






public static void preOrderStack(Node node){
    if(node == null){
        return;
    }
    Stack<Node> stack = new Stack();
    stack.push(node);
    while(!stack.empty()){

        Node temp = stack.pop();
         Log.e("VDSADGSA", temp.getData()+"");

        if(temp.getRchild() != null){
            stack.push(temp.getRchild());
        }

        if(temp.getLchild() != null){
            stack.push(temp.getLchild());
        }
    }
}

复制代码

 

原 理:利用一个栈,先序遍历即为根先遍历,先将根入栈,然后出栈,凡是出栈的元素都打印值,入栈之前top++,出栈之后top–,利用栈后进先出的原 理,右节点先于左节点进栈,根出栈后,开始处理左子树,然后是右子树,读者朋友们可以自己走一遍程序看看,也不算难理解!中序非递归遍历:

 

复制代码
 public void myInOrderNoRecursive(Node node) {
        Node[] nodes = new Node[CAPACITY];  //栈中只保存处理过左节点的根节点
        int top = -1;
        Node temp;
        if (node == null) {
            return;
        }
        temp = node;
        //循环处理 原始根节点和栈中已经处理过左节点的根节点
        while (temp != null || top > -1) {
            //处理原始根节点,将原始根节点的左节点取出并处理,再将处理过左节点的原始根节点压入栈中
            while (temp != null) {
                top++;
                nodes[top] = temp;
                temp = temp.getLchild();
            }

            //从栈中取出处理过左节点的根节点,处理根节点,然后将右节点当作原始根节点进行处理
            if (top > -1) {
                temp = nodes[top];
                mInOrder2 += temp.getData();
                top--;
                temp = temp.getRchild();
            }
        }

    }







public static void inOrderStack(Node node){
    if(node == null){
        return;
    }
    Stack<Node> stack = new Stack<>();
    while(node != null || !stack.empty()){

        while(node != null){
            stack.push(node);
            node = node.getLchild();
        }

        if(!stack.empty()){
            Node pop = stack.pop();
            Log.e("VDSADGSA", pop.getData()+"");
            node = pop.getRchild();
        }

    }

}

复制代码

 

原理省略。后续非递归遍历:

 

复制代码
  public void myPostOrderNoRecursive(Node node) {
        Node[] nodes = new Node[CAPACITY];
        int top = -1;
        Node temp1, temp2 = null;
        if (node == null) {
            return;
        }
        temp1 = node;
        while (temp1 != null || top > -1) {
            while (temp1 != null) {
                top++;
                nodes[top] = temp1;
                temp1 = temp1.getLchild();
            }
            //从栈中取出处理过左节点的根节点,处理根节点,然后将右节点当作原始根节点进行处理
            if (top > -1) {
                temp1 = nodes[top];

                //从栈中取出的处理过左节点的根节点,判断其右节点是否处理过,如果右节点为空或者上个循环处理的节点就是其右节点则认为取出的根节点的右节点已经处理
                if (temp1.getRchild() == null || temp1.getRchild() == temp2) {
                    mPostOrder2 += temp1.getData();
                    top--;
                    temp2 = temp1;
                    temp1 = null;
                } else {
//                    如果 右节点没有处理,则将右节点当作原始根节点进行处理
                    temp2 = null;
                    temp1 = temp1.getRchild();
                }

            }
        }
    }






public static void postOrderStack(Node node){
    if(node == null){
        return;
    }
    Stack<Node> stack = new Stack<>();
    Node temp = null;

while (node != null || !stack.empty()){

    while(node != null ){
        stack.push(node);
        node = node.getLchild();
    }


    if(!stack.empty()){
        Node peek = stack.peek();
        if(peek.getRchild() == null || peek.getRchild() == temp){
            Log.e("VDSADGSA", peek.getData()+"");
            stack.pop();
            temp = peek;
            node = null;
        }else {
            temp = null;
            node = peek.getRchild();
        }
    }
}
}





复制代码

 

三、树与二叉树的转换

本人之前总结的:

%title插图%num

 

这部分概念的其他知识,请读者自己上网查看。