目錄:
1.什么是二叉搜索樹
2.二叉搜索樹的由來與作用
3.二叉搜索樹的建立
1.什么是二叉搜索樹?
二叉搜索樹又稱為二叉排序樹,它或者是一棵空樹,或者是具有一下性質的樹:
若它的左子樹不空,則左子樹上所有的結點的值均不大於它根結點的值;
若它的左子樹不空,則左子樹上所有的結點的值均不小於它根結點的值;
它的左右子樹也是二叉搜索樹。
2.二叉搜素樹的由來與作用?
假設我們現在有一個數據集,且這個數據集是順序存儲的有序線性表,那么查找可以運用折半、插值、斐波那契二等查找算法來實現,但是因為有序,在插入和刪除操作上,就需要耗費大量的時間(需進行元素的移位),能否有一種既可以使得插入和刪除效率不錯,又可高效查找的數據結構和算法呢?
首先解決一個問題,如何使插入時不移動元素,我們可以想到鏈表,但是要保證其有序的話,首先得遍歷鏈表尋找合適的位置,那么又如何高效的查找合適的位置呢,能否可以像二分一樣,通過一次比較排除一部分元素。那么我們可以用二叉樹的形式,以數據集第一個元素為根節點,之后將比根節點小的元素放在左子樹中,將比根節點大的元素放在左子樹中,在左右子樹中同樣采取此規則。
例子:將{63,55,90,42,58,70,10,45,67,83}形成二叉樹
那么在查找x時,若x比根節點小可以排除右子樹所有元素,去左子樹中查找(類似二分查找),這樣查找的效率非常好,而且插入的時間復雜度為O(h),h為樹的高度,較O(n)來說效率提高不少。
故二叉搜索樹用作一些查找和插入使用比較高的場景。
3.二叉搜索樹的建立、查詢、最大最小關鍵字,前驅、后繼
先來一個二叉搜索樹的結點結構
class BiNode{ public BiNode left; public BiNode right; public BiNode parent; public int data; public BiNode(BiNode left, BiNode right, BiNode parent, int data){ this.left = left; this.right = right; this.parent = parent; this.data = data; } }
在由來之中已經提到,二叉搜索樹的建立,是通過一個一個的結點的插入來建立,每個結點有四個域,左右孩子、雙親、數據。
插入算法:將要插入的結點x,與根節點進行比較,若小於則去到左子樹進行比較,若大於則去到右子樹進行比較,重復以上操作直到找到一個空位置用於放置該新節點
其中root為該樹的根節點。
public void insert(BiNode x){ BiNode y = null; BiNode temp = root; while(temp != null){ y = temp; if(x.data < temp.data){ temp = temp.left; }else{ temp = temp.right; } } x.parent = y; //若該樹為空樹可直接將x放置在根節點上 if(null == y){ root = x; }else if(x.data<y.data){//找到空位置后,進行插入 y.left = x; }else{ y.right = x; } }
建立:可想而知,簡單的方法就是遍歷數組,將每一個元素插入到二叉搜索樹中便完成了建立操作
public BiSortTree(int[] arr,int n){ for(int i=0;i<n;i++){ BiNode node = new BiNode(null, null ,null, arr[i]); insert(node); } }
查詢:根據二叉搜索樹的性質,將需查找的x與根節點進行比較,若小於則在左子樹中繼續查詢,若大於則在右子樹中繼續查詢,直到查到元素或元素為空
遞歸查找:
/** * 查找結點,通過遞歸 * @param x * @return */ public BiNode queryByRec(BiNode root, int x){ if (x == root.data || null == root){ return root; } else if(x < root.data) { return queryByRec(root.left, x); } else { return queryByRec(root.right, x); } }
非遞歸查找(能不用遞歸則不用遞歸,遞歸會隱式的創建棧,從而浪費內存空間):
/** * 查找結點,非遞歸,需要傳入查找根節點 * @param x * @return */ public BiNode query(BiNode root, int x){ while(null != root && root.data != x){ if(x < root.data){ root = root.left; }else{ root = root.right; } } return root; }
最大最小關鍵字:這個樹中的最大的元素,根據二叉搜索樹性質,最大關鍵字為該樹的最右元素,最小關鍵字為該樹的最左元素,故直接將這兩個元素搜索出來即可(通過傳入根節點可實現查找子樹的最大最小關鍵字元素)
/** * 查找最小關鍵字元素,即最左結點,需要傳入查找根節點 * @return */ public BiNode minNode(BiNode x){ while(x.left!=null){ x=x.left; } return x; } /** * 查找最大關鍵字元素,即最右結點,需要傳入查找根節點 * @return */ public BiNode maxNode(BiNode x){ while(x.right!=null){ x=x.right; } return x; }
查找后繼結點:如果我們需要找到以下二叉搜索樹的后繼,根據需要考慮兩種情況
1.如果該節點有右子樹,那么它右子樹的最小關鍵字元素便是其后繼
2.如果該節點無右子樹,由於后繼為大於該元素的元素,那么這個結點必在其后繼的左子樹上,又由於后繼為大於該元素的最小元素那么這個后繼必然是該節點第一個擁有左孩子的的祖先,綜合一下:這個結點的后繼為這個結點第一個有左孩子的祖先,且這個左孩子也是這個結點的祖先。
/** * 查找指定數字的后繼 * @return */ public BiNode queryFollow(int x){ //首先查找指定結點是否存在 BiNode node = query(root, x); //不存在返回null if(null == node){ return null; } //若該節點有右子樹,則其后繼為其右子樹的最左結點 //若該節點無右子樹,則其后繼為其第一個擁有左孩子的祖先,且這個左孩子也是該結點的祖先 BiNode p = null; if(node.right!=null){ return minNode(node.right); }else{ p = node.parent; while(null != p && node == p.right){ node = p; p=p.parent; } return p; } }
前驅與后繼是相互對應的,不妨自己思考一下,代碼如下:
public BiNode queryPre(int x){ BiNode node = query(root, x); BiNode p = null; if(null == node){ return null; } if(node.left!=null){ return maxNode(node.left); }else{ p = node.parent; while(p!=null&&node==p.left){ node=p; p=p.parent; } return p; } }