二叉樹查找樹:
二叉查找樹也叫二叉搜索樹,二叉排序樹。它也是一種特殊的二叉樹,
它具有以下特點
1.如果它的左子樹不為空,則左子樹上結點的值都小於根結點。
2.如果它的右子樹不為空,則右子樹上結點的值都大於根結點。
3.子樹的子樹同樣也要遵循以上兩點
為什么又叫做二叉排序樹,因為具有這種特殊特點的二叉樹,它的中序遍歷一定是有序的,如下:
中序遍歷的結果是0,1,3,4,5,7,8,9,10
推薦一個網站:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html 這個上面有各種數據結構的操作,之前在mysql索引數據結構這篇文章中也推薦過。
插入的時候每次都是和根結點或者子樹的根結點比較,大於走右邊,小於走左邊,直到找到它應該插入的位置。新元素插入的位置肯定值在葉子結點。其實它的插入就是一次查找。每次判斷之后就會折半,所以說它的查詢效率是O(logn)。
查詢和插入的時候類似,修改就沒什么好說的了,直接把數據覆蓋上去即可
重點說下二叉查找樹的刪除。
它的刪除分三種情況:
1.刪除的是葉子結點數據,可以看出來,直接刪除就可以了,比如上面0,4,7,10,將其雙親的對應子樹指向null即可
2.刪除的是度為1的結點,比如上面的1,9,只需要將它的子樹的根結點覆蓋當前刪除的這個節點即可,以1的刪除為例,也就是把其雙親3的右子樹指針改指向像它的孩子0即可。
3.刪除的結點度為2,也就有兩棵子樹的結點。這個的處理就稍微復雜一點了,因為二叉查找樹的特性,根大於左子樹小於右子樹的。所以刪除的需要去尋找前驅/后繼結點來補充它的位置。如果刪除的根左邊的結點(比根小的結點),那么就是找前驅結點,前驅結點是其左子樹中最大的結點,前驅結點的右子樹一定為空,因為沒有比它大的了;如果刪除的根右邊的結點(比根大的結點),那么就是找后繼結點,后繼結點是其右子樹中最小的結點,后繼結點的左子樹一定為空,因為沒有比它小的了。其實前驅后繼就是中序遍歷的時候在根前后的兩個結點。如此一來,可以看出找到的前驅/后繼結點的條件肯定是滿足1或者2的,找到這個節點之后,將其覆蓋當前要刪除的這個節點。各項指針都移動好了,只需要將這個前驅/后繼結點刪除就可以了,這個節點的刪除就和前面兩種情況一樣的了。
具體每一個注意點可以看下代碼注釋說明,應該算比較詳細,里面涉及的那些結點指針的指向變更,還是比較繞的。
package com.nijunyang.algorithm.tree; import com.nijunyang.algorithm.util.RefObject; /** * Description: * Created by nijunyang on 2020/4/19 20:03 */ public class BinarySearchTree<T extends Comparable<T>> extends TreeNode<T> { private T data; private BinarySearchTree<T> leftChild; private BinarySearchTree<T> rightChild; public BinarySearchTree(T data) { this.data = data; } public static void main(String[] args) { BinarySearchTree<Integer> binarySearchTree = new BinarySearchTree(5); BinarySearchTree.insert(binarySearchTree, 3); BinarySearchTree.insert(binarySearchTree, 1); BinarySearchTree.insert(binarySearchTree, 4); BinarySearchTree.insert(binarySearchTree, 8); BinarySearchTree.insert(binarySearchTree, 7); BinarySearchTree.insert(binarySearchTree, 9); BinarySearchTree.insert(binarySearchTree, 10); BinarySearchTree.insert(binarySearchTree, 0); TreeUtil.inOrderTraversal(binarySearchTree); System.out.println(); TreeUtil.levelOrder(binarySearchTree); System.out.println(); BinarySearchTree<Integer> integerBinarySearchTree = BinarySearchTree.find(binarySearchTree, 9, new RefObject<>()); delete(binarySearchTree, 8); delete(binarySearchTree, 7); TreeUtil.inOrderTraversal(binarySearchTree); } /** * 插入數據 * @param root * @param data * @param <T> */ public static <T extends Comparable<T>> void insert(BinarySearchTree<T> root, T data) { if (root.data.compareTo(data) < 0) { if (root.rightChild == null) { root.rightChild = new BinarySearchTree(data); } else { insert(root.rightChild, data); } } else { if (root.leftChild == null) { root.leftChild = new BinarySearchTree(data); } else { insert(root.leftChild, data); } } } /** * 查詢數據 * @param root * @param data * @param parent 用於帶出父節點 * @param <T> * @return */ public static <T extends Comparable<T>> BinarySearchTree<T> find( BinarySearchTree<T> root, T data, RefObject<BinarySearchTree<T>> parent) { if (root.data.compareTo(data) == 0) { return root; } parent.setValue(root); if (root.data.compareTo(data) < 0) { if (root.rightChild != null) { return find(root.rightChild, data, parent); } } else { if (root.leftChild != null) { return find(root.leftChild, data, parent); } } return null; } /** * 查詢最大數據 * @param root * @param parentRef 返回結果的父結點包裝 * @param <T> * @return */ public static <T extends Comparable<T>> BinarySearchTree<T> findMax( BinarySearchTree<T> root, RefObject<BinarySearchTree<T>> parentRef) { if (root.rightChild != null) { parentRef.setValue(root); return findMax(root.rightChild, parentRef); } return root; } /** * 查詢最小數據 * @param root * @param parentRef 返回結果的父結點包裝 * @param <T> * @return */ public static <T extends Comparable<T>> BinarySearchTree<T> findMin( BinarySearchTree<T> root, RefObject<BinarySearchTree<T>> parentRef) { if (root.leftChild != null) { parentRef.setValue(root); return findMin(root.leftChild, parentRef); } return root; } /** * 刪除數據 * @param root * @param data * @param <T> * @return */ public static <T extends Comparable<T>> void delete(BinarySearchTree<T> root, T data) { RefObject<BinarySearchTree<T>> parentRef = new RefObject<>(); BinarySearchTree<T> delBinarySearchTree = find(root, data, parentRef); if (delBinarySearchTree == null) { return; } /** * 二叉搜索樹結點的刪除分三種情況: * 1.葉子結點,可以直接刪除 * 2.度為1的結點,可以直接刪除(只有一個子樹的結點) * 3.兩棵子樹的結點刪除,找前驅結點/后繼結點。就是刪除了該結點,前驅/后繼結點可以直接補位 * 如果刪除的根左邊的結點,那么就是找前驅結點,前驅結點是其左子樹中最大的結點,前驅結點的右子樹一定為空,因為沒有比它大的了 * 如果刪除的根右邊的結點,那么就是找后繼結點,后繼結點是其右子樹中最小的結點,后繼結點的左子樹一定為空,因為沒有比它小的了 * 如此一來,可以看出找到的前驅/后繼結點的條件肯定是滿足1或者2的 */ BinarySearchTree<T> parent = parentRef.getValue(); //葉子結點直接將父結點的孩子置空 if (delBinarySearchTree.leftChild == null && delBinarySearchTree.rightChild == null) { if (parent.rightChild == delBinarySearchTree) { parent.rightChild = null; } else { parent.leftChild = null; } } //度為2的結點刪除 else if (delBinarySearchTree.leftChild != null && delBinarySearchTree.rightChild != null) { //刪除比根大的,刪除的結點在根的右邊,需要找后繼結點 if (root.data.compareTo(data) < 0) { RefObject<BinarySearchTree<T>> postParentRef = new RefObject<>(); //后繼結點的父結點 BinarySearchTree<T> postNode = findMin(root.rightChild, postParentRef); //判斷要刪除的結點是它父結點的左結點還是右結點,修改對應指針指向后繼結點 if (parent.data.compareTo(delBinarySearchTree.data) < 0) { parent.rightChild = postNode; } else { parent.leftChild = postNode; } postParentRef.getValue().leftChild = null; //后繼結點因為要移走,所以置空其父結點的左孩子(后繼必定是其父的左孩子) if (postNode.rightChild != null) {//后繼結點的左子樹一定為空, 將其父結點的左孩子指向后繼結點的右孩子 postParentRef.getValue().leftChild = postNode.rightChild; postNode.rightChild = null; //置空相關引用,便於垃圾回收 } //將刪除的這個結點的左右子樹的指針給到后繼結點 postNode.rightChild = delBinarySearchTree.rightChild; delBinarySearchTree.rightChild = null; //置空相關引用,便於垃圾回收 postNode.leftChild = delBinarySearchTree.leftChild; delBinarySearchTree.leftChild = null; //置空相關引用,便於垃圾回收 } //刪除根或者比根小的,刪除的結點在根的左邊,需要找前驅結點 else{ RefObject<BinarySearchTree<T>> preParentRef = new RefObject<>(); //前驅結點的父結點 BinarySearchTree<T> preNode = findMax(root.leftChild, preParentRef); if (parent != null) { //如果刪除的是根結點 沒有父結點 //判斷要刪除的結點是它父結點的左結點還是右結點,修改對應指針指向前驅結點 if (parent.data.compareTo(delBinarySearchTree.data) < 0) { parent.rightChild = preNode; } else { parent.leftChild = preNode; } } preParentRef.getValue().rightChild = null; //前驅結點因為要移走,所以置空其父結點的右孩子(前驅必定是其父的右孩子) if (preNode.leftChild != null) {//前驅結點的右子樹一定為空, 將其父結點的右孩子指向前驅結點的左孩子 preParentRef.getValue().rightChild = preNode.leftChild; preNode.leftChild = null; //置空相關引用,便於垃圾回收 } if (delBinarySearchTree == root) { //刪除的是根 直接將交換值 delBinarySearchTree.data = preNode.data; } else { //將刪除的這個結點的左右孩子的指針給到前驅結點 preNode.rightChild = delBinarySearchTree.rightChild; delBinarySearchTree.rightChild = null; //置空相關引用,便於垃圾回收 preNode.leftChild = delBinarySearchTree.leftChild; delBinarySearchTree.leftChild = null; //置空相關引用,便於垃圾回收 } } } //度為1的結點刪除 將其父結點的孩子指向它的孩子 else { BinarySearchTree<T> leftChild = delBinarySearchTree.leftChild; BinarySearchTree<T> child = leftChild == null ? delBinarySearchTree.rightChild : leftChild; delBinarySearchTree.leftChild = null; //置空相關引用,便於垃圾回收 delBinarySearchTree.rightChild = null; //置空相關引用,便於垃圾回收 if (parent.data.compareTo(child.data) < 0) { parent.rightChild = child; } else { parent.leftChild = child; } } } @Override public T getData() { return data; } @Override public void setData(T data) { this.data = data; } @Override public BinarySearchTree<T> getLeftChild() { return leftChild; } public void setLeftChild(BinarySearchTree<T> leftChild) { this.leftChild = leftChild; } @Override public BinarySearchTree<T> getRightChild() { return rightChild; } public void setRightChild(BinarySearchTree<T> rightChild) { this.rightChild = rightChild; } }
遍歷代碼:
package com.nijunyang.algorithm.tree; import java.util.LinkedList; import java.util.Queue; import java.util.Stack; /** * @author: create by nijunyang * @date:2019/7/28 */ public final class TreeUtil { private TreeUtil() { } /** * 構造二叉樹 * @param dataList * @param <T> * @return */ public static <T> TreeNode<T> createBinaryTree(LinkedList<T> dataList) { TreeNode<T> node = null; if (dataList == null || dataList.isEmpty()) { return null; } T data = dataList.removeFirst(); if (data != null) { node = new TreeNode(data); node.setLeftChild((createBinaryTree(dataList))); node.setRightChild((createBinaryTree(dataList))); } return node; } /** * 前序遍歷 根 左子樹 右子樹 * @param node */ public static<N extends TreeNode<T>, T> void preOrderTraversal(N node) { if(node == null){ return; } //遇根先輸出,再去找左右 System.out.print(node.getData()); preOrderTraversal(node.getLeftChild()); preOrderTraversal(node.getRightChild()); } /** * 二叉樹中序遍歷 左子樹 根 右子樹 * @param node 二叉樹節點 */ public static<N extends TreeNode<T>, T> void inOrderTraversal(N node){ if(node == null){ return; } //先找左再輸出根,再去找右 inOrderTraversal(node.getLeftChild()); System.out.print(node.getData()); inOrderTraversal(node.getRightChild()); } /** * 二叉樹后序遍歷 左子樹 右子樹 根 * @param node 二叉樹節點 */ public static<N extends TreeNode<T>, T> void postOrderTraversal(N node){ if(node == null){ return; } //先找左右,最后輸出根 postOrderTraversal(node.getLeftChild()); postOrderTraversal(node.getRightChild()); System.out.print(node.getData()); } /** * 利用棧前序遍歷二叉樹 * @param root */ public static <N extends TreeNode<T>, T> void preOrderTraversalByStack(N root) { Stack<TreeNode<T>> stack = new Stack<>(); TreeNode<T> node = root; while(node != null || !stack.isEmpty()) { //節點不為空,遍歷節點,並入棧用於回溯 while(node != null) { System.out.print(node.getData()); stack.push(node); node = node.getLeftChild(); } //沒有左節點,彈出該棧頂節點(回溯),訪問右節點 if(!stack.isEmpty()) { node = stack.pop(); node = node.getRightChild(); } } } /** * 層次遍歷 * @param root * @param <T> */ public static <N extends TreeNode<T>, T> void levelOrder(N root) { if (root == null) { return; } Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); //入隊 while (!queue.isEmpty()) { TreeNode<T> node = queue.poll(); //取出 if (node != null) { System.out.print(node.getData()); queue.offer(node.getLeftChild()); //左孩子入隊 queue.offer(node.getRightChild()); //右孩子入隊 } } } }
RefObject:
package com.nijunyang.algorithm.util; /** * Description: 引用包裝,用於去一個方法里面除開返回值之外,將其他額外需要的數據帶出來 * Created by nijunyang on 2020/4/20 10:26 */ public class RefObject<E> { public RefObject() { } public RefObject(E value) { this.value = value; } private E value; public E getValue() { return value; } public void setValue(E value) { this.value = value; } }