数据结构与算法-二叉树

  1. 树的实现
    实现树的一种方法是在每一个节点除数据外还有一些链,使得该节点的每一个儿子都有一个链指向他,但是该节点儿子的数量是不知道的,可以采用如下的方式:
public class TreeNode{
    Object element;
    TreeNode firstChild;//该节点的第一个儿子
    TreeNode nextSibling;//该节点的兄弟节点
}

节点 ni 深度为从根到 ni 的唯一路径的长;节点 ni 高度是节点 ni 到一片树叶的最长路径的长。

二叉树

二叉树的特点是每个节点都不能有多于两个儿子,其平均深度为 O(N1/2)
1. 二叉树的实现
二叉树的节点最多包含两个节点,因而可以保存直接链接到他们的链。

public class BinaryNode{
    Object element;
    BinaryNode left;
    BinaryNode right;
}

2.二叉树的例子-表达式树
表达式树的叶子节点是操作数、变量或者常数,而其他节点为操作符。
利用后缀表达式转换为表达式树遵从以下几个原则:
- 如果读入的是操作数,建立单节点树,压入栈中。
- 如果读入的是操作符,那么从栈中弹出两棵树T1和T2并形成新的树,根节点为操作符,左子树为T2,右子树为T1。

二叉查找树

二叉查找树与二叉树的区别在于,树中的每个节点node,其左子树中所有项的值小于node节点的值;其右子树中所有项的值大于node节点的值。
1.二叉查找树的实现
这里实现了二叉查找树类的框架,包括节点的插入,寻找,删除,遍历等功能

public class BinarySearchTree<T extends Comparable<? super T>> {

    private static class BinaryNode<T>{
        T element;
        BinaryNode<T> left;
        BinaryNode<T> right;

        public BinaryNode(T element) {
            this(element, null, null);
            // TODO Auto-generated constructor stub
        }
        public BinaryNode(T element,BinaryNode<T> left,BinaryNode<T> right){
            this.element = element;
            this.left = left;
            this.right = right;
        }
    }

    private BinaryNode<T> root;

    public BinarySearchTree(){
        root = null;
    }

    public void insert(T element){
        this.insert(root, element);
    }
    private BinaryNode<T> insert(BinaryNode<T> node, T element){
        /*2*/
    }

    public void makeEmpty(){
        root = null;
    }

    public boolean isEmpty(){
        return root == null;
    }

    public boolean contains(T element){
        return this.contains(root, element);
    }

    private boolean contains(BinaryNode<T> node, T element){
        /**/
    }

    public void remove(T element){
        this.remove(root, element);
    }

    private  BinaryNode<T> remove(BinaryNode<T> node,T element){

    }
    public T findMin(){
        if(isEmpty()) throw new NullPointerException();
        return findMin(root).element;
    }

    private BinaryNode<T> findMin(BinaryNode<T> node){

    }
    public void preOrderI(){
        this.preOrderI(this.root);
    }
    private void preOrderI(BinaryNode<T> root){
    }

    public void preOrderT(){
        this.preOrderT(this.root);
    }
    private void preOrderT(BinaryNode<T> root){
    }

    public void inOrderI(){
        this.inOrderI(this.root);
    }
    private void inOrderI(BinaryNode<T> root){
    }

    public void inOrderT(){
        this.inOrderT(this.root);
    }
    private void inOrderT(BinaryNode<T> root){
    }

    public void postOrderI(){
        this.postOrderI(this.root);
    }

    private void postOrderI(BinaryNode<T> root){
    }

    public void postOrderT(){
        this.postOrderT(this.root);
    }
    private void postOrderT(BinaryNode<T> root){
    }
}

2.节点的插入
为了将X插入到树中,从root节点开始向下寻找,如果找到X,则更新节点或者不做任何处理;否则,将X插入到遍历路径上的最后一个节点上,代码如下:

private BinaryNode<T> insert(BinaryNode<T> node, T element){
        /*2*/
        if(node == null)
            return new BinaryNode<T>(element,null,null);

        int compareResult = element.compareTo(node.element);
        if(compareResult < 0)
            node.left = insert(node.left, element);
        else if(compareResult >0)
            node.right = insert(node.right, element);
        else
            ;

        return node;
    }

3.contains方法
contains方法的实现类似于insert方法,同样是从root节点向下寻找,如果找到了目标节点,则返回TRUE;否则,继续遍历寻找下一节点。下面的实现方式采用了尾递归的方式,也可用while循环替代:

private boolean contains(BinaryNode<T> node, T element){
        /*3*/
        if(node == null)
            return false;

        int compareResult = element.compareTo(node.element);

        if(compareResult < 0)
            return contains(node.left,element);
        else if(compareResult > 0)
            return contains(node.right, element);
        else
            return true;
    }

3.节点的删除remove方法
节点的删除分三种情况。如果目标节点是一个叶子节点,则可以直接删除;如果目标节点含有一个子节点,则直接删除该节点,并将该节点的父节点引用链指向子节点;如果目标节点a含有左子树b和右子树c,寻找右子树c中最小值节点替换目标节点(删除),然后删除右子树中的最小值节点即可

private BinaryNode<T> findMin(BinaryNode<T> node){
        if(node == null)
            return null;
        if(node.left != null)
            return findMin(node.left);
        return node;
    }
private  BinaryNode<T> remove(BinaryNode<T> node,T element){
        if(node == null)
            return node;//未找到目标节点

        int compareResult = element.compareTo(node.element);
        if(compareResult < 0)
            node.left = remove(node.left, element);
        else if(compareResult > 0)
            node.right = remove(node.right, element);
        else if(node.left != null && node.right != null){
            node.element = findMin(node.right).element;
            node.right = remove(node.right, node.element);
        }else {
            node = (node.left != null)?node.left:node.right;
        }
        return node;
    }

4.遍历
前序遍历(中左右):采用递归的方式或者借助栈来实现遍历的方式。

    private void preOrderI(BinaryNode<T> root){
        if(root == null)
            return;
        System.out.println(root.element);
        preOrderI(root.left);
        preOrderI(root.right);
    }

    private void preOrderT(BinaryNode<T> root){
        if(root == null)
            return;
        Stack<BinaryNode<T>> stack = new Stack<>();
        BinaryNode<T> cur = root;
        while(cur != null && !stack.empty()){
            if(cur != null){
                System.out.println(cur.element);
                stack.push(cur);
                cur = cur.left;
            }else{
                cur = stack.pop();
                cur = cur.right;
            }
        }
    }

中序遍历(左中右):递归方式和循环遍历方式

private void inOrderI(BinaryNode<T> root){
        if(root == null)
            return;
        inOrderI(root.left);
        System.out.println(root.element);
        inOrderI(root.right);
    }
    private void inOrderT(BinaryNode<T> root){
        if(root == null)
            return;
        Stack<BinaryNode<T>> stack = new Stack<>();
        BinaryNode<T> cur = root;
        while(cur != null || !stack.empty()){
            if(cur != null){
                stack.push(cur);
                cur = cur.left;
            }else{
                cur = stack.pop();
                System.out.println(cur.element);
                cur = cur.right;
            }
        }
    }

后序遍历(左右中):递归的实现方式比较简单;遍历的方式借助于两个栈,利用栈1实现中右左的遍历,并将遍历的元素添加到栈2中,然后将栈2中的元素顺序弹出并打印即可实现左右中的遍历。
这里写图片描述

操作1操作2操作3操作4操作5
stack1入3入3出1出2入
result1入3入2入

这样,在result栈中弹出的元素正好是231,正好满足后续遍历的规则。

private void postOrderI(BinaryNode<T> root){
        if(root == null)
            return;
        postOrderI(root.left);
        postOrderI(root.right);
        System.out.println(root.element);
    }
    private  void postOrderT(BinaryNode<T> root){
        if(root == null)
            return;
        Stack<BinaryNode<T>> stack = new Stack<>();
        Stack<BinaryNode<T>> result = new Stack<>();
        BinaryNode<T> cur = root;
        while(cur != null || !stack.empty()){
            if(cur != null){
                stack.push(cur);
                result.push(cur);
                cur = cur.right;
            }else{
                cur = stack.pop();
                cur = cur.left;
            }
        }
        while(!result.empty()){
            System.out.println(result.pop().element);
        }
    }

AVL树

AVL树是带有平衡条件的二叉查找树,每个节点的左子树和右子树的高度最多差1(空树的高度为-1),树的深度为 O(logN) 。在高度为 h 的AVL树中,最小节点数S(h)=S(h1)+S(h2)+1
1. AVL树节点的表示

public class AVLNode<T extends Comparable<? super T>>{
    public T element;
    public int height;
    public AVLNode leftNode;
    public AVLNode rightNode;
}

2.AVL树的旋转
树中插入节点后,从插入点到根节点路径上的节点的平衡是有可能被改变,因此需要找到第一个不平衡的节点(即最深的点)旋转使其平衡。假定插入后需要重新平衡的节点叫做 α ,插入有可能发生在以下几种情况:
- α 的左儿子的左子树 单旋
- α 的左儿子的右子树 双旋
- α 的右儿子的左子树 双旋
- α 的右儿子的右子树 单旋
单旋转:以右旋为例,插入节点0后,造成节点4位置的左右子树不平衡,因此需要左旋。首先将节点4的左链指向节点3,然后将节点2的右链指向节点4,最后更新各节点的高度即可。
左旋示意图
但是,如果是下图的情况,单旋是无法解决平衡问题的。插入节点3后导致节点4的左右子树高度不平衡,执行右旋之后节点2的左右子树依然不平衡。
这里写图片描述
第二次的插入是在不平衡点(4)的左节点(2)的右子树进行的,此时应该执行的是双旋操作。
双旋:以右左双旋为例,插入节点7之后造成节点4的左右子树高度不平衡,应先对节点4的右子树8执行右旋,如下图的前3副图,然后再对节点4执行左旋,最后更新节点的高度信息。
这里写图片描述


public class AvlTree<T extends Comparable<? super T>>{

    private static class AvlNode<T>{

        public AvlNode(T element) {
            this(element, null, null);
            // TODO Auto-generated constructor stub
        }
        public AvlNode(T element,AvlNode<T> left,AvlNode<T> right){
            this.element = element;
            this.left = left;
            this.right = right;
            this.height = 0;
        }

        T element;
        AvlNode<T> right;
        AvlNode<T> left;
        int height;
    }

    private AvlNode<T> root;

    private int height(AvlNode<T> node){
        return node == null ? -1 : node.height;
    }

    public void insert(T element){
        this.insert(root, element);
    }
    private AvlNode<T> insert(AvlNode<T> node,T element){

        if(node == null)
            return new AvlNode<T>(element);

        int compareResult = element.compareTo(node.element);

        if(compareResult < 0){
            node.left = insert(node.left, element);
            if(height(node.left) - height(node.right) == 2){
                if(compare(element, node.left.element) < 0){//左儿子的左子树
                    node = roateWithLeftChild(node);
                }else//左儿子的右子树
                    node = doubleWithLeftChild(node);
            }
        }else if(compareResult > 0){
            node.right = insert(node.right, element);
            if(height(node.right) - height(node.left) == 2){
                if(compare(element, node.right.element) > 0){//右儿子的右子树
                    node = roateWithRightChild(node);
                }else{//右儿子的左子树
                    node = doubleWithRightChild(node);
                }
            }
        }else {//重复的节点
            ;
        }

        node.height = Math.max(height(node.left), height(node.right))+1;
        return node;
    }

    private AvlNode<T> roateWithLeftChild(AvlNode<T> k2){
        AvlNode<T> k1 = k2.left;
        k2.left = k1.right;
        k1.right = k2;
        k1.height = Math.max(height(k1.left), height(k2.right));
        k2.height = Math.max(height(k2.left),height(k2.right));

        return k1;
    }
    private AvlNode<T> roateWithRightChild(AvlNode<T> k2){
        AvlNode<T> k1 = k2.right;
        k2.right = k1.left;
        k1.left = k2;
        k1.height = Math.max(height(k1.left), height(k1.right));
        k2.height = Math.max(height(k2.left), height(k2.right));
        return k1;
    }
    private AvlNode<T> doubleWithLeftChild(AvlNode<T> k3){
        k3.left = roateWithRightChild(k3.left);
        return roateWithLeftChild(k3);
    }
    private AvlNode<T> doubleWithRightChild(AvlNode<T> k3){
        k3.right = roateWithLeftChild(k3.right);
        return roateWithRightChild(k3);

    }

    private int compare(T val1,T val2){
        return val1.compareTo(val2);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值