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:
- I first assume ‘there’ is no child, so int h = 0;
- 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
-
When tree grows in that way,
height(child) + 1
returns every path’s depth, thus we need a comparision. Thus, it should be modified ash = Math.max(h, height(child) + 1);
-
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,没有左边。如此反复。最终有