public interface Tree<E> extends Iterable<E> { boolean search (E e); boolean insert(E e); boolean delete (E e); void inorder(); //Inorder traversal void preorder();//Preorder traversal void postorder();//Postorder traversal int getSize(); boolean isEmpty(); void clear(); }
2. Implement the abstract convenience class AbstractTree
public abstract class AbstractTree<E> implements Tree<E> { @Override public void inorder() { } @Override public void preorder() { } @Override public void postorder() { } @Override public boolean isEmpty() { return getSize()==0; } }
3. Realize BST
public class BinarySearchTree<E extends Comparable<E>> extends AbstractTree<E>{ protected TreeNode<E> root; protected int size = 0; public BinarySearchTree(){} public BinarySearchTree(E[] objects){ for(E object:objects){ insert(object); } } public TreeNode<E> getRoot() { return root; } public static class TreeNode<E extends Comparable<E>>{ protected E element; protected TreeNode<E> leftNode; protected TreeNode<E> rightNode; public TreeNode(E element) { this.element = element; } } protected TreeNode<E> createNewNode(E e){ return new TreeNode<>(e); } @Override public boolean search(E e) { TreeNode<E> current = root; while (current != null){ if(e.compareTo(current.element) > 0){ current = current.rightNode; } else if(e.compareTo(current.element) < 0){ current = current.leftNode; } else return true; } return false; } @Override public boolean insert(E e) { if (root == null) root = createNewNode(e); else { TreeNode<E> parent = root; TreeNode<E> current = root; while (current != null){ if (e.compareTo(current.element) > 0){ parent = current; current = current.rightNode; } else if(e.compareTo(current.element) < 0){ parent = current; current = current.leftNode; } else return false; // cannot insert the same element } if (e.compareTo(parent.element) > 0){ parent.rightNode = createNewNode(e); } else{ parent.leftNode = createNewNode(e); } } size++; return true; } @Override public boolean delete (E e) { TreeNode<E> parent = null; TreeNode<E> current = root; while(current!=null){ if (e.compareTo(current.element)>0){ parent = current; current = current.rightNode; } else if (e.compareTo(current.element)<0){ parent = current; current = current.leftNode; } else break; } if (current == null) return false; //The element is not in the binary tree if(current.leftNode == null){ //If the node to be deleted has no left subtree if (parent == root) root = current.rightNode; else { if (e.compareTo(parent.element) > 0) parent.rightNode = current.rightNode; else parent.leftNode = current.rightNode; } } else{ //If the node to be deleted has a left subtree, TreeNode<E> parentOfRightMost = current; TreeNode<E> rightMost = current.leftNode; while (rightMost.rightNode != null){ //Find out the rightmost child node in the left subtree, that is, the largest child node rightMost in the left subtree parentOfRightMost = rightMost; rightMost = rightMost.rightNode; } current.element = rightMost.element; //Replace the largest node in the left subtree with the node to be deleted if (parentOfRightMost == current) parentOfRightMost.leftNode = rightMost.leftNode; else parentOfRightMost.rightNode = rightMost.leftNode; } size--; return true; } @Override public void inorder() { inorder(root); } protected void inorder(TreeNode<E> root){ if (root == null) return; inorder(root.leftNode); System.out.print(root.element+" "); inorder(root.rightNode); } @Override public void preorder() { preorder(root); } protected void preorder(TreeNode<E> root){ if (root == null) return; System.out.print(root.element+" "); preorder(root.leftNode); preorder(root.rightNode); } @Override public void postorder() { postorder (root); } protected void postorder(TreeNode<E> root){ if (root == null) return; postorder(root.leftNode); postorder(root.rightNode); System.out.print(root.element+" "); } @Override public int getSize() { return size; } @Override public void clear() { root = null; size = 0; } private class InorderIterator implements Iterator<E>{ private ArrayList<E> list = new ArrayList<>(); private int current = 0; public InorderIterator(){ inorder(); } private void inorder(){ inorder(root); } private void inorder(TreeNode<E> root){ if (root == null) return; inorder(root.leftNode); list.add(root.element); inorder(root.rightNode); } @Override public void remove() { delete(list.get(current)); list.clear(); inorder(); } @Override public boolean hasNext() { return current<list.size(); } @Override public E next() { return list.get(current++); } } @Override public Iterator iterator() { return new InorderIterator(); } public ArrayList<TreeNode<E>> getPath(E e){ ArrayList<TreeNode<E>> list = new ArrayList<>(); TreeNode<E> current = root; while (current != null){ list.add(current); if(e.compareTo(current.element)>0) current = current.rightNode; else if (e.compareTo(current.element)<0) current = current.leftNode; else break; } return list; } }
4. Summary
1. The time complexity of pre-order, in-order, and post-order traversal is O(n), because each node has to be traversed once. The time complexity of lookups, deletions and insertions is the height of the tree. In the worst case, the height of the tree is O(n) . But if it is a balanced binary tree, the height is O(logn) .
2. If it is a balanced binary tree, most operations of the tree need to search the nodes of the tree from top to bottom. For a full tree, about half of the nodes are at the bottom (the number of nodes at the bottom = the number of nodes in other layers). Sum + 1), so about half of the node operations need to find the bottommost node, about a quarter of the nodes are in the penultimate layer, so about a quarter of the node operations need to find the penultimate layer node, according to And so on
During the search process, it is necessary to visit the nodes of each layer, so as long as you know the number of layers to be searched, you can know the time required for the operation. If the total number of nodes is N and the number of layers is L, L=log 2 (N+1)
If it is a search operation or a delete operation, the operated node may be any node of the tree, so the time complexity of the search operation or delete operation is: 1/2 1 *log 2 (N+1) + 1/2 2 * log 2 (N/2+1) + ... + 1/2 N *1
If it is an insertion operation, since a new node is inserted at the lowest level of the tree every time, the time complexity of the insertion operation is: log 2 (N+1)
In general, it can be considered that the time complexity of binary search tree operations is O(logN)
If the tree is not a full tree, it is more complicated to judge, but if the number of layers is the same, the operation on the full tree is definitely more time-consuming than the operation on the unsatisfactory tree.
For an ordered linked list with 10,000 data items , the lookup operation requires an average of 5,000 comparisons, and for a binary search tree with 10,000 nodes, the lookup operation takes about 13 times.
For an ordered array with 10,000 data items , the insertion operation requires an average of 5,000 moves (for the number of comparisons, the number of comparisons varies with different algorithms). For a binary search tree with 10,000 nodes, the insertion operation only requires It takes about 13 comparisons to find the insertion position of the node to be inserted, and since the position is always at the bottom of the binary search tree, there is no need to move other nodes
It can be seen that the binary search tree combines the advantages of high insertion and deletion efficiency of ordered linked list and high efficiency of ordered array query.