今天就聊聊這個”五大經典查找“中的最後一個”二叉排序樹“,又叫二叉查找樹。
1. 概念
如圖就是一棵二叉排序樹:
2.實際操作:
我們都知道,對一個東西進行操作,無非就是增刪查改,接下來我們就聊聊其中的基本操作。
<1> 插入:相信大家對“排序樹”的概念都清楚了吧,那麼插入的原理就很簡單了。
比如說我們插入一個20到這棵樹中。
首先:20跟50比,發現20是老小,不得已,得要歸結到50的左子樹中去比較。
然後:20跟30比,發現20還是老小。
再然後:20跟10比,發現自己是老大,隨即插入到10的右子樹中。
最後: 效果呈現圖如下:
<2>查找:相信懂得了插入,查找就跟容易理解了。
就拿上面一幅圖來說,比如我想找到節點10.
首先:10跟50比,發現10是老小,則在50的左子樹中找。
然後:10跟30比,發現還是老小,則在30的左子樹中找。
再然後: 10跟10比,發現一樣,然後就返回找到的信號。
<3>刪除:刪除節點在樹中還是比較麻煩的,主要有三種情況。
《1》 刪除的是“葉節點20“,這種情況還是比較簡單的,刪除20不會破壞樹的結構。如圖:
《2》刪除”單孩子節點90“,這個情況相比第一種要麻煩一點點,需要把他的孩子頂上去。
《3》刪除“左右孩子都有的節點50”,這個讓我在代碼編寫上糾結了好長時間,問題很直白,我把50刪掉了,誰頂上去了問題,是左孩子呢?還是右
孩子呢?還是另有蹊蹺?這裏我就坦白吧,不知道大家可否知道“二叉樹”的中序遍歷,現在可以當公式記住吧,就是找到右節點的左子樹最左孩子。
比如:首先 找到50的右孩子70。
然後 找到70的最左孩子,發現沒有,則返回自己。
最後 原始圖和最終圖如下。
3.說了這麼多,上代碼說話。
- //二叉搜索樹
- public class BinarySearchTree<T extends Comparable<? super T>>
- {
- /** 二叉排序樹的根 */
- private BinaryNode<T> root;
- /**
- * 構造一棵空樹.
- */
- public BinarySearchTree( )
- {
- root = null ;
- }
- /**
- * 在二叉搜索樹中插入數據.
- * @param x the item to insert.
- */
- public void insert( T x )
- {
- root = insert( x, root );
- }
- /**
- * 從二叉搜索中刪除數據(節點).
- * @param x the item to remove.
- */
- public void remove( T x )
- {
- root = remove( x, root );
- }
- /**
- * 找最小數據.
- * @return smallest item or null if empty.
- */
- public T findMin( )
- {
- if ( isEmpty( ) )
- throw new UnderflowException( );
- return findMin( root ).element;
- }
- /**
- * 找最大數據.
- * @return the largest item of null if empty.
- */
- public T findMax( )
- {
- if ( isEmpty( ) )
- throw new UnderflowException( );
- return findMax( root ).element;
- }
- //二叉搜索樹中是否包含x
- public boolean contains( T x )
- {
- return contains( x, root );
- }
- public void makeEmpty( )
- {
- root = null ;
- }
- public boolean isEmpty( )
- {
- return root == null ;
- }
- //中序遍歷輸出二叉搜索樹的內容
- public void printTree( )
- {
- if ( isEmpty( ) )
- System.out.println( "Empty tree" );
- else
- printTree( root );
- }
- //插入
- private BinaryNode<T> insert( T x, BinaryNode<T> t )
- {
- if ( t == null )
- return new BinaryNode<T>( x, null , null );
- int compareResult = x.compareTo( t.element );
- if ( compareResult < 0 )
- t.left = insert( x, t.left );
- else if ( compareResult > 0 )
- t.right = insert( x, t.right );
- else
- ; // 重複的,什麼也不做。當然你也可以將重複的數據加入右邊。
- return t;
- }
- //刪除
- private BinaryNode<T> remove( T x, BinaryNode<T> t )
- {
- //先在樹中查找x
- if ( t == null )
- return t; // 沒有找到,返回
- int compareResult = x.compareTo( t.element );
- // 在左樹中找
- if ( compareResult < 0 )
- t.left = remove( x, t.left );
- //在右樹中找
- else if ( compareResult > 0 )
- t.right = remove( x, t.right );
- //在樹中找到了節點值爲x的節點
- else if ( t.left != null && t.right != null ) // 這個節點有兩個孩子節點
- {
- t.element = findMin( t.right ).element;
- t.right = remove( t.element, t.right );
- }
- else //這個節點只有一個孩子節點或沒有孩子節點
- t = ( t.left != null ) ? t.left : t.right;
- return t;
- }
- //在二叉搜索樹中找最小值節點
- private BinaryNode<T> findMin( BinaryNode<T> t )
- {
- if ( t == null )
- return null ;
- else if ( t.left == null )
- return t;
- return findMin( t.left );
- }
- //在二叉搜索樹中找最大值節點
- private BinaryNode<T> findMax( BinaryNode<T> t )
- {
- if ( t != null )
- while ( t.right != null )
- t = t.right;
- return t;
- }
- //是否包含
- private boolean contains( T x, BinaryNode<T> t )
- {
- if ( t == null )
- return false ;
- int compareResult = x.compareTo( t.element );
- if ( compareResult < 0 )
- return contains( x, t.left );
- else if ( compareResult > 0 )
- return contains( x, t.right );
- else
- return true ; // Match
- }
- //中序遍歷二叉樹
- private void printTree( BinaryNode<T> t )
- {
- if ( t != null )
- {
- printTree( t.left );
- System.out.print(t.element+" " );
- printTree( t.right );
- }
- }
- // 二叉搜索樹節點
- private static class BinaryNode<T>
- {
- // Constructors
- BinaryNode( T theElement )
- {
- this ( theElement, null , null );
- }
- BinaryNode( T theElement, BinaryNode<T> lt, BinaryNode<T> rt )
- {
- element = theElement;
- left = lt;
- right = rt;
- }
- T element; // The data in the node
- BinaryNode<T> left; // Left child
- BinaryNode<T> right; // Right child
- }
- // 測試
- public static void main( String [ ] args )
- {
- //創建二叉排序樹
- int list[]={ 50 , 30 , 70 , 10 , 40 , 90 , 80 };
- BinarySearchTree<Integer> bsTree = new BinarySearchTree<Integer>( );
- for ( int i = 0 ; i<list.length;i++)
- bsTree.insert( list[i] );
- System.out.println("中序遍歷的原始數據:" );
- //中序遍歷
- bsTree.printTree( );
- System.out.printf("\n--------------------------------" );
- //查找一個節點
- System.out.printf("\n10在二叉樹中是否包含:" + bsTree.contains( new Integer( 10 )));
- System.out.printf("\n---------------------------------" );
- //插入一個節點
- bsTree.insert(20 );
- System.out.printf("\n20插入到二叉樹,中序遍歷後:" );
- //中序遍歷
- bsTree.printTree();
- System.out.printf("\n-----------------------------------\n" );
- System.out.printf("刪除葉子節點 20, \n中序遍歷後:" );
- //刪除一個節點(葉子節點)
- bsTree.remove(new Integer( 20 ));
- //再次中序遍歷
- bsTree.printTree();
- System.out.printf("\n****************************************\n" );
- System.out.printf("刪除單孩子節點 90, \n中序遍歷後:" );
- //刪除單孩子節點
- bsTree.remove(new Integer( 90 ));
- //再次中序遍歷
- bsTree.printTree();
- System.out.printf("\n****************************************\n" );
- System.out.printf("刪除根節點 50, \n中序遍歷後:" );
- //刪除根節點
- bsTree.remove(new Integer( 50 ));
- bsTree.printTree();
- }
- }
- public class UnderflowException extends RuntimeException{}
運行結果:
D:\java>java BinarySearchTree
中序遍歷的原始數據:
10 30 40 50 70 80 90
------------------------------------------------
10在二叉樹中是否包含:true
--------------------------------------------------
20插入到二叉樹,中序遍歷後:10 20 30 40 50 70 80 90
-------------------------------------------------
刪除葉子節點 20,
中序遍歷後:10 30 40 50 70 80 90
*************************************************
刪除單孩子節點 90,
中序遍歷後:10 30 40 50 70 80
************************************************
刪除根節點 50,
中序遍歷後:10 30 40 70 80
值的注意的是:二叉排序樹同樣採用“空間換時間”的做法。
突然發現,二叉排序樹的中序遍歷同樣可以排序數組,呵呵,不錯!
PS: 插入操作:O(LogN)。
刪除操作:O(LogN)。
查找操作:O(LogN)。