本文從屬於筆者的數據結構與算法系列文章。java
二叉查找樹能夠遞歸地定義以下,二叉查找樹或者是空二叉樹,或者是知足下列性質的二叉樹:github
(1)若它的左子樹不爲空,則其左子樹上任意結點的關鍵字的值都小於根結點關鍵字的值。算法
(2)若它的右子樹不爲空,則其右子樹上任意結點的關鍵字的值都大於根節點關鍵字的值。數據結構
(3)它的左、右子樹自己又是一個二叉查找樹。app
從性能上來講若是二叉查找樹的全部非葉子結點的左右子樹的結點數目均保持差很少(平衡),那麼二叉查找樹的搜索性能逼近二分查找;但它比連續內存空間的二分查找的優勢是,改變二叉查找樹結構(插入與刪除結點)不須要移動大段的內存數據,甚至一般是常數開銷。二叉查找樹能夠表示按順序序列排列的數據集合,所以二叉查找樹也被稱爲二叉排序樹,而且同一個數據集合能夠表示爲不一樣的二叉查找樹。二叉查找樹的結點的數據結構定義爲:socket
struct celltype{ records data; celltype * lchild, * rchild; } typedef celltype * BST;
在Java中,節點的數據結構定義以下:函數
package wx.algorithm.search.bst; /** * Created by apple on 16/7/29. */ /** * @function 二叉搜索樹中的節點 */ public class Node { //存放節點數據 int data; //指向左子節點 Node left; //指向右子節點 Node right; /** * @function 默認構造函數 * @param data 節點數據 */ public Node(int data) { this.data = data; left = null; right = null; } }
而二叉查找樹的查找過程爲從根結點開始,若是查詢的關鍵字與結點的關鍵字相等,那麼就命中;不然,若是查詢關鍵字比結點關鍵字小,就進入左兒子;若是比結點關鍵字大,就進入右兒子;若是左兒子或右兒子的指針爲空,則報告找不到相應的關鍵字。性能
BST Search(keytype k, BST F){ //在F所指的二叉查找樹中查找關鍵字爲k的記錄。若成功,則返回響應結點的指針,不然返回空 if(F == NULL) //查找失敗 return NULL; else if(k == F -> data.key){ //查找成功 return F; } else if (k < F -> data.key){ //查找左子樹 return Search(k,F -> lchild); } else if (k > F -> data.key){ //查找右子樹 return Search(k,F -> rchild); } }
把一個新的記錄R插入到二叉查找樹,應該保證在插入以後不破壞二叉查找樹的結構性質。所以,爲了執行插入操做首先應該查找R所在的位置。查找時,仍然採用上述的遞歸算法。若查找失敗,則把包含R的結點插在具備空子樹位置,若查找成功,則不執行插入,操做結束。
void Insert(records R, BST &F){ //在F所指的二叉查找樹中插入一個新紀錄R if(F == NULL){ F = new celltype; F -> data = R; F -> lchild = NULL; F -> rchild = NULL; } else if (R.key < F -> data.key){ Insert(R,F -> lchild); }else if(R.key > F -> data.key){ Insert(R,F -> rchild); } //若是 R.key == F -> data.key 則返回 }
若是咱們進行簡單的替換,那麼可能碰到以下狀況:
所以咱們要在子樹中選擇一個合適的替換節點,替換節點通常來講會是右子樹中的最小的節點:
BinarySearchTree的Java版本代碼參考BinarySearchTree:
package wx.algorithm.search.bst; /** * Created by apple on 16/7/29. */ /** * @function 二叉搜索樹的示範代碼 */ public class BinarySearchTree { //指向二叉搜索樹的根節點 private Node root; //默認構造函數 public BinarySearchTree() { this.root = null; } /** * @param id 待查找的值 * @return * @function 默認搜索函數 */ public boolean find(int id) { //從根節點開始查詢 Node current = root; //當節點不爲空 while (current != null) { //是否已經查詢到 if (current.data == id) { return true; } else if (current.data > id) { //查詢左子樹 current = current.left; } else { //查詢右子樹 current = current.right; } } return false; } /** * @param id * @function 插入某個節點 */ public void insert(int id) { //建立一個新的節點 Node newNode = new Node(id); //判斷根節點是否爲空 if (root == null) { root = newNode; return; } //設置current指針指向當前根節點 Node current = root; //設置父節點爲空 Node parent = null; //遍歷直到找到第一個插入點 while (true) { //先將父節點設置爲當前節點 parent = current; //若是小於當前節點的值 if (id < current.data) { //移向左節點 current = current.left; //若是當前節點不爲空,則繼續向下一層搜索 if (current == null) { parent.left = newNode; return; } } else { //不然移向右節點 current = current.right; //若是當前節點不爲空,則繼續向下一層搜索 if (current == null) { parent.right = newNode; return; } } } } /** * @param id * @return * @function 刪除樹中的某個元素 */ public boolean delete(int id) { Node parent = root; Node current = root; //記錄被找到的節點是父節點的左子節點仍是右子節點 boolean isLeftChild = false; //循環直到找到目標節點的位置,不然報錯 while (current.data != id) { parent = current; if (current.data > id) { isLeftChild = true; current = current.left; } else { isLeftChild = false; current = current.right; } if (current == null) { return false; } } //若是待刪除的節點沒有任何子節點 //直接將該節點的本來指向該節點的指針設置爲null if (current.left == null && current.right == null) { if (current == root) { root = null; } if (isLeftChild == true) { parent.left = null; } else { parent.right = null; } } //若是待刪除的節點有一個子節點,且其爲左子節點 else if (current.right == null) { //判斷當前節點是否爲根節點 if (current == root) { root = current.left; } else if (isLeftChild) { //掛載到父節點的左子樹 parent.left = current.left; } else { //掛載到父節點的右子樹 parent.right = current.left; } } else if (current.left == null) { if (current == root) { root = current.right; } else if (isLeftChild) { parent.left = current.right; } else { parent.right = current.right; } } //若是待刪除的節點有兩個子節點 else if (current.left != null && current.right != null) { //尋找右子樹中的最小值 Node successor = getSuccessor(current); if (current == root) { root = successor; } else if (isLeftChild) { parent.left = successor; } else { parent.right = successor; } successor.left = current.left; } return true; } /** * @param deleleNode * @return * @function 在樹種查找最合適的節點 */ private Node getSuccessor(Node deleleNode) { Node successsor = null; Node successsorParent = null; Node current = deleleNode.right; while (current != null) { successsorParent = successsor; successsor = current; current = current.left; } if (successsor != deleleNode.right) { successsorParent.left = successsor.right; successsor.right = deleleNode.right; } return successsor; } /** * @function 以中根順序遍歷樹 */ public void display() { display(root); } private void display(Node node) { //判斷當前節點是否爲空 if (node != null) { //首先展現左子樹 display(node.left); //而後展現當前根節點的值 System.out.print(" " + node.data); //最後展現右子樹的值 display(node.right); } } }
測試函數:
package wx.algorithm.search.bst; import org.junit.Before; import org.junit.Test; /** * Created by apple on 16/7/30. */ public class BinarySearchTreeTest { BinarySearchTree binarySearchTree; @Before public void setUp() { binarySearchTree = new BinarySearchTree(); binarySearchTree.insert(3); binarySearchTree.insert(8); binarySearchTree.insert(1); binarySearchTree.insert(4); binarySearchTree.insert(6); binarySearchTree.insert(2); binarySearchTree.insert(10); binarySearchTree.insert(9); binarySearchTree.insert(20); binarySearchTree.insert(25); binarySearchTree.insert(15); binarySearchTree.insert(16); System.out.println("原始的樹 : "); binarySearchTree.display(); System.out.println(""); } @Test public void testFind() { System.out.println("判斷4是否存在樹中 : " + binarySearchTree.find(4)); } @Test public void testInsert() { } @Test public void testDelete() { System.out.println("刪除值爲2的節點 : " + binarySearchTree.delete(2)); binarySearchTree.display(); System.out.println("\n 刪除有一個子節點值爲4的節點 : " + binarySearchTree.delete(4)); binarySearchTree.display(); System.out.println("\n 刪除有兩個子節點的值爲10的節點 : " + binarySearchTree.delete(10)); binarySearchTree.display(); } }