数据结构与算法

  1. 1. 集合:所有元素同属于一个集合,除此之外数据元素之间灭有任何关系
  2. 2. 线性表:数据元素之间存在一一对应的关系(简单来说,线性表中的元素都有一个序的概念)。栈和队列是操作受限的线性表
  3. 3. 树:一对多
  4. 4. 图:多对多
  5. 数据结构的物理表示:顺序映象(连续存储)和非顺序映象(离散存储)

线性表的顺序映象实现

编程注意事项:
    add(String element, int index)
    1. 检查index在线性表中的位序是否合法([0,size])
    2. 检查,数组容量(不够的话扩容(1.5), 扩容之后可能容量可能超出线性表的容量上限)。//移位运算的优先级,
    3. 根据指定位序,放入元素(放入元素之前, 移动元素,空出带插入位置,放入) //System.arrayCopy()
    4. Size++

    remove(index)
    1. 检查index位序是否合法([0,size - 1])
    2. 移动位置,覆盖(删除)
    3. size--
    两个使用的api: Arrays.copyOf(), System.arrayCopy() //copy以后记得将数组赋值给原数组

    isEmpty()
    size()
    toString            //使用更高效的StringBuilder类
    indexOf(Stirng e)   //处理当查找空元素,以及查找的元素有null。(==和equals的使用)
例:
   public class SequentialLinearList {
    private String[] elements;                                    //实际盛放数据的数组
    private int size;                                             //数组的装数据的长度
    private final static int MAX_ARRAY_SIZE = Integer.MAX_VALUE;  //定义一个线性表的容量上线
    private final static int DEFAULT_INIT_ELEMENTS = 10;          //默认初始化elements的数据元素个数10个
    public SequentialLinearList() {                               //构造方法执行初始化动作
        elements = new String[DEFAULT_INIT_ELEMENTS];             //避免硬编码
    }
    public SequentialLinearList(int capacity) {
        if(capacity <= 0 || capacity > MAX_ARRAY_SIZE) {
            throw new IllegalArgumentException("illegal arguments " + capacity);
        }
        elements = new String[capacity];
    }
    public void add(String e,int index) {      //添加元素
        rangeCheckForAdd(index);               //校验线性表中待插入元素的指定位置,是否合法
        ensureCapacityInternal(size + 1);      //首先判断一下,当前的数组中,是否可以在插入元素
        int copyLength = size - index;         //将元素,放入线性表中,int copyLength = (size - 1) - index + 1;先将size都换到0开始

        System.arraycopy(elements,index, elements,index + 1, copyLength); //api比较智能  复制的范围(index,index + size - index -1)                                         
        elements[index] = e;                                              //components at positions <code>srcPos</code> through                                         
        size++;                                                           //<code>srcPos+length-1</code> were first copied to a temporary
    }
    private void ensureCapacityInternal(int  minCapacity) {     //所需的最小容量都无法满足要求
        if(minCapacity - elements.length >0 ) {                 //minCapacity > elements.length与minCapacity-elements.length>0不等价。
                                                             //当size取Integer.MAX_VALUE时minCapacity<0,前面不成立,后面成立
            int oldCapacity = elements.length;                  //访问局部变量要比访问成员变量,要快
            int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容,一次扩多少呢?效率和内存利用率的一个折中,扩容多少呢?(1.5倍扩容),要加括号,优先级
                                                                //如果一次可以添加多个元素的话,判断扩容之后,容量是否足够(我们一个一个的添加元素不会遇到这种情况)
            newCapacity = hugeCapacity(newCapacity);            //临界问题,扩容的数值超出数组最大容量
            elements = Arrays.copyOf(elements, newCapacity);    //记得赋值给原数组
        }
    }
    private int hugeCapacity(int newCapacity) {
        if(newCapacity < 0) {
            if(size + 1 < 0) {                   //可能,线性表中存放的元素个数还未达到上限,但是,扩容之后的数值,超出了线性表中的元素个数上限  
                throw new OutOfMemoryError();
            }
            newCapacity = MAX_ARRAY_SIZE;
        }
        return newCapacity;
    }
    private void rangeCheckForAdd(int index) {
        if(index < 0 || index > size) {
            throw new ArrayIndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }
    /*  
    private void arrayCopy (String[] src, int offset, String[] desc, int length) { //arrayCopy自己写的写法
        for (int i = offset,j = 0; i < offset + length; i++,j++) {
            desc[j] = src[i];
        }
    }*/
    public String remove(int index) {      //删除元素
        rangeCheckForRemove(index);        //检测删除元素位置是否合法
        int movNum = size - index - 1;     //删除元素,int movNum = (size - 1) - index + 1 - 1;
        String oldValue = elements[index];
        if(movNum > 0) {
            System.arraycopy(elements,index + 1, elements, index, movNum);
        }
        elements[size - 1] = null;
        size--;
        return oldValue;
    }
    public void add(String e) {
        ensureCapacityInternal(size + 1);    //容量检测
        elements[size] = e;
        size++;
    }
    private void rangeCheckForRemove(int index) {
        if(index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }
    private String outOfBoundsMsg(int index) { //降低代码的耦合
        return "index outOfBounds , size = " + size + ", index = " + index;
    }
    @Override
    public String toString() {
        if(size == 0) {
            return "[]";
        }
        StringBuilder builder = new StringBuilder("[");  //使用StringBuilder提高效率
        for (int i = 0; i < size; i++) {
            builder.append(elements[i]);
            if(i == size - 1) {
                builder.append(']');
                break;
            }
            builder.append(", ");            //方法返回的是 StringBuilder,可以链式调用
        }
        return builder.toString();
    }
    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }
    public int indexOf(String e) {          //查找当前线性表中,和给定元素相匹配的,第一个元素出现的位置
        if(null == e) {                     //当查找的元素为null时
            for(int i = 0; i < size; i++) {
                if(e == elements[i]) {      //用==判断
                    return i;
                }
            }
        } else {
            for(int i = 0; i < size; i++) {
                if(e.equals(elements[i])) { //肯定了e不等于null,故用它的equals方法
                    return i;
                }
            }
        }
        return -1;
    }
}

线性表(顺序映象)编程中的知识点

1. 扩容问题,数组超过预设的容量,如何考虑效率和内存的折中,sun公司做的实验是1.5容量,
   容量上限是Integer.MAX_VALUE,预设容量是10
2. 计算临界容量时候要考虑,线性表中存放的元素个数还未达到上限,但是,扩容之后的数值,超出了线性表中的元素个数上限
3. 将检查用户输入单独抽出一个方法,输入不合法则抛出异常。注意插入和删除的合法输入差一个
4. 容量改变后记得size++或size--
5. 查找线性表元素时,针对用户输入为null时的搜索。以及用户输入不为null搜索(==和equals的使用)
//和源码比较发现在使用ArryList的无参构造法创建数组时,系统默认创建的是一个空数组{},该数组经过.length为0
//防止创建了数组对象,却一直不使用它,浪费空间,直到向里面添加元素时才扩容为默认长度10
//这个数组只是起一个标志作用,int[] a={} a.length=0, 数组在java中是一个内置对象,a中此时存的数组对象的地址

Arrays.copyOf()和System.arrayCopy()

1. elements = Arrays.copyOf(elements, newCapacity);
 //将elements[]数组复制到newCapacity长度的数组中
2. System.arrayCopy(Object src, int srcPos, Object dest, int destPos, int length)             
//当src数组和dest数组是同一个时,函数会通过一个temp数组,将原数组中的srcPos到xx长度为length
//复制到原数组的destPos到xx长度为length

StringBuilder类

普通String对象一旦创建,其值无法被修改(见字符串内存图解),效率很低
StringBuilder直接操作字节流,效率更高。
使用:
StringBuilder builder = new StringBuilder("[")
builder.append(']');
builder.toString();

访问局部变量要比访问成员变量快

程序中经常使用elements.length,可以将这个值提取出来
例如:
int oldCapacity = elements.length;

数学上的等价在计算机中不等价

minCapacity > elements.length 与 minCapacity-elements.length > 0 不等价。
minCapacity = Integer.MAX_VALUE + 1 ,elements.length = Integer.MAX_VALUE 
第一个式子小于0的数大于Integer.MAX_VALUE不成立
第二个式子成立

搜索元素考虑null的问题

if(null == e) {                     //当查找的元素为null
   for(int i = 0; i < size; i++) {
      if(e == elements[i]) {      //null用==判断
         return i;
      }
   }
} else {
   for(int i = 0; i < size; i++) {
      if(e.equals(elements[i])) { //肯定了e不等于null,故用e的equals方法
         return i;
      }
   }
}

内部类的使用static避免内存泄漏 (见详图static内部类)

static内部类.png

普通成员内部类对象都隐式的持有其外部类对象的一个引用
(只要inner类对象有被使用,inner有指向outer类对象的隐式引用,outer类对象无法被销毁)
但是一旦该内部类加了static修饰符,该内部类对象不会再持有其外部类对象的引用,可以规避内存泄露
注:
  普通类对象中包含一个对其它对象的引用,若没有引用变量指向普通类对象,普通类对象会变成垃圾(不同之处是指向的方向)

线性表的非顺序映象实现 (链表)

public class MyLinkedList {
    private int size;    //链表长度
    private Node first;  //指向第一个节点
    private Node last;   //指向最后一个节点
    private static class Node {       //链表节点,内部类使用static避免内存泄漏
      String data;
      Node next;
    }
    public  void insertHead(String e){  //头插法
        linkNode(null,e);
    }
    public  void insertTail(String e){   //尾插法
       linkNode(last,e);
    }

    public void add(String e, int index) { //添加节点

        checkAddInput(index);              //检查输入是否合法
        Node pre = getPreNode(index);      //获取插入节点的前一个节点
        linkNode(pre, e);                  //链接操作
    }
    private void linkNode(Node pre, String e) {
        Node node = new Node();
        node.data = e;

        if(size == 0){                  //如果里面没有元素
            node.next =first;
            first = node;
            last = node;
        }else {
            if(pre == null){             //如果插在0处
                node.next =first;
                first = node;            //错误核查如果按错误执行下去为什么会一直运行
            }else {
                if(pre.next == null){    //如果插在尾部改last
                    last = node;
                }
                node.next = pre.next;    //添加元素
                pre.next = node;
            }
        }
        size++;
    }
    private Node getPreNode(int index) {
        if(index == 0 || first == null){
            return null;
        }
        //另法获取PreNode更简洁
        /*Node node = first;
        for(int i = 1; i < index; i++) {  //i的作用类似于count
            node = node.next;
        }
        return node;*/
        int count = 1;
        Node node = first;

        while(node!= null){
            if(count == index){  
                return node;
            }
            count++;
            node= node.next;
        }
        return node;
    }
    private void checkAddInput(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException(outOfBoundMsg(index));
        }
    }
    private String outOfBoundMsg(int index) {
        return "用户输入非法 index = " + index + ",size = " + size;
    }

    public String remove(int  index) {  //删除节点       
        checkRemoveInput(index);        //检查输入      
        Node pre = getPreNode(index);   //获取删除节点的前一个节点
        String str = unlinkNode(pre);
        return str;
    }
    private String unlinkNode(Node pre) {      
        String str = first.data ;    //删除的节点 

        if(size==1){                 //只有一个节点
            first=last=null;
        }else {         
            if(pre == null){         //删除第一个节点
                first = first.next;
            }else {               
                if(pre.next == null) //删除最后一个节点
                {
                    last = pre;
                }
                str = pre.data;
                pre.next = pre.next.next;
            }
        }
        size--;
        return str;
    }
    public int indexOf(String e){        //获取字符所在位置
        int index = 0;
        if(e == null){
            for ( Node node = first; node!=null; node = node.next ) {
                if(node.data == e){       //使用==比较,null用==比较
                    return index;
                }
                index++;
            }
        }else {
            for ( Node node = first; node!=null; node = node.next ) {
                if(node.data.equals(e)){  //使用equals比较,而且还要考虑使用的元素不能为空,空则用==
                    return index;
                }
                index++;
            }
        }
        return  -1;
    }
    private void checkRemoveInput(int index) {
        if(index < 0 || index > size-1 || size == 0){ //注意长度为0的情况
            throw new IllegalArgumentException(outOfBoundMsg(index));
        }
    }
    public boolean isEmpty(){
        if(size == 0){
            return  true;
        }
        return false;
    }
    public int getSize(){
        return size;
    }
    @Override
    public String toString(){
        if(first==null){
            return "[]";
        }
        StringBuilder stringBuilder= new StringBuilder("[");  //使用stringBuilder效率更高
        for (Node node  = first; node!=null; node = node.next) {
            stringBuilder.append(node.data);
            if(node.next==null){
                break;
            }
            stringBuilder.append(", ");
        }
        stringBuilder.append(']');
       return  stringBuilder.toString();
        /*if(first==null){   //另一种输出效率低
            return "[]";
        }
        String str="[";
        for (Node node  = first; node!=null; node = node.next) {
            str+=node.data;
            if(node.next==null){
                break;
            }
            str+=", ";
        }
        str+=']';
        return str;*/
    }
}
单链表编程中的知识点:
1. 添加节点,里面没有元素插入,插入在头部,插入在尾部,三种情况分别考虑
2. 删除节点,只有一个节点删除,删除第一个节点,删除最后一个节点,三种情况分别考虑
3. 查找线性表元素时,针对用户输入为null时的搜索。以及用户输入不为null搜索(==和equals的使用)

栈(顺序映象)实现

顺序栈实现:
public class MyStack {
    private int top;            //栈顶指针
    private String[] elements;  //数组
    private final static int MAX_CAPACITY = Integer.MAX_VALUE;  //最大长度 
    private final static int DEFAULT_INIT_ELEMENTS = 10;        //初始长度
    public MyStack() {
        elements = new String[DEFAULT_INIT_ELEMENTS];
    }

    public void push(String e) {  //入栈

        ensureCapacity(top+1);    //确保容量足够,top >= elements.length-1 ,先化成都由0开始
        elements[top] = e;        //入栈
        top++;
    }
    private void ensureCapacity(int mincapacity ) {
        if (mincapacity - elements.length >= 0) {
            int oldCapacity = elements.length;
            int newCapacity = oldCapacity +(oldCapacity>>1);
            newCapacity = checkNewCapacity(newCapacity);
            elements = Arrays.copyOf(elements,newCapacity );
        }
    }
    private int checkNewCapacity(int newCapacity) {
        if(newCapacity<0){
            if(top+2 < 0 ){
                throw new OutOfMemoryError("内存溢出");
            }
            newCapacity = MAX_CAPACITY;
        }
        return newCapacity;
    }

    public String pop(){          //出栈
        checkEmpty();
        top--;
        return  elements[top];
    }
    private void checkEmpty() {   //为了抛异常
        if(isEmpty()){
            throw new IllegalArgumentException("栈为空");
        }
    }
    public String peek(){         //输出栈顶
        checkEmpty();
        return  elements[top-1];
    }

    @Override
    public  String toString(){   //tostring
        if(top==0){
            return "[]";
        }
        StringBuilder stringBuilder = new StringBuilder("[");
        for (int i = top-1; i >= 0 ; i--) {
            stringBuilder.append(elements[i]);
            if(i == 0){
                stringBuilder.append("]");
                break;
            }
            stringBuilder.append(',');
        }
        return  stringBuilder.toString();
    }
    public boolean isEmpty(){    //是否为空
        if(top==0){
            return  true;
        }
        return false;
    }
}
注意:
    1. 注意事项和顺序表数组实现类似,需要考虑扩容
   2. tostring方法访问为null情形
    3. peek()方法输出栈顶元素,不删除元素
   4. 栈顶top指向的是承装元素的下一个空间

队列的实现(顺序映象) (见详图顺序映象队列空和队列满)

顺序映象队列空和队列满.png

解决队空和队满判断条件相同的方案:(rear=front)
1. 添加一个标志位,来区分当前究竟是队空还是队满的状态。
2. 浪费一个空间,约定以队列头指针在队尾的下一位置(指环状的下一位置)上,作为队列满的标志。
   //front指向队头,rear指向队尾的下一个元素
public class MyLinearQueue {

    private String[] elemets ;
    private final  static  int MAX_CAPACITY = Integer.MAX_VALUE;  //最大容量
    private final  static  int ORIGIN_CAPACITY = 10;              //默认容量
    private  int front;  //队头
    private  int rear;   //队尾  
    private  int size;   //队列长度
    public  int  getSize(){
       return  (rear-front + elemets.length)%elemets.length;
    }
    public MyLinearQueue(){
        elemets = new String[ORIGIN_CAPACITY];
    }
    public void  enQueue(String e){  //入队

        size = getSize(); 
        ensureCapacity(size + 2 );  //确保容量足够,注意这里的最小容量是size + 2,循环队列要留一个空间浪费

        elemets[rear] = e;          //进队
        rear = next(rear);
    }
    public String poll(){           //获取队头元素
        checkIsEmpty();
        return elemets[front];
    }
    public String deQueue(){        //出队
       String str =  poll();
       front = next(front);
       return  str;
    }

    private void checkIsEmpty() {  //检查是否为空
        if(isEmpty()){
            throw  new ArrayIndexOutOfBoundsException("队列为空");
        }
    }
    private void ensureCapacity(int minCapacity) {
        if( minCapacity- elemets.length>0){

            int oldCapacity = elemets.length;
            int newCapacity = oldCapacity + (oldCapacity>>1);
            newCapacity = checkNewCapacity(newCapacity);
            String[] temp = new String[newCapacity];

            elemets = arrryCopy(elemets,front,temp,getSize());      //自己写数组的拷贝操作
            front = 0;
            rear = size ;
        }
    }
    private String[] arrryCopy(String[] elemets, int front, String[] temp, int size) {
        for( int i=0;  size > 0 ; size--, front = next(front),i++){  //i++忘了
            temp[i] = elemets[front];
            }
            return temp;
    }
    private int next(int cur){
        return  (cur+1)%elemets.length;
    }
    private int checkNewCapacity(int newCapacity) {
        if(newCapacity<0){
            if(size+2<0){
                throw  new OutOfMemoryError("内存溢出");
            }
            newCapacity = MAX_CAPACITY;
        }
        return  newCapacity;
    }

    public boolean isEmpty() {   //是否为空
        if (getSize() == 0) {
            return true;
        }
        return false;
    }

    @Override
    public String toString (){   //toString
        if(isEmpty()){
            return "[]";
        }

        StringBuilder stringBuilder = new StringBuilder("[");
        for (int i = front ; rear != i ;  i = next(i)) {
            stringBuilder.append(elemets[i]+" ");
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}
注意:
   1. 队头出队,队尾进队
   2. 出队操作(front+1)%elements.length,入队操作(rear+1)%elements.length,队列空间(rear-front + elemets.length)%elemets.length
   3. 扩容操作还是1.5 容量,其中判断的最小容量是size + 2,循环队列要留一个空间浪费(此时front指向队头,rear指向队尾的下一个元素)  
   4. poll()是获取队头元素但不删除元素
   5. 大量使用private int next(int cur){return (cur+1)%elemets.length;} //都是加一数组长度取余,抽成一个方法,
   6. 队空判断条件front==rear,队满的条件(rear+1)%elements.length==front

树的定义

树(Tree)是n( n>=0)个结点的有限集合。在任意一颗非空树中:
1. 有且仅有一个特定的成为根(Root)的结点的
2. 当n>1的时,其余结点可分文m( m>0 )个互不相交的有限集T1,T2,..., Tm,
   其中每一个集合本身又是一棵树,并且称之为根的子树。
注:树的定义本身是一个递归定义,即在树的定义中又用到树的概念。

基本术语

1. 树的结点:包含一个数据元素即若干指向其子树的分支。结点拥有的子树的数目,称之为结点的度(degree)。
2. 度为0的结点称为叶子,或终端结点。度不为0的结点称为非终端结点或分支节点
3. 节点的子树的根,称为该结点的孩子(child),相应的该结点称为孩子的双亲(parent)
4. 同一个双亲的孩子之间互称兄弟(sibling)
5. 结点的祖先是从根到该结点所经分支上所有的结点。
6. 以某结点为根的子树中的任意一结点都称为该结点的子孙
7. 结点的层次(Level)从根开始定义,根为第一层,根的孩子为第二层。若某结点为在第l层,则其子树的根就在l+1层
8. 树中结点的最大层次称为树的深度(depth)或高度

二叉树

二叉树是另一种树形结构,它的特点是每个结点至多只有两颗子树(即二叉树中不存在度大于2的结点),
并且,二叉树有左右之分,其次序不能任意颠倒(有序)。有五种形态空树、只有根节点、只有左子树、只有右子树,左右子树都有

二叉树具有以下重要特性

1. 二叉树,在第i层至多有2^(i-1)个结点  (主要是在理论上使用的比较多)
2. 深度为k的二叉树至多有(2^k)-1个结点
3. 对任何一颗二叉树T,如果其终端结点数位n0,度为2的结点数位n2,则n0=n2+1  //1+n1+2*n2 = no+n1+n2
4. 具有n个结点的完全二叉树,的深度为log2n + 1(向下取整)
5. 如果对一颗有n个结点的完全二叉树(其深度为log2n + 1)的结点按层序编号,则对任意一结点,有
   1)如果i=1,则结点i是二叉树的根,无双亲;如果i > 1,则其双亲parent(i)是节点i/2
   2)如果2i > n,则结点i无左孩子(结点i为叶子结点);否则其左孩子lchild(i)是节点2i
   3)如果2i+1 > n,则结点i无右孩子,否则,其右孩子rchild(i)是结点2i+1

中序后序遍历构造二叉树,深度优先遍历,和广度优先遍历 (见后详图中序后序创建二叉树及中序先序创建二叉树)

中序后序创建二叉树 (2).png

中序后序创建二叉树.png

public class BinaryTree {

   //树节点定义
    public static class Node {
        char data;
        Node lChild;
        Node rChild;
        public Node() {
        }
        public Node(char data, Node lChild, Node rChild) {
            this.data = data;
            this.lChild = lChild;
            this.rChild = rChild;
        }
    }
   //递归创建树的节点
    public  Node build(char[] mid ,int lmid,int rmid ,char[] post ,int lpost,int rpost){
        //取出根节点的值
        char rootData = post[rpost];
        //获取中序遍历中根节点的位置
        int midRootIndex = getMidRootIndex(mid, lmid, rmid, post[rpost]);
        //检查输入是否合法
        checkInput(midRootIndex);
        //左右子树的长度
        int lNum = midRootIndex - lmid;
        int rNum = rmid - midRootIndex;
        //创建左子树
        Node l,r;
        if(lNum == 0){
            l = null;
        }else {
            l = build(mid,lmid,midRootIndex-1,post,lpost,lpost+lNum-1);  //最主要的错误就是这范围选择
        }
        //创建右子树
        if(rNum == 0){
            r = null;
        }else {
            r = build(mid,midRootIndex+1,rmid,post,rpost-rNum, rpost - 1);
        }

        //创建根节点
        return  new Node(rootData,l,r);                                //最后返回的是树的根节点
    }
    private void checkInput(int midRootIndex) {
        if(midRootIndex == -1)
        {
            throw new IllegalArgumentException("输入非法");
        }
    }
    private int getMidRootIndex(char[] mid, int lmid, int rmid, char root) {
        for (int i = lmid; i <= rmid ; i++) {
            if(mid[i] == root){
                return  i;
            }
        }
        return  -1;
    }
    //先序遍历
    public static void preOrder(Node root){
        if(root != null){
            //访问根节点
            System.out.print(root.data+" ");
            //访问左子树
            preOrder(root.lChild);
            //访问右子树
            preOrder(root.rChild);
        }
    }
    //层次遍历
    public static void BSF(Node root){
        //创建队列
        Queue<Node> queue = new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()){         //这里是一个循环
            //出队
            Node node  = queue.remove();
            System.out.print( node.data+" " );
            //左孩子进队
            if(node.lChild!=null){
                queue.add(node.lChild);
            }
            //右孩子进队
            if(node.rChild!=null){
                queue.add(node.rChild);
            }
        }
    }
}
注意:
   层次遍历算法:(借助队列,先进先出)
   1. 首先根节点出队,访问根节点,然后判断是否有左孩子,如果有左孩子,入队
   2. 继续判断,该节点是否有右孩子,有则入队
   3. 重复1,2 直到队列为空

计算树的深度算法

1. 如果一颗树只有一个节点,它的深度是1;
2. 如果根节点只有左子树而没有右子树,那么二叉树的深度应该是其左子树的深度加1;
3. 如果根节点只有右子树而没有左子树,那么二叉树的深度应该是其右树的深度加1;
4. 如果根节点既有左子树又有右子树,那么二叉树的深度应该是其左右子树的深度较大值加1;
public  static int getDepth(BinaryTree.Node node){
   if(node == null){  //先写节点为空的情况
      return 0;
   }  

   int lDepth = getDepth(node.lChild); //左子树的深度   
   int rDepth = getDepth(node.rChild); //右子树的深度

   return lDepth>rDepth? lDepth+1:rDepth+1;
}
注:(倒过来分析)
    节点无,深度为0
    节点为1,深度为1
    左右子树深度相同,深度+1
    左右子树深度不同,最大深度+1    

递归分析:画一个简单的树,从最低层一个个计算,怎么递归得到最终值

计算二叉树中叶节点个数

public static int getLeafNodeNum(BinaryTree.Node node){
   if(node==null){
      return 0;
   }
   if(node.rChild==null&&node.lChild==null){
      return 1;
   }
   return getLeafNodeNum(node.rChild)+getLeafNodeNum(node.lChild);
}

排序算法