二分搜索樹是一顆二叉樹
- 每個節點的鍵值大於左孩子
- 每個節點的鍵值小於右孩子
- 一左右孩子爲根的子樹仍爲二分搜索數
下圖爲二分搜索樹,左邊爲完全二叉樹
堆是一個完全二叉樹,可以用數組表示
二分搜索樹不一定是完全二叉樹,數組表示不方便,設置node節點,來表示這些數據對
1、構建二分搜索樹
public class BST<Key extends Comparable<Key>, Value> {
// 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
private class Node {
private Key key;
private Value value;
//左右子樹
private Node left, right;
public Node(Key key, Value value) {
this.key = key;
this.value = value;
left = right = null;
}
}
private Node root; // 根節點
private int count; // 樹種的節點個數
// 構造函數, 默認構造一棵空二分搜索樹
public BST() {
root = null;
count = 0;
}
// 返回二分搜索樹的節點個數
public int size() {
return count;
}
// 返回二分搜索樹是否爲空
public boolean isEmpty() {
return count == 0;
}
}
2、插入元素
插入60,先跟41 作比較,比41要大,插入到41的右側,
60比58還要大,插到58的右子樹中。
插入相同的元素,用的新的數據覆蓋舊的數據
// 向二分搜索樹中插入一個新的(key, value)數據對
public void insert(Key key, Value value){
root = insert(root, key, value);
}
// 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
// 返回插入新節點後的二分搜索樹的根
private Node insert(Node node, Key key, Value value){
if( node == null ){
count ++;
return new Node(key, value);
}
//這個key是否等於當前傳過來node的key,更新
//這個value換成新傳過來的value
if( key.compareTo(node.key) == 0 )
node.value = value;
//否則就是這個key小於node的key,
//向node的左子樹傳入key,value數據對,返回數據是返回給node的左子樹
else if( key.compareTo(node.key) < 0 )
node.left = insert( node.left , key, value);
else // key > node->key
node.right = insert( node.right, key, value);
return node;
}
3、查找元素
查找鍵key所對應的值,
其實要是比根節點小,去左子樹繼續找,遞歸下去
要是比根節點大,就去右子數找,直到直到爲止。
// 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
public Value search(Key key){
return search( root , key );
}
// 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
// 若value不存在, 則返回NULL
private Value search(Node node, Key key){
if( node == null )
return null;
//看着key是否是需要找的key值
//如果相等就返回
if( key.compareTo(node.key) == 0 )
return node.value;
//否則要是key值比該根節點的key要小,去它的左子樹繼續尋找
else if( key.compareTo(node.key) < 0 )
return search( node.left , key );
//否則要是key值比該根節點的key要大,去它的右子樹繼續尋找
//直到找到爲止
else // key > node->key
return search( node.right, key );
}
4、刪除最大、最小元素
刪除最大值、最小值
要刪除就要先找到最大值和最小值
找最大值,就是二分搜索樹的最左邊的孩子節點,如果左子樹沒有值,就是它的根節點
// 尋找二分搜索樹的最大的鍵值
public Key minimum(){
assert count != 0;
Node maxNode = minimum(root);
return minimum.key;
}
// 返回以node爲根的二分搜索樹的最小鍵值所在的節點
private Node minimum(Node node){
//就是這個node的左孩子爲空,就是最小值了
if( node.left == null )
return node;
//否則就返回回去,在在這個二叉樹的左孩子繼續找
return minimum(node.left);
}
找最小值,就是最右邊的子樹的節點,如果右子樹沒有值,就是它的根節點
下面開始刪除最小值
最小值22,22爲根節點,它沒有左子樹了,所以它就是最小值
將22拿出去,將22的右子樹作爲22位置的根節點,就是33作爲根節點,同時維護空間,索引減1
刪除最大值也是同理
// 從二分搜索樹中刪除最小值所在節點
public void removeMin(){
if( root != null )
root = removeMin( root );
}
// 刪除掉以node爲根的二分搜索樹中的最小節點
// 返回刪除節點後新的二分搜索樹的根
private Node removeMin(Node node){
//看當前的節點的左孩子是否爲空
//就找到最小的節點,就是node節點,然後刪除掉
if( node.left == null ){
//看下這個node右子樹節點是不是存在,如果存在
//就要代替這個node稱爲新的二分搜索樹的根
//就是成爲node節點的父親節點新的左孩子
Node rightNode = node.right;
//維護空間,釋放空間
node.right = null;
count --;
return rightNode;
}
//如果不成立,就需要繼續向當前的 node爲根的左孩子二分搜索樹中
//去查找最小的節點 ,將他刪除,刪除節點在賦值返回給node的左孩子
node.left = removeMin(node.left);
return node;
}
同理刪除最大值
// 從二分搜索樹中刪除最大值所在節點
public void removeMax(){
if( root != null )
root = removeMax( root );
}
// 刪除掉以node爲根的二分搜索樹中的最大節點
// 返回刪除節點後新的二分搜索樹的根
private Node removeMax(Node node){
if( node.right == null ){
Node leftNode = node.left;
node.left = null;
count --;
return leftNode;
}
node.right = removeMax(node.right);
return node;
}
5、刪除任意節點元素
下面刪除二叉樹種任意節點的元素
比如刪除58的節點d
因爲58左右都有孩子節點,
- 那麼就要找一個數值代替58,這個值是58的右子樹中最小值s爲59,這樣就可以變爲d的後續替換元素,因爲要維護二分搜索樹的性質
- 下面就是把59這樣元素從這個節點s上面刪除掉
- 將59變成原來d
- 將s節點的右邊指向原來d的右子樹,這裏爲什麼不用d來指向右子樹,因爲右孩子的值不全了
- 最後在維護左孩子,s節點的左孩子是原來d的右孩子
然後將d徹底刪除掉,s節點59稱爲新的子樹的根節點
// 從二分搜索樹中刪除鍵值爲key的節點
public void remove(Key key){
root = remove(root, key);
}
// 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
private class Node {
private Key key;
private Value value;
private Node left, right;
public Node(Key key, Value value) {
this.key = key;
this.value = value;
left = right = null;
}
//將小的賦值的node節點都賦值新的node節點
public Node(Node node){
this.key = node.key;
this.value = node.value;
this.left = node.left;
this.right = node.right;
}
}
// 刪除掉以node爲根的二分搜索樹中鍵值爲key的節點, 遞歸算法
// 返回刪除節點後新的二分搜索樹的根
Node remove(Node node, Key key){
//如果這個鍵值的節點爲空,說明沒有這個鍵值的節點就返回回去
if( node == null )
return null;
//否則就是要找到的key與當前的node中的key作比較
if( key.compareTo(node.key) < 0 ){
//小於這個key,就在node的左孩子中找到這個key,
//並把結果賦值返回node左孩子
node.left = remove( node.left , key );
return node;
}
else if( key.compareTo(node.key) > 0 ){
node.right = remove( node.right, key );
return node;
}
//上面兩種都沒有存在,
//就要看這個node有幾個孩子
else{ // key == node->key
// 待刪除節點左子樹爲空的情況
//還有一種左右孩子都爲空,那就進入左子樹爲空的情況
//如果左孩子爲空的話
//就相當於回到刪除最小值,這就爲什麼要提上面要刪除最小值的原由
//就找到最小的節點,就是node節點,然後刪除掉
if( node.left == null ){
//看下這個node右子樹節點是不是存在,如果存在
//就要代替這個node稱爲新的二分搜索樹的根
//就是成爲node節點的父親節點新的左孩子
Node rightNode = node.right;
node.right = null;
count --;
return rightNode;
}
// 待刪除節點右子樹爲空的情況
//就相當於刪除最大值
//同理如上
if( node.right == null ){
Node leftNode = node.left;
node.left = null;
count--;
return leftNode;
}
// 待刪除節點左右子樹均不爲空的情況
// 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
// 用這個節點頂替待刪除節點的位置
//把要刪除的node保存下路,要找到一個節點,是這個node節點的後繼
//這裏就可以重新建立一個node
Node successor = new Node(minimum(node.right));
//這個元素要放入二叉樹中維護count
count ++;
//爲這個後繼 左右孩子賦值
//右孩子就是返回來的指針
//下面調用這個賦值的時候count--也,這個count並沒有改變
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
count --;
//這個successo返回回去作爲新的二分搜索的根節點
return successor;
}
}