之前的第4部分提到了二叉搜索树的查找,插入,删除操作,那二叉树的效率如何呢?
在一个满树中,大约有一半的节点在最低层,因此,查找、插入、删除节点的操作大约有一半都需要找到最低层的节点。
按照满树的计算方法,树的操作复杂度为O(logN)。但是遍历树相对来说要慢上许多,因此,如果不涉及到遍历操作的,二叉搜索树是一种很多好的数据结构。
二叉树完整代码:包含查找,插入,删除,遍历操作。
package Charp04.BinaryTree; import java.util.Stack; public class Tree { /* * 根节点 */ private Node root; /** * 构造函数 */ public Tree() { root = null; } /** * 查找节点 * * @param key 输入key值 * @return 返回节点值 */ public Node find(int key) { // 当前节点,相当于一个指针,引用会随着查找变化 Node current = root; // 遍历查询节点 // 查找方法:根据二叉树的特点,左子节点值总是比父节点值小,右子节点值总比父节点值大 while (current.iData != key) { // 比较当前节点值, if (key < current.iData) { // 如果值小于key,则将当前节点current引用变化为左子节点,此时不用判断引用是否为null current = current.leftChild; } else { // 如果值大于key,则将当前节点current引用变化为右子节点,此时不用判断引用是否为null current = current.rightChild; } // 如果当前节点为空,则表示无法找到key对应的节点 if (current == null) { return null; } } return current; } /** * 插入节点,跟查找节点代码类似,只是在遇到null时,将节点插入,修改引用 * * @param id key值 * @param dd value值 */ public void insert(int id, double dd) { // 插入的节点 Node newNode = new Node(); newNode.iData = id; newNode.dData = dd; // 如果根节点为空,则插入节点为根节点 if (root == null) { root = newNode; } else { // 当前节点,引用会随在查找变化 // 由于查找要插入的节点是从根基点开始,所以root引用赋值给current Node current = root; // 父节点 // 有父节点的引用,才能够给子节点leftChild或者rightChild赋值 Node parent; while (true) { // 保存父节点的引用,因为后面在查找时,current的引用一定会为null, // 此时说明已经找到插入节点的位置了 parent = current; // 插入节点在左子树 if (id < current.iData) { current = current.leftChild; if (current == null) { parent.leftChild = newNode; return; } } // 插入节点在右子树 else { current = current.rightChild; if (current == null) { parent.rightChild = newNode; return; } } } } } /** * 删除节点 * * @param key 删除节点key值 * @return 返回值 */ public boolean delete(int key) { Node current = root; // 当前节点 Node parent = root; // 父节点,用于标记删除节点的父节点 boolean isLeftChild = true; // 是否是左子节点 // 查找要删除的节点 while (current.iData != key) { // 保存父节点的引用 parent = current; // 删除节点在左子树 if (key < current.iData) { isLeftChild = true; current = current.leftChild; } // 删除节点在左子树 else { isLeftChild = false; current = current.rightChild; } // 找不到删除节点,返回 if (current == null) { return false; } } // 删除没有子节点的节点 // 即删除节点为current,此时其左子节点、右子节点的应用都为null if (current.leftChild == null && current.rightChild == null) { if (current == root) { root = null; } else if (isLeftChild) { // 修改current父节点左子节点的引用 parent.leftChild = null; } else { // 修改current父节点右子节点的引用 parent.rightChild = null; } } // 删除节点current的右子节点为空,即只有左子节点 else if (current.rightChild == null) { // 判断是否为根 if (current == root) { root = current.leftChild; } // 如果删除节点为parent的左子节点,则引用赋值为左子树 else if (isLeftChild) { parent.leftChild = current.leftChild; } // 如果删除节点为parent的右子节点,则引用赋值为右子树 else { parent.rightChild = current.leftChild; } } // 删除节点current的左子节点为空,即只有右子节点 else if (current.leftChild == null) { // 判断是否为根 if (current == root) { root = current.rightChild; } // 如果删除节点为parent的左子节点,则引用赋值为左子树 else if (isLeftChild) { parent.leftChild = current.rightChild; } // 如果删除节点为parent的右子节点,则引用赋值为右子树 else { parent.rightChild = current.rightChild; } } else { // 找到后继 Node successor = getSuccessor(current); // 判断删除节点是否为根的情形 if (current == root) { root = successor; } else if (isLeftChild) { // 连接删除节点的父节点与后继节点 parent.leftChild = successor; } else { // 连接删除节点的父节点与后继节点 parent.rightChild = successor; } // 连接后继左子节点 successor.leftChild = current.leftChild; } return true; } /** * 获取后继节点 * @param delNode 删除节点 * @return 后继节点 */ private Node getSuccessor(Node delNode) { Node successorParent = delNode; // 存放后继节点的父节点,因为需要断开后继节点,需要保存父节点的引用 Node successor = delNode; // 存放后继节点 Node current = delNode.rightChild; // 当前节点 // 循环查找后继节点,最后currnt一定为null while (current != null) { successorParent = successor; successor = current; current = current.leftChild; } // 后继节点不是右子节点,即沿着左子节点路径寻找的情形 if (successor != delNode.rightChild) { // 将后继节点的右子节点(有可能是右子树)的引用赋值给后继的父节点,即连接父节点与孙节点 // 这样才能将后继节点断开,同时保持后继节点的子节点关系 successorParent.leftChild = successor.rightChild; // 将删除节点的右子节点引用赋值给后继节点 // 由于后继替换到删除节点的位置,因此需要改变删除节点右子节点的连接关系 successor.rightChild = delNode.rightChild; } return successor; } /** * 二叉树遍历 * * @param traverseType 遍历类型 */ public void traverse(int traverseType) { switch (traverseType) { case 1: // 前序遍历 System.out.print(" Preorder traversal:"); preOrder(root); break; case 2: // 中序遍历 System.out.print(" Inorder traversal:"); inOrder(root); break; case 3: // 后序遍历 System.out.print(" Postorder traversal:"); postOrder(root); break; default: break; } } /** * 前序遍历 * * @param localRoot 节点值 */ private void preOrder(Node localRoot) { if (localRoot != null) { System.out.print(localRoot.iData + " "); preOrder(localRoot.leftChild); preOrder(localRoot.rightChild); } } /** * 中序遍历 * * @param localRoot 节点值 */ private void inOrder(Node localRoot) { if (localRoot != null) { inOrder(localRoot.leftChild); System.out.print(localRoot.iData + " "); inOrder(localRoot.rightChild); } } /** * 后续遍历 * * @param localRoot 节点值 */ private void postOrder(Node localRoot) { if (localRoot != null) { postOrder(localRoot.leftChild); postOrder(localRoot.rightChild); System.out.print(localRoot.iData + " "); } } /** * 打印二叉树 */ public void displayTree() { Stack<Node> globalStack = new Stack<Node>(); globalStack.push(root); int nBlanks = 32; boolean isRowEmpty = false; System.out.println("......................................"); while (isRowEmpty == false) { Stack<Node> localStack = new Stack<Node>(); isRowEmpty = true; for (int i = 0; i < nBlanks; i++) { System.out.print(' '); } while (globalStack.isEmpty() == false) { Node temp = (Node) globalStack.pop(); if (temp != null) { System.out.print(temp.iData); localStack.push(temp.leftChild); localStack.push(temp.rightChild); if (temp.leftChild != null || temp.rightChild != null) { isRowEmpty = false; } } else { System.out.print("--"); localStack.push(null); localStack.push(null); } for (int i = 0; i < nBlanks * 2 - 2; i++) { System.out.print(' '); } } System.out.println(); nBlanks /= 2; while (localStack.isEmpty() == false) { globalStack.push(localStack.pop()); } } System.out.println("......................................"); } }