[Java/数据结构] 4 二叉树

2 篇文章 0 订阅
1 篇文章 0 订阅

1 Terminology

请添加图片描述

  • root, the node that has a child or more without parent; (/user/rt/courses/
  • parent, the node has a child or more; (cs016, /user/rt/courses/
  • child, the node has parent; (cs016, grades
  • external, the node has no child; (grades
  • internal, the node has a child or more;
  • orderness, if each node is arranged in a certain order, then we call the tress as ordered tree.(book catalog
    is a tree with orderness
  • depth, the level a node is in. Mind you that the root is in depth 0, and grades in picture above is in the depth 2;
  • height, the maximum depth, height = 4;
  • binary tree, a specital tree.
    • it’s an ordered tree, usually from left to right;
    • it has upmost two children, left child and right child;
    • a binary tree is proper if every node doesn’t has exactly one child; In another word, every internal node of binary tree has two children;

2 A Tree Interface

@FunctionalInterface
public interface Position<E> {
    E getElem() throws IllegalStateException;
}

Position here is a wrapper, it records the address of the inside data; Another functionality is to force the node in tree to implement it, so I can use some attributes of node while remaining encapsulation.


//todo: Iterable is here;
public interface MyTree<E> extends Iterable<E> {

    Position<E> root();

    //return the parent position of the position p;
    Position<E> parent(Position<E> p) throws IllegalArgumentException;



    int numChildren(Position<E> p) throws IllegalArgumentException;

    //return true if the node(implementing Position) has at least one child;
    boolean isInternal(Position<E> p) throws IllegalArgumentException;

    //also boolean isLeaf(Position<E> p);
    // return true if it has no child at all;
    boolean isExternal(Position<E> p) throws IllegalArgumentException;

    boolean isRoot(Position<E> p) throws IllegalArgumentException;


    int size();

    boolean isEmpty();

    //---------------------iterator----------------------------
 
    Iterator<E> iterator();

    //returns an iterable collection containing the children of the position p(if any)
    //including the children's children.
    Iterable<Position<E>> children(Position<E> p) throws IllegalArgumentException;

    //returns an iterable collection of all positions of the tree;
    Iterable<Position<E>> positions();


}

then we use an abstract class to implement some of the functions

public abstract class AbstractTree<E> implements Tree<E> {
    public boolean isInternal(Position<E> p) {
        return numChildren(p) > 0;
    }

    public boolean isExternal(Position<E> p) {
        return numChildren(p) == 0;
    }

    public boolean isRoot(Position<E> p) {
        return p == root();
    }

    public boolean isEmpty() {
        return size() == 0;
    }

    public int depth(Position<E> p) {
        if (isRoot(p)) {
            return 0;
        } else {
            /*since it's not root, it definitely has parent*/
//            if(parent(p)!=null){
            return 1 + depth(parent(p));
//            }
        }
    }


    public int height(Position<E> p) {
        /*h=0, assume the level doesn't have any child*/
        int h = 0;
        for (Position<E> child : children(p)) {
            /*if the level has a child, then +1
             * , height(child) + 1 is the height of a path */
            h = Math.max(h, height(child) + 1);
        }
        return h;
    }
}

let’s talk about height

public int height(Position<E> p) {
    /*h=0, assume the level doesn't have any child*/
    int h = 0;
    for (Position<E> child : children(p)) {
        /*if the level has a child, then +1
         * , height(child) + 1 is the height of a path */
        h = Math.max(h, height(child) + 1);
    }
    return h;
}

I construct it following that way:

  1. I first assume ‘there’ is no child, so int h = 0;
  2. 在这里插入图片描述Suppose I have a tree looks like this. If we only want to calculate the depth of the path 2-5-11 from the root, we can simply write Java code like
public int height(Position<E> p) {
    
    int h = 0;
    for (Position<E> child : children(p)) {
        h =  height(child) + 1;
    }
    return h;
}

That is, the height(child) + 1 simply returns the the depth of a single path

  1. 在这里插入图片描述When tree grows in that way,height(child) + 1returns every path’s depth, thus we need a comparision. Thus, it should be modified as h = Math.max(h, height(child) + 1);

  2. the algorithm is O(n);

3 Binary Tree

A binary tree can be divided as left subtree and right subtree. So a recursive definition of binary tree is:

  • An empty tree;
  • If not empty, the tree has left or right or both subtree(s). And the subtrees can be defined recursively until it reaches the first rule;

3.1 Binary Tree interface and Abstract Binary Tree

public interface BinaryTree<E> extends Tree<E> {
    /*p here is the parent*/
    Position<E> left(Position<E> p) throws IllegalArgumentException;

    Position<E> right(Position<E> p) throws IllegalArgumentException;
    /*p here is one of the child node*/
    Position<E> sibling(Position<E> p) throws IllegalArgumentException;

}

public abstract class AbstractBinaryTree<E> extends AbstractTree<E> implements BinaryTree<E> {
    /*the p here is one of the child*/
    @Override
    public Position<E> sibling(Position<E> p) throws IllegalArgumentException {
        /*parent(p) is descent from Tree, which is implemented by AbstractTree*/
        Position<E> parent = parent(p);
        if (parent == null) {
            /*the parent is the root */
            return null;
        }
        if (p == left(parent)) {
            return right(parent);
        } else if (p == right(parent)) {
            return left(parent);
        }

        return null;
    }

    /*p is parent*/
    @Override
    public Iterable<Position<E>> children(Position<E> p) throws IllegalArgumentException {
        /*the "iterable" of tree relies on other data structure*/
        Collection<Position<E>> snapshot = new ArrayList<>(2);
        if (left(p) != null) {
            snapshot.add(left(p));
        }
        if (right(p) != null) {
            snapshot.add(right(p));
        }
        return snapshot;
    }


    /*the method descends from tree, p is parent*/
    @Override
    public int numChildren(Position<E> p) throws IllegalArgumentException {
        int count = 0;
        for (Position<E> iter : children(p)) {
            if (iter != null) {
                count++;
            }
        }
        return count;
    }
}

3.2 Linked list implementation

The following codes should be pasted into your IDE, which helps you grasp those points;
The following codes should be pasted into your IDE, which helps you grasp those points;
The following codes should be pasted into your IDE, which helps you grasp those points;

public class LinkedBinaryTree<E> extends AbstractBinaryTree<E> {
    protected static class Node<E> implements Position<E> {
        private E elem;
        private Node<E> parent;
        private Node<E> left;
        private Node<E> right;

        public Node() {
        }

        public Node(E elem, Node<E> parent, Node<E> left, Node<E> right) {
            this.elem = elem;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

        @Override
        public E getElem() {
            return elem;
        }

        public void setElem(E elem) {
            this.elem = elem;
        }

        public Node<E> getParent() {
            return parent;
        }

        public void setParent(Node<E> parent) {
            this.parent = parent;
        }

        public Node<E> getLeft() {
            return left;
        }

        public void setLeft(Node<E> left) {
            this.left = left;
        }

        public Node<E> getRight() {
            return right;
        }

        public void setRight(Node<E> right) {
            this.right = right;
        }
    }

    protected Node<E> createNode(E e, Node<E> parent, Node<E> left, Node<E> right) {
        return new Node<E>(e, parent, left, right);
    }

    protected Node<E> root = null;
    private int size = 0;

    /*do nothing*/
    public LinkedBinaryTree() {

    }

    /*--- start: helper methods*/

    protected Node<E> validate(Position<E> p) throws IllegalStateException {
        if (!(p instanceof Node)) {
            throw new IllegalArgumentException("Not valid position type");
        }
        Node<E> node = (Node<E>) p;
        /*it just a convention for those useless nodes */
        if (node.getParent() == node) {
            throw new IllegalArgumentException("p is no longer in the tree");
        }
        return node;
    }

    /*--- end: helper methods*/


    /*p is parent*/
    @Override
    public Position<E> left(Position<E> p) throws IllegalArgumentException {

        Node<E> parentNode = validate(p);
        return parentNode.getLeft();
    }

    @Override
    public Position<E> right(Position<E> p) throws IllegalArgumentException {
        Node<E> parentNode = validate(p);

        return parentNode.getRight();
    }


    /*-- start: adding, and setting(replacing) methods*/
    public Position<E> addRoot(E e) throws IllegalStateException {
        if (!isEmpty()) throw new IllegalStateException("Tree is not empty");
        root = createNode(e, null, null, null);
        size = 1;
        return root;
    }

    public Position<E> addLeft(Position<E> p, E e) {
        Node<E> parentNode = validate(p);
        if (parentNode.getLeft() != null) {
            throw new IllegalStateException("Left child already exists");
        }
        Node<E> left = new Node<E>(e, parentNode, null, null);
        parentNode.setLeft(left);
        size++;

        return left;
    }

    public Position<E> addRight(Position<E> p, E e) {
        Node<E> parentNode = validate(p);
        if (parentNode.getRight() != null) {
            throw new IllegalStateException("Right child already exists");
        }
        Node<E> right = new Node<E>(e, parentNode, null, null);
        parentNode.setRight(right);
        size++;

        return right;
    }

    public E set(Position<E> p, E e) {
        Node<E> node = validate(p);
        E temp = node.getElem();
        node.setElem(e);
        return temp;
    }
    /*-- end: adding and setting(replacing) methods*/

    /**
     * return nothing but warning
     *
     * @param p:        the position you wanna attach sub tree;
     * @param leftSub:  what sub tree you wanna to attach on the left;
     * @param rightSub: .... right;
     */
    public void attach(Position<E> p, LinkedBinaryTree<E> leftSub, LinkedBinaryTree<E> rightSub) {
        Node<E> node = validate(p);
        if (isInternal(p)) throw new IllegalArgumentException("p  must be a leaf");
        size += leftSub.size() + rightSub.size();
        if (!leftSub.isEmpty()) {
            leftSub.root.setParent(node);

            node.setLeft(leftSub.root);

            /*for CG */
            leftSub.root = null;
            leftSub.size = 0;
        }

        if (!rightSub.isEmpty()) {
            rightSub.root.setParent(node);

            node.setRight(rightSub.root);

            /*for CG */
            rightSub.root = null;
            rightSub.size = 0;
        }
    }

    public E remove(Position<E> p) {
        Node<E> node = validate(p);
        if (numChildren(p) == 2) {
            throw new IllegalArgumentException("p has two children");
        }
        /*since the right can also be null, thus it also contains the condition of node has no child at all*/
        Node<E> child = (node.getLeft() == null ? node.getLeft() : node.getRight());

        /*handle the child first, then the node itself*/
        if (child != null) {
            child.setParent(node.getParent());
        }
        if (node == root) {
            root = child;
        } else {
            Node<E> nodeParent = node.getParent();
            if (node == nodeParent.getLeft()) {
                nodeParent.setLeft(child);
            } else {
                nodeParent.setRight(child);
            }
        }
        size--;
        /*CG*/
        E elem = node.getElem();
        node.setLeft(null);
        node.setRight(null);
        node.setElem(null);
        node.setParent(node);
        return elem; 
    }

    @Override
    public Position<E> root() {
        return root;
    }

    /*p is child*/
    @Override
    public Position<E> parent(Position<E> p) throws IllegalArgumentException {
        Node<E> node = validate(p);

        return node.getParent();
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public Iterator<E> iterator() {
        return null;
    }

    @Override
    public Iterable<Position<E>> positions() {
        return null;
    }

}

Methods worth mentioning are public void attach(Position<E> p, LinkedBinaryTree<E> leftSub, LinkedBinaryTree<E> rightSub)andpublic E remove(Position<E> p)

3.3 Traversal algorithm and iterator

3.3.1 Recursion

3.3.1.1 preorder traversal

Visiting the root first, then the left child to the right child;
在这里插入图片描述
the figure above should print out like : 1,2,6,5,3,4,7,6,99,10

private void preorderSubtree(Position<E> p, List<Position<E>> snapshot) {
    snapshot.add(p);
    for (Position<E> c : children(p)) {
        preorderSubtree(c, snapshot);
    }
}

public Iterable<Position<E>> preorder() {
    List<Position<E>> snapshot = new ArrayList<>();
    //if the tree is not empty, then perform the recursive function
    if (!this.isEmpty()) {
        preorderSubtree(root, snapshot);
    }
    return snapshot;
}

Note that the real method performs that algorithm is the preoderSubtree( ). preoder( ) only stores the result produced by preorderSubtree();

3.3.1.2 postorder traversal

left child first then the right, and the root comes the the last;
在这里插入图片描述

private void postorderSubtree(Position<E> p	, List<Position<E>> snapshot) {
    for (var iter : children(p)) {
        postorderSubtree(iter, snapshot);
    }
    snapshot.add(p);
}

public Iterable<Position<E>> postorder() {
    List<Position<E>> snapshot = new ArrayList<>();
    if (!this.isEmpty()) {
        postorderSubtree(root, snapshot);
    }
    return snapshot;
}
3.3.1.3 inorder traversal

left first with parent in the middle and right child is the last;
在这里插入图片描述

private void inorderSubtree(Position<E> p, List<Position<E>> snapshot) {
    if (left(p) != null) {
        inorderSubtree(left(p), snapshot);
    }
    snapshot.add(p);
    if (right(p) != null) {
        inorderSubtree(right(p), snapshot);
    }
}

public Iterable<Position<E>> inorder() {
    List<Position<E>> snapshot = new ArrayList<>();
    if (!this.isEmpty()) {
        inorderSubtree(root, snapshot);
    }
    return snapshot;
}
3.3.1.4 breadth first traversal/ level traversal

在这里插入图片描述
1,2,3,6,7,5,11, we can use a queue to represent this result. Think of this, we enque the parent, then traverse it’s children and enque them. Next we deque, and treat the previous left child(if exist) as a new parent;

public Iterable<Position<E>> breathFirst() {
    List<Position<E>> snapshot = new ArrayList<>();
    Queue<Position<E>> queue = new LinkedList<>();
    /*enqueue*/
    queue.offer(root());
    while (queue.peek() != null) {
        snapshot.add(queue.peek());
        for (Position<E> iter : children(queue.peek())) {
            if (iter != null) {

                queue.offer(iter);
            }
        }
        queue.poll();
    }

    return snapshot;
}

3.3.2 Iteration

3.3.2.1 preorder traversal
public Iterable<Position<E>> preorder_iter() {
    List<Position<E>> snapshot = new ArrayList<>();
    Stack<Position<E>> stack = new Stack<>();
    stack.push(root());

    while(!stack.isEmpty()){
        var tmp = stack.pop();
        snapshot.add(tmp);
        var node = validate(tmp);
        if(node.getRight()!=null){
            stack.push(node.getRight());
        }
        if(node.getLeft()!=null){
            stack.push(node.getLeft());
        }
    }
    return snapshot;
}

The key of the codes is to push the Right before pushing the Left;

3.3.2.2 postorder traversal(FIXME)

there are some subtle bugs

public Iterable<Position<E>> postorder_iter() {
    List<Position<E>> snapshot = new ArrayList<>();
    Stack<Position<E>> stack = new Stack<>();

    var pointer = validate(root());
    var follow = validate(root());

    while (!stack.isEmpty()) {
        while (pointer != null) {
            stack.push(pointer);
            pointer = pointer.getLeft();
        }
        pointer = validate(stack.peek());

        if (pointer.getRight() != null && pointer.getRight() != follow) {
            pointer = pointer.getRight();
        } else {
            snapshot.add(pointer);
            follow = pointer;
            stack.pop();
            pointer = null;
        }
    }

    return snapshot;
}

The key is we use follow to deal with the repeated visiting of the right children; and rely on the pointer=null to handle with the repeated visiting of left children;

3.3.2.3 inorder traversal
public Iterable<Position<E>> inorder_iter() {
    List<Position<E>> snapshot = new ArrayList<>();
    Stack<Position<E>> stack = new Stack<>();

    stack.push(root());
    var tmp = stack.peek();
    var node = validate(tmp);
    while (!stack.isEmpty()) {

        /*keep searching for the left node*/
        while (node != null) {
            stack.push(node);
            node = node.getLeft();

        }
        /*finish the left, then turn right*/
        node = validate(stack.pop());
        snapshot.add(node);
        node = node.getRight();

    }
    return snapshot;
}

3.4 Array-based Binary Tree and General Tree

3.4.1 Array-based Binary Tree

It’s ok to use array to store the binary tree, if the i is the index of a parent, then 2i+1 is the left child, and 2i+2 is the right; However, this will bring a significant problem: you must spare a space for all children even though you don’t even have one. And the exponential growth will teach you what’s the waste.

3.4.2 General Tree

If a tree has arbitrary children, then the parent should link to a list. And each elem in the list should also link to another list.

4 一些练习

写出下面二叉树的前中后,层次遍历结果
在这里插入图片描述

前:父节点,左孩子,右孩子;-+aXb-cd/ef
中:左孩子,父节点,右孩子;a+bXc-d-e/f
后:左孩子,右孩子,父节点;abcd-x+ef/-

在这里插入图片描述

前:1,2,6,5,3,4,7,6,99,10
中:5,6,3,2,4,7,1,99,6,10
后:5,3,6,7,4,2,99,10,6,1

一个二叉树的先序,中序分别如下所示,请构造出相应的二叉树
先序[ABCDEFGHIJ],个数10
中序[CDBFEAIHGJ]

由于先序是父左右,中序是左父右,所以这实际上是一个递归的过程,首先先序告诉我们的是每颗子树的根;而中序则告诉我们那些元素在每颗子树的左边还是右边。

比如,先序第一个A,是根,中序告诉我们CDBFE在A的左边,IHGJ在A的右边。基于这个信息,看先序第二个,B,B此时看做一个根,然后CD在B的左边,FE在B的右边。。。如此往复,最后我们有

在这里插入图片描述

一个二叉树的中序,后序分别如下所示,请构造出相应的二叉树
中序[BDCEAFHG],个数8
后序[DECBHGFA]

后序的正好跟先序的反过来,因为后序后面的数就是根,所以A是根,然后以同样的逻辑,中序中A的右边是FHG,左边是BDCE;然后是F,F的右边是HG,没有左边。如此反复。最终有
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值