数据结构总结-- 树和二叉树

树的性质

1.树的结点数 = 结点度数和 + 1;(n0 + n1 + nm ….+1)
2.度为m的数第i层最多m^(i-1)次方个结点
3.高度为h的m次树,最多(m^h)/(m-1)
4.n个结点的m次树,最小高度logm(n(m-1)+1)

二叉树的定义

结点定义,根节点,左孩子,右孩子
java代码:

/**
     * 结点类
     * 
     * @author Administrator
     *
     * @param 
     */
    static class BTNode {
        T value;
        BTNode leftChild;
        BTNode rightChild;

        /**
         * 结点构造方法
         * 
         * @param value
         */
        public BTNode(T value) {
            this.value = value;
            leftChild = null;
            rightChild = null;
        }

        public BTNode() {
            value = null;
            leftChild = rightChild = null;
        }

    }

二叉树类:

public class BinaryTree<T> {

    /**
     * 结点类
     * 
     * @author Administrator
     *
     * @param 
     */
    static class BTNode<T> {
        T value;
        BTNode leftChild;
        BTNode rightChild;

        /**
         * 结点构造方法
         * 
         * @param value
         */
        public BTNode(T value) {
            this.value = value;
            leftChild = null;
            rightChild = null;
        }

        public BTNode() {
            value = null;
            leftChild = rightChild = null;
        }

    }

    /**
     * 根节点
     */
    BTNode root;

    /**
     * 构造方法
     */
    public BinaryTree() {
        root = new BTNode(null);
    }

二叉树的性质

1.叶子节点数= 双分支结点+1,分支数 = 总结点数-1;
2.编号为i (i>=0,i<=n)的结点,左孩子为2i + 1,右孩子为2i+2,一般使用数组存储时使用。如果起始i ==1,则左孩子 2i,右孩子2i+1

二叉树的存储结构

1.顺序存储

优点:结构紧凑,没有额外的指针空间,随机存取能力强,
缺点:插入删除耗时,所需空间较大时,较难找到连续的内存空间,不利于碎片空间利用。

2.链式存储结构

优点:碎片利用率高,不产生空结点,
缺点:不能随机存取,必须从头遍历

二叉树的基本操作

由括号表示法创建二叉树

括号表示法:兄弟结点用“,”分割,父节点后()的内表示其子节点

Java实现

/**
     * 使用括号表示法创建二叉树
     * @param kuohao
     * @return
     */
    public BTNode createBTree(String kuohao){

        BTNode [] stack= (BTNode[]) new Object[maxSize]; 
        int top =-1;
        //是否是左子结点
        boolean isLeft = true;

        BTNode tempRoot = null ;

        for (int i = 1; i < kuohao.length(); i++) {
            switch (kuohao.charAt(i)) {
            case '(':
                isLeft = true;
                stack[++top] = tempRoot;

                break;
            case ')':
                top--;
                break;
            case ',':
                isLeft = false;
                break;
            default:
                BTNode tempNode = new BTNode<>(kuohao.charAt(i));
                if(tempRoot == null){
                    tempRoot = tempNode;
                }else{
                    if(isLeft){
                        stack[top].leftChild = tempNode;
                    }else{
                        stack[top].rightChild = tempNode;
                    }
                }
                break;
            }
        }
        return tempRoot;
    }

二叉树的遍历,递归实现

所谓遍历就是找到结点并对其进行一定的操作

先序遍历

二叉树是递归结构,遍历很显然也是递归的,遍历顺序:根节点–》左子结点–》右子节点

/**
     * 先序遍历
     */
    public void preOrder(BTNode root) {
        // 对根节点进操作
        System.out.println(root.value.toString());
        preOrder(root.leftChild);
        preOrder(root.rightChild);
    }

中序遍历

遍历顺序:左子结点–》根节点–》右子节点


/**
     * 中序遍历
     * 
     * @param root
     */
    public void inOrder(BTNode root) {
        inOrder(root.leftChild);
        System.out.println(root.value.toString());
        inOrder(root.rightChild);
    }

后序遍历

遍历顺序:左子节点–》右子节点–》根节点

/**
     * 后序遍历
     * 
     * @param root
     */
    public void postOrder(BTNode root) {
        postOrder(root.leftChild);
        postOrder(root.rightChild);
        System.out.println(root.value.toString());
    }

二叉树的非递归遍历

非递归遍历使用栈保存需要遍历的结点,出栈表示遍历,因为栈的后入先出,适合回溯。

栈需要维护栈顶指针top,初始为top=-1,入栈top++,出栈top–;

先序遍历

先序遍历,根节点进栈,栈不为空时循环出栈,根节点出战,将根节点孩子进栈,注意先将右孩子进栈,再将左孩子进栈,

java 实现:

/**
     * 非递归先序遍历
     * @param root
     */
    public void preOrderNoRecur(BTNode root){
        if(root == null){
            return;
        }
        int top =-1;
        BTNode [] stack= (BTNode[]) new Object[maxSize];
        top ++;
        stack[top] = root;//根节点入栈

        while(top>-1){
            BTNode current = stack[top];//出栈
            top --;

            System.out.println(current.value.toString());//访问根节点,下次循环访问左子结点(如果存在)

            if(current.rightChild !=null){
                stack[top] = current.rightChild;//右子结点入栈
                top ++;
            }
            if(current.leftChild != null){//右子节点入栈
                stack[top] = current.leftChild;
                top++;
            }
        }
    }

中序遍历

中序遍历,开始访问的结点是根节点的最左子节点,在该节点之前的节点都进栈,出栈最左子节点访问之,将其右子节点入栈,重复上面的步骤。直到节点为null 或者栈空。两重循环,外循环判是否栈空结束,内循环入栈左子树。

java实现:

/**
     * 非递归中序遍历
     * 
     * @param root
     */
    public void inOrderNoRecur(BTNode root) {
        if (root == null) {
            return;
        }
        int top = -1;
        BTNode[] stack = (BTNode[]) new Object[maxSize];
        //
        top++;
        stack[top] = root;
        BTNode current = root;// 根节点入栈
        while (top > -1 || current != null) {// ||
            while (current.leftChild != null) {
                stack[top] = current.leftChild;
                top++;
            }
            // current 为根的左子结点都入栈了
            if (top > -1) {
                current = stack[top];
                top--;
                //
                System.out.println(current.value.toString());
                // stack[top] = current.rightChild;//入栈不是在这,在前面的循环
                // top++;
                current = current.rightChild;// 这里处理右子节点,下一步将右子树的左子树入栈
            }
        }
    }

后序遍历

难点在于如何判断一个节点的右子树已经访问过了,需要一个标志刚刚访问过的节点 previous,如果current->rightChild == previous 那么说明current的右子树已经访问过,可以访问current。还要一个标志左子树是否全访问过。

后序遍历是先访问左子树,在访问右子树,最后访问根节点。先将左子树全部入栈,再判断栈顶的右子树是否访问过(即判断刚才访问的是否是栈顶的右子节点),是则访问栈顶出栈,否则重复上面步骤访问右子树的左子树,直到栈空。

三次循环。外部循环判断是否栈为空结束,内部1将左子树并入栈,内部2判断栈顶是否可以访问,可以则出栈,不可以则将右子树的左子树入栈。

栈中保存的是当前节点current的所有祖先节点

java实现:

/**
     * 非递归后序遍历
     */
    public void postOrderNoRecur(BTNode root) {
        if (root == null) {
            return;
        }
        int top = -1;
        BTNode[] stack = (BTNode[]) new Object[maxSize];
        stack[++top] = root;
        while (top > -1) {
            BTNode current = stack[top];
            while (current != null) {
                top++;
                stack[top] = current;
                current = current.leftChild;
            }
            // 左子结点都入栈了,开始判断栈顶是否能访问,
            boolean leftCompeleted = true;
            // 上一次访问的节点
            BTNode previous = null;
            while (leftCompeleted && top > -1) {
                BTNode stackTop = stack[top];
                if (stackTop.rightChild == previous) {// previous 说明没有右子节点
                    top--;
                    previous = stackTop;// !! 将stacktop设为之前访问过的节点。
                    System.out.println(stackTop.value.toString());
                } else {
                    current = current.rightChild;
                    leftCompeleted = false;
                }
            }
        }
    }

层次遍历

层次遍历也叫广度优先遍历,从根节点开始,第一层遍历完遍历第二层,借助队列入队保存每层遍历过的结点,每次出队遍历其子节点,同时将子节点入队。队列先进先出,适合按层遍历。

java实现

/**
     * 使用数组实现队列管理结点访问顺序
     * 
     * @param root
     */
    @SuppressWarnings("unchecked")
    public void layOrder(BTNode root) {
        if (root == null) {
            return;
        }
        int max = 50;
        int front = 0, rear = 0;
        int count = 0;

        //创建object数组转为泛型数组
        BTNode[] queue = (BTNode[]) new Object[max];

        // 入队
        queue[rear] = root;
        rear = (rear + 1) % max;
        count++;

        while (count > 0) {
            //出队
            BTNode out = queue[front];
            front = (front + 1) % max;
            count --;

            //遍历出队结点的左右结点
            //入队
            if(out.leftChild != null){
                queue[rear] = out.leftChild;
                rear = (rear+1)%max;
                count ++;
            }
            if(out.rightChild != null){
                queue[rear] = out.rightChild;
                rear = (rear+1)%max;
                count ++;
            }

        }

    }

查找节点的父节点

利用层次遍历法,在队列出队时,会遍历其左右子节点,此时比较待查找的结点和左右子节点是否相等,相等则返回刚刚出队的父节点。
特殊情况,待查结点是根节点,结点为null,结点不在树上

Java实现

public BTNode findParentNode(BTNode child){
        if (child == null || child == root) {
            return null;
        }
        int max = 50;
        int front = 0, rear = 0;
        int count = 0;

        //创建object数组转为泛型数组
        BTNode[] queue = (BTNode[]) new Object[max];

        // 入队
        queue[rear] = root;
        rear = (rear + 1) % max;
        count++;

        BTNode out;
        while (count > 0) {
            //出队
            out = queue[front];
            front = (front + 1) % max;
            count --;

            //遍历出队结点的左右结点
            //入队
            if(out.leftChild != null){
                //判断是否是待查的子节点,是则返回刚刚出列的节点,为待求的父节点
                if(out.leftChild.value == child.value){
                    return out;
                }
                queue[rear] = out.leftChild;
                rear = (rear+1)%max;
                count ++;
            }
            if(out.rightChild != null){
                if(out.rightChild.value == child.value){
                    return out;
                }
                queue[rear] = out.rightChild;
                rear = (rear+1)%max;
                count ++;
            }

        }
        //所有遍历完了没找到,则返回null,不在树上
        return null;
    }

求结点的左右兄弟结点

由上面的求父节点的方法,求得父节点后,再求该节点的兄弟结点即可

public BTNode getBrotherNode(BTNode child) {

        BTNode parent = findParentNode(child);
        // 没有父节点
        if (parent == null || child == null) {
            return null;
        }
        if (parent.rightChild == child) {// 返回做兄弟
            return parent.leftChild;
        } else if (parent.leftChild == child) {
            return parent.rightChild;
        } else {
            return null;
        }

    }

求树的高度

求树高度就是后序遍历树,取左子树和右子树较大者+1。
递归实现:

/**
     * 求树的高度,递归后序遍历
     * @param root
     * @return
     */
    public int getTreeHeight(BTNode root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = getTreeHeight(root.leftChild);
        int rightHeight = getTreeHeight(root.rightChild);
        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }

非递归实现主要是非递归后序遍历的修改。
判断是否是叶子节点,是则更新最大高度。

求某一节点所在的层次,或者某一节点的高度

/**
     * 求任意节点的高度
     * 
     * @param root
     * @param node
     * @param h,初始值为1,递归了多少次
     * @return
     */
    public int nodeHeight(BTNode root, T node, int h) {
        if (node == null) {
            return 0;
        }
        if (root == null) {
            return 0;
        }
        if (root.value == node) {
            return h;
        }
        int leftHeight = nodeHeight(root.leftChild, node, h + 1);
        if (leftHeight == 0) {
            return nodeHeight(root.rightChild, node, h + 1);
        }
        return leftHeight;
    }

将一个二叉树插入到另一棵树

找到待插入的节点,将其左子树改为要插入的二叉树。

/**
     * 插入子树
     * 
     * @param subTree
     * @param node
     */
    public void insertSubTree(BinaryTree subTree, T node) {
        if (node == null) {
            return;
        }
        if (subTree.root == null) {
            return;
        }
        BTNode found = findNode(root, node);
        if (found.leftChild == null) {
            found.leftChild = subTree.root;
        }
    }

查找节点

递归查找,先序遍历

  /**
     * 查找节点
     * 
     * @param root
     * @param node
     * @return
     */
    private BTNode findNode(BTNode root, T node) {
        if (root == null) {
            return null;
        }
        if (root.value == node) {
            return root;
        }
        BTNode found = null;
        found = findNode(root.leftChild, node);
        if (found != null)
            return found;

        return findNode(root.rightChild, node);
    }

删除子树

删除子树即删除某一节点的左子树或者右子树的所有节点。

递归删除节点,后序遍历树,先删除该节点的左子树,再删右子树,最后删根节点

javaj实现

/**
     * 删除所有节点
     * 
     * @param node
     */
    public void deleteNodes(BTNode node) {
        if (node == null) {
            return;
        }
        if (root == null) {
            return;
        }
        deleteNodes(root.leftChild);
        deleteNodes(root.rightChild);
        node = null;// 置为null
    }

    /**
     * 删除左子树
     * @param node
     */
    public void deleteSubTree(BTNode node) {
        if (node == null)
            return;
        deleteNodes(node.leftChild);
    }

重建树

有先序遍历和中序遍历可以重建树,同理由后序遍历和中序遍历实现方式一样,都是先在中序序列上找到根节点,在重建左右子树。

/**
     * 有前序和中序序列,重建二叉树
     * @param preStr
     * @param inStr
     * @param nodesNum
     * @param preStart
     * @param inStart
     * @return
     */
    public BTNode rebuildBinaryTree(T[] preStr, T[] inStr, int nodesNum, int preStart, int inStart) {
        if (preStr == null || inStr == null || nodesNum <= 0) {
            return null;
        }
        BTNode tempRoot = new BTNode<>();
        tempRoot.value = preStr[preStart];// 先序根节点为第一个
        int i;
        // 在中序中找到根节点
        for (i = inStart; i < nodesNum; i++) {
            if (inStr[i] == preStr[preStart]) {
                break;
            }
        }
        // 递归创建左右子树
        //调整先序和中序开始构建的下标
        tempRoot.leftChild = rebuildBinaryTree(preStr, inStr, i, preStart + 1, inStart);
        tempRoot.rightChild = rebuildBinaryTree(preStr, inStr, nodesNum - i - 1, preStart + i + 1,  i + 1);
        return tempRoot;

线索化二叉树

若节点的左右子节点为空,可以利用节点的空指针指向,该节点的直接前去和直接后继。

得到的线索化二叉树,根据不同的遍历方式不一样,

你可能感兴趣的:(Android,数据结构与算法)