二叉排序樹與平衡二叉樹
二叉排序樹 :
特點:
1、如果它的左子樹不空,那麼左子樹上的所有結點值均小於它的根結點值;
2、如果它的右子樹不空,那麼右子樹上的所有結點值均大於它的根結點值;
3、它的左右子樹也分別為二叉查詢樹
如下如所示二叉查詢樹:
二叉查詢樹的插入和刪除都非常的方便,很好的解決了折半查詢新增刪除所帶來的問題。
那麼它的效率又如何呢?
很顯然,二叉查詢樹查詢一個數據,不需要遍歷全部的節點,查詢效率確實提高了。但是,也有一個很嚴重的問題,我在a圖中查詢8需要比較5次,而在b圖中查詢8需要3次,更為嚴重的是,我的二叉查詢樹是c圖,如果再查詢8,那將會如何呢?很顯然,整棵樹就退化成了一個線性結構,此時再查詢8,就和順序查詢沒什麼區別了。
時間複雜度分析:最壞的情況下和順序查詢相同,是O(N),最好的情況下和折半查詢相同,是O(logN)。進過研究發現,在隨機的情況下,二叉排序樹的平均查詢長度和logn是等數量級的。然而,在某些情況下(有人研究表明,這種概率大約佔46.5%)尚需在構成二叉排序樹的過程中進行”平衡化”處理,成為二叉平衡樹。
這說明了一個問題,同樣的一組資料集合,不同的新增順序會導致二叉查詢樹的結構完全不一樣,直接影響到了查詢的效率。
二叉排序樹的一些基本操作,包括,新增節點,刪除節點,獲取做大的節點,獲取最小的節點,得到任意孩子節點的父節點等。
/**
* 向二叉排序樹中新增節點
* @param root
* @param value
* @return 二叉排數樹根節點
*/
public static TreeNode insertTreeNode(TreeNode root,int value){
if(root == null){
root = new TreeNode(value);
}else if(root.value>value){
root.left = insertTreeNode(root.left,value);
}else if(root.value<value){
root.right =insertTreeNode(root.right,value);
}
return root;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
/**
* 在二叉排數樹中查詢,關鍵字為key的節點
* @param root
* @param key
* @return 二叉排數樹中關鍵字為key的節點
*/
public static TreeNode serachTreeNode(TreeNode root,int key){
if(root == null){
return null;
}
if(key>root.value)
return serachTreeNode(root.right,key);
else if(key<root.value)
return serachTreeNode(root.left,key);
else
return root;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
//獲取父節點
public static TreeNode getParentTreeNode (TreeNode root,int key){
Queue<TreeNode> queue = new LinkedList<TreeNode>();
if(root==null || root.value==key){
return null;
}
queue.add(root);
while(!queue.isEmpty()){
TreeNode current = queue.poll();
if(current.left!=null){
if(current.left.value==key)
return current;
else
queue.add(current.left);
}
if(current.right!=null){
if(current.right.value==key)
return current;
else
queue.add(current.right);
}
}
return null;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
//獲得關鍵字最大的節點
public static TreeNode getMaxTreeNode(TreeNode root){
if(root == null || root.right==null)
return root;
if(root.right != null){
root = getMaxTreeNode(root.right);
}
return root;
}
//獲得關鍵字最小的節點
public static TreeNode getMinTreeNode(TreeNode root){
if(root == null || root.left==null)
return root;
if(root.left != null){
root = getMinTreeNode(root.left);
}
return root;
}
//列印排序二叉樹
public static void printInOrder(TreeNode root){
if(root!=null){
printInOrder(root.left);
System.out.print(root.value+" ");
printInOrder(root.right);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
最後關於二叉排序樹的刪除相對比較複雜,可以分下面三種情況討論:
(1)需要刪除的節點下並沒有其他子節點,其為葉子節點。
(2)需要刪除的節點下有一個子節點(左或右)。
(3)需要刪除的節點下有兩個子節點(既左右節點都存在)。
-
針對第一種情況:刪除葉子節點,例如刪除4,只需判斷4是它的父節點的左孩子,還是右孩子。如圖中,4則為其父節點3的右孩子,所以直接將3的右孩子置為空。
-
針對第二種情況:要刪除的節點有一個孩子節點,若其為其父節點的右孩子,則讓其父節點的右指標指向其孩子節點;若其為其父節點的左孩子,則讓其父節點的左指標指向其孩子節點。
-
針對第三種情況:要刪除的節點既有左孩子又有右孩子,例如節點2,則我們先在需要刪除的節點的右子樹中,找到一個最小的值(因為右子樹中的節點的值一定大於根節點)4。然後,用找到的最小的值與需要刪除的節點的值替換。然後,再將最小值的原節點進行刪除.
public static void deleteTreeNode(TreeNode root,TreeNode pNode){
TreeNode parent = getParentTreeNode(root,pNode.value);
//該節點是葉子節點
if(pNode.left==null && pNode.right==null){
//該節點是父節點的左孩子
if(parent.left==pNode)
parent.left=null;
else
parent.right=null;
//該節點只有右孩子
}else if(pNode.left==null&&pNode.right!=null){
//該節點為其父節點的左孩子
if(pNode == parent.left ){
parent.left = pNode.right;
}else{
parent.right = pNode.right;
}
//該節點只有右孩子
}else if(pNode.left!=null&&pNode.right==null){
if(pNode ==parent.left ){
parent.left = pNode.left;
}else{
parent.right = pNode.left;
}
//該節點左右孩子都存在
}else if(pNode.left!=null&&pNode.right!=null){
/*
* 我們先在需要刪除的節點的右子樹中,找到一個最小的值(因為右子樹中的節點的值一定大於根節點)。
* 然後,用找到的最小的值與需要刪除的節點的值替換。然後,再將最小值的原節點進行刪除
* */
TreeNode minNode=getMinTreeNode(pNode.right);
deleteTreeNode(root,minNode);
TreeNode pNpdeparent = getParentTreeNode(root,pNode.value);
if(pNpdeparent.left==pNode){
pNpdeparent.left=minNode;
minNode.left = pNode.left;
minNode.right = pNode.right;
}
else{
pNpdeparent.right=minNode;
minNode.left = pNode.left;
minNode.right = pNode.right;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
最後附上有關二叉排序樹的基本操作的程式碼:(可直接執行)
輸入:兩行,第一行為二叉樹節點的個數,第二行為各個節點的值(不重複)
10
89 67 34 30 40 50 44 55 66 77
輸出:逐步按輸入刪除節點(除根節點)後二叉排序樹的中序遍歷(有序)
30 34 40 44 50 55 66 67 77 89
30 34 40 44 50 55 66 77 89
30 40 44 50 55 66 77 89
40 44 50 55 66 77 89
44 50 55 66 77 89
44 55 66 77 89
55 66 77 89
66 77 89
77 89
89
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.LinkedList;
import java.util.Queue;
class TreeNode {
public int value;
public TreeNode left;
public TreeNode right;
public TreeNode(int value){
this.value = value;
this.left = this.right = null;
}
}
public class BinarySearchTree {
/**
* 向二叉排序樹中新增節點
* @param root
* @param value
* @return 二叉排數樹根節點
*/
public static TreeNode insertTreeNode(TreeNode root,int value){
if(root == null){
root = new TreeNode(value);
}else if(root.value>value){
root.left = insertTreeNode(root.left,value);
}else if(root.value<value){
root.right =insertTreeNode(root.right,value);
}
return root;
}
/**
* 在二叉排數樹中查詢,關鍵字為key的節點
* @param root
* @param key
* @return 二叉排數樹中關鍵字為key的節點
*/
public static TreeNode serachTreeNode(TreeNode root,int key){
if(root == null){
return null;
}
if(key>root.value)
return serachTreeNode(root.right,key);
else if(key<root.value)
return serachTreeNode(root.left,key);
else
return root;
}
public static void deleteTreeNode(TreeNode root,TreeNode pNode){
TreeNode parent = getParentTreeNode(root,pNode.value);
//該節點是葉子節點
if(pNode.left==null && pNode.right==null){
//該節點是父節點的左孩子
if(parent.left==pNode)
parent.left=null;
else
parent.right=null;
//該節點只有右孩子
}else if(pNode.left==null&&pNode.right!=null){
//該節點為其父節點的左孩子
if(pNode == parent.left ){
parent.left = pNode.right;
}else{
parent.right = pNode.right;
}
//該節點只有右孩子
}else if(pNode.left!=null&&pNode.right==null){
if(pNode ==parent.left ){
parent.left = pNode.left;
}else{
parent.right = pNode.left;
}
//該節點左右孩子都存在
}else if(pNode.left!=null&&pNode.right!=null){
/*
* 我們先在需要刪除的節點的右子樹中,找到一個最小的值(因為右子樹中的節點的值一定大於根節點)。
* 然後,用找到的最小的值與需要刪除的節點的值替換。然後,再將最小值的原節點進行刪除
* */
TreeNode minNode=getMinTreeNode(pNode.right);
deleteTreeNode(root,minNode);
TreeNode pNpdeparent = getParentTreeNode(root,pNode.value);
if(pNpdeparent.left==pNode){
pNpdeparent.left=minNode;
minNode.left = pNode.left;
minNode.right = pNode.right;
}
else{
pNpdeparent.right=minNode;
minNode.left = pNode.left;
minNode.right = pNode.right;
}
}
}
//獲取父節點
/*
public static TreeNode getParentTreeNode(TreeNode root,int key){
if(root!=null){
if(root.left!=null && root.right!=null){
if(root.left.value == key || root.right.value ==key){
return root;
}else{
TreeNode tempRoot= getParentTreeNode(root.left,key);
if(tempRoot!=null)
return tempRoot;
root = getParentTreeNode(root.right,key);
}
}else if(root.left==null && root.right!=null){
if(root.right.value ==key){
return root;
}else{
root = getParentTreeNode(root.right,key);
}
}else if(root.left!=null && root.right==null){
if(root.left.value ==key){
return root;
}else{
root = getParentTreeNode(root.left,key);
}
}
else {
return null;
}
}
return root;
}
*/
public static TreeNode getParentTreeNode (TreeNode root,int key){
Queue<TreeNode> queue = new LinkedList<TreeNode>();
if(root==null || root.value==key){
return null;
}
queue.add(root);
while(!queue.isEmpty()){
TreeNode current = queue.poll();
if(current.left!=null){
if(current.left.value==key)
return current;
else
queue.add(current.left);
}
if(current.right!=null){
if(current.right.value==key)
return current;
else
queue.add(current.right);
}
}
return null;
}
//獲得關鍵字最大的節點
public static TreeNode getMaxTreeNode(TreeNode root){
if(root == null || root.right==null)
return root;
if(root.right != null){
root = getMaxTreeNode(root.right);
}
return root;
}
//獲得關鍵字最小的節點
public static TreeNode getMinTreeNode(TreeNode root){
if(root == null || root.left==null)
return root;
if(root.left != null){
root = getMinTreeNode(root.left);
}
return root;
}
//列印排序二叉樹
public static void printInOrder(TreeNode root){
if(root!=null){
printInOrder(root.left);
System.out.print(root.value+" ");
printInOrder(root.right);
}
}
public static void main(String[] args) throws IOException {
StreamTokenizer cin = new StreamTokenizer (new BufferedReader(new InputStreamReader(System.in)));
while(cin.nextToken()!=cin.TT_EOF){
int n =(int)cin.nval;
int []input = new int[n];
for(int i=0;i<n;i++){
cin.nextToken();
input[i] = (int)cin.nval;
}
TreeNode root = null;
for(int i=0;i<n;i++){
root = insertTreeNode(root,input[i]);
}
printInOrder(root);
for(int i=1;i<input.length;i++){
TreeNode pNpde = serachTreeNode(root,input[i]);
deleteTreeNode(root,pNpde);
System.out.print("\n");
printInOrder(root);
}
}
}
}
平衡二叉樹:
由於二叉排序樹的查詢效率是在O(n)~O(logn)之間,為了提高二叉排序樹的查詢效率,於是很多人想到通過調節二叉排序樹使之變化成為左右子樹之間的深度絕對值之差不超過1的平衡二叉樹。
最小二叉平衡樹的節點的公式如下 F(n)=F(n-1)+F(n-2)+1 這個類似於一個遞迴的數列,可以參考Fibonacci數列,1是根節點,F(n-1)是左子樹的節點數量,F(n-2)是右子樹的節點數量。
關於二叉排序樹這篇文章介紹的比較好:二叉排序樹的介紹