Java簡(jiǎn)單幾步實(shí)現(xiàn)一個(gè)二叉搜索樹
1、認(rèn)識(shí)二叉搜索樹
從字面上來(lái)看,它只比二叉樹多了搜索兩個(gè)字,我們回想一下,如果要是在二叉樹中查找一個(gè)元素的話,需要遍歷這棵樹,效率很慢,而二叉搜索樹,則會(huì)效率高很多,為什么呢?
二叉搜索樹,可以是一棵空樹,或者是具有以下的性質(zhì):
- 若它的左子樹不為空,則左樹上所有的節(jié)點(diǎn)都小于根節(jié)點(diǎn)
- 若它的右子樹不為空,則右樹上所有節(jié)點(diǎn)的都大于根節(jié)點(diǎn)
- 它的左子樹和右子樹也分別為二叉搜索樹
通俗來(lái)講,左孩子都小于父節(jié)點(diǎn),右孩子都大于父節(jié)點(diǎn),以此類推,這里我們畫圖來(lái)認(rèn)識(shí)下二叉搜索樹:

當(dāng)然二叉搜索樹不要求是完全二叉樹或滿二叉樹,甚至?xí)霈F(xiàn)單分支的二叉搜索樹,所以針對(duì)這種特殊的情況進(jìn)行了優(yōu)化也就延申而來(lái)的 AVL樹,這個(gè)是后續(xù)的話題。
仔細(xì)觀察上圖,可以觀察出二叉搜索樹的一個(gè)新特性:
中序遍歷二叉搜索樹是有序的,所以二叉搜索樹也被稱為二叉排序樹。
2、實(shí)現(xiàn)一個(gè)二叉搜索樹
2.1 成員變量
public class BinarySearchTree {
private TreeNode root; //存放根節(jié)點(diǎn)
private static class TreeNode {
private int val;
private TreeNode left;
private TreeNode right;
private TreeNode(int val) {
this.val = val;
}
}
}這里跟我們的二叉樹成員變量大同小異,主要是去實(shí)現(xiàn)插入,查找,刪除的邏輯。
2.2 insert 方法
往二叉搜索樹插入一個(gè)節(jié)點(diǎn)的時(shí)候,我們要注意兩點(diǎn),首先如果二叉搜索樹為空,則直接令 root 為當(dāng)前插入的節(jié)點(diǎn)即可,那如果二叉搜索樹不為空,我們則需要利用二叉搜索樹的性質(zhì),找到該節(jié)點(diǎn)要插入的位置即可,具體我們來(lái)看下圖:

通過(guò)動(dòng)圖我們可以看到,當(dāng)二叉搜索樹不為空的時(shí)候,新的元素會(huì)依次節(jié)點(diǎn)比較,如果比根節(jié)點(diǎn)大,則去根的右邊,比根節(jié)點(diǎn)小,則取根的左邊,以此類推。(搜索二叉樹不存在相同的元素)
但是我們用代碼如何實(shí)現(xiàn)呢?定義一個(gè) cur 引用,當(dāng) cur 等于 null 了,則表示是我要插入的位置,既然找到了要插入的位置,但是還得知道這個(gè)位置的父節(jié)點(diǎn)是誰(shuí),通過(guò)父節(jié)點(diǎn)的指針域給連接起來(lái),于是代碼可以這樣寫:
public boolean insert(int key) {
// 二叉搜索樹沒有節(jié)點(diǎn)的情況
if (root == null) {
root = new TreeNode(key);
return true;
}
// 二叉搜索樹不為空的情況 -> 找到該節(jié)點(diǎn)要插入的位置進(jìn)行插入
// 如果已經(jīng)存在該節(jié)點(diǎn)了, 則不用插入 -> 二叉搜索樹中不能出現(xiàn)重復(fù)值
TreeNode parent = null; // 記錄cur的父節(jié)點(diǎn)
TreeNode cur = root;
while (cur != null) {
if (cur.val < key) {
parent = cur;
cur = cur.right;
} else if (cur.val > key) {
parent = cur;
cur = cur.left;
} else {
return false; // 插入重復(fù)的節(jié)點(diǎn)
}
}
// 走到這, cur為空了, key 需要插入到 parent 的左節(jié)點(diǎn)或右節(jié)點(diǎn)中
TreeNode newNode = new TreeNode(key);
if (parent.val < key) {
parent.right = newNode;
} else {
parent.left = newNode;
}
return true;
}2.3 search 方法
搜索方法,也就是給一個(gè) key 你,讓你在這顆二叉樹找有沒有這個(gè)元素,有的話返回該節(jié)點(diǎn),沒有的話返回 null,這個(gè)就很簡(jiǎn)單了,跟上面的步驟一樣無(wú)非就是碰到相同的元素返回 cur 嘛,當(dāng) cur 根據(jù) key 遍歷完這棵二叉搜索樹的時(shí)候,也就是 cur 為 null 了,則表示沒有該元素,直接返回 null即可。
代碼如下:
public TreeNode search(int key) {
TreeNode cur = root;
while (cur != null) {
if (cur.val < key) {
cur = cur.right;
} else if (cur.val > key) {
cur = cur.left;
} else {
return cur;
}
}
return null;
}2.4 remove 方法(重點(diǎn))
在二叉搜索樹中,刪除一個(gè)節(jié)點(diǎn)是一個(gè)比較麻煩的事,但是只要把各種刪除的情況下列舉出來(lái),一一解決它即可,對(duì)于二叉搜索樹來(lái)說(shuō),你刪除了一個(gè)節(jié)點(diǎn),它仍然滿足二叉搜索樹的性質(zhì)。
設(shè) cur 為要?jiǎng)h除的節(jié)點(diǎn),所以首先我們得判斷這個(gè)二叉搜索樹中,是否存在要?jiǎng)h除的節(jié)點(diǎn),這個(gè)邏輯上面已經(jīng)寫過(guò)了,找到要?jiǎng)h除的節(jié)點(diǎn)后,我們一共會(huì)面臨三種情況:
① 如果 cur 沒有左子樹的情況
- 如果 cur 是 root 的情況,只需要 root = cur.right
- 如果 cur 不是 root,cur 是 parent.left 的情況,只需要 parent.left = cur.right
- 如果 cur 不是 root,cur 是 parent.right 的情況,只需要 parent.right = cur.right
圖解:

② 如果 cur 沒有右子樹的情況
- 如果 cur 是 root 的情況,只需要 root = cur.left
- 如果 cur 不是 root,cur 是 parent.left 的情況,只需要 parent.left = cur.left
- 如果 cur 不是 root,cur 是 parent.right 的情況,只需要 parent.right = cur.left
圖解:

③ 如果 cur 既有左子樹,又有右子樹的情況
使用替換法進(jìn)行刪除,即在 cur 的右子樹中,一直往左尋找最小的元素,將這個(gè)最小值賦值給要?jiǎng)h除節(jié)點(diǎn)的 val 值中,接著把這個(gè)最小元素的節(jié)點(diǎn)刪除即可,刪除的邏輯見下圖和完整刪除代碼。


代碼如下:
public boolean remove(int key) {
TreeNode parent = null;
TreeNode cur = root;
while (cur != null) {
if (cur.val < key) {
parent = cur;
cur = cur.right;
} else if (cur.val > key) {
parent = cur;
cur = cur.left;
} else {
removeNode(parent, cur);
return true;
}
}
return false;
}
private void removeNode(TreeNode parent, TreeNode cur) {
if (cur.left == null) {
if (cur == root) {
root = cur.right;
} else if (cur == parent.left) {
parent.left = cur.right;
} else {
parent.right = cur.right;
}
} else if (cur.right == null) {
if (cur == root) {
root = cur.left;
} else if (cur == parent.left) {
parent.left = cur.left;
} else {
parent.right = cur.left;
}
} else {
TreeNode target = cur.right;
TreeNode targetParent = cur;
while (target.left != null) {
targetParent = target;
target = target.left;
}
// 走到這, target就是要?jiǎng)h除節(jié)點(diǎn)的右子樹中最小的節(jié)點(diǎn), 接下來(lái)進(jìn)行覆蓋
cur.val = target.val;
// 覆蓋完成, 現(xiàn)在需要?jiǎng)h除 target 節(jié)點(diǎn)
// 如果 cur.right 沒有左孩子的情況, 此時(shí)的target就是cur.right
// 即直接將 cur.right 覆蓋到 cur 位置, 也就是滿足 target == targetParent.right 條件
// 所以需要進(jìn)行特殊處理.
if (target == targetParent.right) {
targetParent.right = target.right;
} else {
targetParent.left = target.right;
}
}
}3、二叉搜索樹總結(jié)
二叉搜索樹在最好的情況下為完全二叉樹,查找的平均比較次數(shù)為:logn
二叉搜索樹在最差的情況下退化成但分支,查找的平均比較次數(shù)為:n/2
所以二叉搜索樹在最差的情況下效率是不高的,為了解決單分支的情況,于是有了 AVL樹,當(dāng)發(fā)現(xiàn)二叉搜索樹左右子樹高度差太大,會(huì)自動(dòng)旋轉(zhuǎn),以致平衡,避免旋轉(zhuǎn)的次數(shù)太多,又引入了紅黑樹,給節(jié)點(diǎn)增加了顏色,細(xì)節(jié)部分后期講解,這里有個(gè)概念即可,下期將會(huì)介紹由紅黑樹作為底層的集合:TreeSet 和 TreeMap
下期預(yù)告: 【Java 數(shù)據(jù)結(jié)構(gòu)】TreeSet 和 TreeMap
到此這篇關(guān)于Java簡(jiǎn)單幾步實(shí)現(xiàn)一個(gè)二叉搜索樹的文章就介紹到這了,更多相關(guān)Java二叉搜索樹內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Springboot2.3集成Spring security 框架(原生集成)
這篇文章主要介紹了詳解Springboot2.3集成Spring security 框架(原生集成),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
啟動(dòng)Springboot項(xiàng)目時(shí)找不到Mapper的問題及解決
這篇文章主要介紹了啟動(dòng)Springboot項(xiàng)目時(shí)找不到Mapper的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
Scala中Array和List的區(qū)別說(shuō)明
這篇文章主要介紹了Scala中Array和List的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Springboot?配置線程池創(chuàng)建線程及配置?@Async?異步操作線程池詳解
這篇文章主要介紹了Springboot?配置線程池創(chuàng)建線程及配置?@Async?異步操作線程池詳解,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
基于springcloud異步線程池、高并發(fā)請(qǐng)求feign的解決方案
這篇文章主要介紹了基于springcloud異步線程池、高并發(fā)請(qǐng)求feign的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
Springboot?通過(guò)FastJson實(shí)現(xiàn)bean對(duì)象和Json字符串互轉(zhuǎn)問題
這篇文章主要介紹了Springboot?通過(guò)FastJson實(shí)現(xiàn)bean對(duì)象和Json字符串互轉(zhuǎn),本文嘗試驗(yàn)證兩種場(chǎng)景給大家詳細(xì)介紹,對(duì)Springboot?FastJson實(shí)現(xiàn)bean和Json互轉(zhuǎn)問題,感興趣的朋友一起看看吧2022-08-08
Java實(shí)戰(zhàn)項(xiàng)目 醫(yī)院預(yù)約掛號(hào)系統(tǒng)
本文是一個(gè)Java語(yǔ)言編寫的實(shí)戰(zhàn)項(xiàng)目,是一個(gè)醫(yī)院預(yù)約掛號(hào)系統(tǒng),主要用到了jdbc+jsp+mysql+ajax等技術(shù),技術(shù)含量比較高,感興趣的童鞋跟著小編往下看吧2021-09-09
Spring boot中使用ElasticSearch的方法詳解
這篇文章主要給大家介紹了關(guān)于Spring boot中使用ElasticSearch的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01

