實現二叉搜尋樹的新增,查詢和刪除(JAVA)
1.二叉搜尋樹
1.1 概念
二叉搜尋樹又稱為二叉排序樹,它是一顆空樹或者具有以下性質的樹:
- 若它的左子樹不為空,則左子樹上所有節點的值都小於根節點的值
- 若它的右子樹不為空,則右子樹上所有節點的值都大於根節點的值
- 它的左右子樹也分別為二叉搜尋樹
例如對上圖二叉樹採用中序遍歷,得到的結果為:{0,1,2,3,4,5,6,7,8,9}
即一顆二叉搜尋樹的中序遍歷一定是有序的,且是從小到大進行排列
2.二叉搜尋樹的操作
建立節點,這裡使用靜態內部類來建立
程式碼示例:
calss BinarySearchTree {
static class Node {
public int val;
public Node left;
public Node right;
public Node(int val) {
this.val = val;
}
}
//定義根節點為外部類的成員變數
Node root = null;
}
2.1 插入
定義插入的值為val,則插入節點要考慮兩種情況:
-
樹為空樹,即root == null,直接插入即可
-
樹不為空樹,則先比較root的值和val大小關係,如果小於插入值,則說明去root的右邊尋找;如果大於val,則說明去root的左邊尋找;等於說明找到了,拼接即可
程式碼示例:
//插入節點
public void put(int val) {
Node node = new Node(val);
//第一次插入,root == null
if(root == null) {
root = node;
return;
}
//不是第一次插入 root != null
Node pre = null;//用來指向cur的父親節點
Node cur = root;
while (cur != null) {
//去右邊找
if (cur.val < val) {
pre = cur;//記錄下此時cur
cur = pre.right;//cur向右節點移動
}else if (cur.val > val) {//該去左邊找
pre = cur;
cur = pre.left;
}else {//已經有相等的值,則不做任何處理,直接退出
return;
}
}
//此時cur = null,pre = cur的根節點
//判斷當前val和pre的val大小,決定插入左邊還是右邊
if (pre.val < val) {//插入到右節點
pre.right = node;
}else {//插入到左節點
pre.left = node;
}
}
2.2 查詢
查詢的大體思路跟插入差不多,也是從根節點的值開始比較,大的就往右邊找,小的往左邊找,直到找到或者找不到,也就是說迴圈退出會有兩種結果,一種是找到節點並返回,一種是沒找到返回null
程式碼示例:
public Node get(int val) {
Node cur = root;
while (cur != null) {
//找到該節點
if (cur.val == val) {
return cur;
}else if (cur.val < val) {//去右邊找
cur = cur.right;
}else {//去左邊找
cur = cur.left;
}
}
//說明沒找到
return null;
}
2.3 刪除(難點)
對於二叉搜尋樹來說,插入和查詢都很方便,因為這兩個操作只有插入會用到父親節點,而父親節點自上而下遍歷很容易尋找;但是對於刪除操作來說,我們要找到當前需要刪除節點的下一個節點,並且讓這個節點覆蓋掉需要刪除的節點,這樣才能完成刪除操作,當然這是大體上的思路
設當前節點為cur,當前節點的父親節點為pre,我們需要考慮一下3種情況
- cur.left == null
① cur == root,則 root = cur.right
② cur == pre.left,則 pre.left = cur.right
③ cur == pre.right,則 pre.right = cur.right - cur.right == null
① cur == root,則 root = cur.left
② cur == pre.left,則 pre.left = cur.left
③ cur == pre.right,則 pre.right = cur.left - cur.left != null && cur.right == null
① 找到cur的下一個節點 minCur
② cur = minCur 覆蓋掉cur
③ 再覆蓋掉之前的minCur,因此還需要定義一個minCur的根節點minPre
程式碼示例:
public boolean remove(int val) {
Node cur = root;//當前結點
Node pre = null;//當前節點的根節點
//先找到要刪除的節點
while (cur != null) {
if (cur.val == val) {
break;
}else if (cur.val < val) {
pre = cur;
cur = pre.right;
}else {
pre = cur;
cur = pre.left;
}
}
//判斷是否找到
//cur為null,說明沒有找到
if (cur == null) return false;
//說明找到了cur,開始判斷cur的位置
if (cur.left == null) {
if (cur == root) {//是根節點
root = cur.right;//替換
}else {//不是根節點
if (cur == pre.left) {
pre.left = cur.right;
}else {
pre.right = cur.right;
}
}
}else if (cur.right == null) {
if (cur == root) {
root = cur.left;
}else {
if (cur == pre.left) {
pre.left = cur.left;
}else {
pre.right = cur.left;
}
}
}else {//cur的左右節點都不為null
Node minPre = cur;//指向cur下一節點的根節點
Node minCur = cur.right;//cur的下一個節點
while (minCur.left != null) {
minPre = minCur;
minCur = minPre.left;
}
cur.val = minCur.val;//先覆蓋掉原來節點
//判斷下一節點在根節點的哪一邊
if (minCur == minPre.left) {
minPre.left = minCur.right;//再覆蓋掉原來的下一節點
}else {
minPre.right = minCur.right;
}
}
return true;
}
2.4 演示
將剛剛的程式碼加上中序遍歷整合到一起,然後給定一組測試資料來測試程式碼
程式碼示例:
public class HomeWork {
public static void main(String[] args) {
int[] arr = {7,5,45,9,26,41,2,6};
BinarySearchTree binarySearchTree = new BinarySearchTree();
for (int i = 0; i < arr.length; i++) {
binarySearchTree.put(arr[i]);
}
binarySearchTree.inOrder(binarySearchTree.root);
System.out.println();
binarySearchTree.preOrder(binarySearchTree.root);
System.out.println();
try {
System.out.println(binarySearchTree.get(45).val);
}catch (NullPointerException e) {
e.printStackTrace();
System.out.println("沒有該節點");
}
System.out.println(binarySearchTree.remove(45));
binarySearchTree.inOrder(binarySearchTree.root);
}
}
class BinarySearchTree {
static class Node {
public int val;
public Node left;
public Node right;
public Node(int val) {
this.val = val;
}
}
Node root = null;
public void put(int val) {
Node node = new Node(val);
//第一次插入,root == null
if(root == null) {
root = node;
return;
}
//不是第一次插入 root != null
Node pre = null;//用來指向cur的父親節點
Node cur = root;
while (cur != null) {
//去右邊找
if (cur.val < val) {
pre = cur;//記錄下此時cur
cur = pre.right;//cur向右節點移動
}else if (cur.val > val) {//該去左邊找
pre = cur;
cur = pre.left;
}else {//已經有相等的值,則不做任何處理,直接退出
return;
}
}
//此時cur = null,pre = cur的根節點
//判斷當前val和pre的val大小,決定插入左邊還是右邊
if (pre.val < val) {//插入到右節點
pre.right = node;
}else {//插入到左節點
pre.left = node;
}
}
public Node get(int val) {
Node cur = root;
while (cur != null) {
//找到該節點
if (cur.val == val) {
return cur;
}else if (cur.val < val) {//去右邊找
cur = cur.right;
}else {//去左邊找
cur = cur.left;
}
}
//說明沒找到
return null;
}
//刪除
public boolean remove(int val) {
Node cur = root;//當前結點
Node pre = null;//當前節點的根節點
//先找到要刪除的節點
while (cur != null) {
if (cur.val == val) {
break;
}else if (cur.val < val) {
pre = cur;
cur = pre.right;
}else {
pre = cur;
cur = pre.left;
}
}
//判斷是否找到
//cur為null,說明沒有找到
if (cur == null) return false;
//說明找到了cur,開始判斷cur的位置
if (cur.left == null) {
if (cur == root) {//是根節點
root = cur.right;//替換
}else {//不是根節點
if (cur == pre.left) {
pre.left = cur.right;
}else {
pre.right = cur.right;
}
}
}else if (cur.right == null) {
if (cur == root) {
root = cur.left;
}else {
if (cur == pre.left) {
pre.left = cur.left;
}else {
pre.right = cur.left;
}
}
}else {//cur的左右節點都不為null
Node minPre = cur;//指向cur下一節點的根節點
Node minCur = cur.right;//cur的下一個節點
while (minCur.left != null) {
minPre = minCur;
minCur = minPre.left;
}
cur.val = minCur.val;//先覆蓋掉原來節點
//判斷下一節點在根節點的哪一邊
if (minCur == minPre.left) {
minPre.left = minCur.right;//再覆蓋掉原來的下一節點
}else {
minPre.right = minCur.right;
}
}
return true;
}
//中序遍歷
public void inOrder(Node root) {
if (root == null) return;
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
//前序遍歷
public void preOrder(Node root) {
if (root == null) return;
System.out.print(root.val + " ");
preOrder(root.left);
preOrder(root.right);
}
}
結果演示
2 5 6 7 9 26 41 45
7 5 2 6 45 9 26 41
45
true
2 5 6 7 9 26 41
以上就是二叉搜尋樹的插入,查詢和刪除,此部落格最想讓大家掌握的是二叉搜尋樹的刪除操作,希望各位小夥伴都能多多練習!
2.5 效能分析
插入和刪除操作都必須先查詢,查詢效率代表了二叉搜尋樹中各個操作的效能
對有n個結點的二叉搜尋樹,若每個元素查詢的概率相等,則二叉搜尋樹平均查詢長度是結點在二叉搜尋樹的深度的函式,即結點越深,則比較次數越多
但對於同一個關鍵碼集合,如果各關鍵碼插入的次序不同,可能得到不同結構的二叉搜尋樹:
最優情況下,二叉搜尋樹為完全二叉樹,其平均比較次數為:logN
最差情況下,二叉搜尋樹退化為單支樹,其平均比較次數為:2/N
問題:如果退化成單支樹,二叉搜尋樹的效能就失去了。那能否進行改進,不論按照什麼次序插入關鍵碼,都可以使二叉搜尋樹的效能最佳?關於這個改進我將再寫一篇部落格來講解
相關文章
- Java實現二叉搜尋樹的插入、刪除Java
- 二叉查詢樹的插入刪除查詢
- 二叉搜尋樹如何刪除節點
- 二叉樹的插入和搜尋–python實現二叉樹Python
- 二叉查詢樹(查詢、插入、刪除)——C語言C語言
- 如何在 Java 中實現二叉搜尋樹Java
- 二叉搜尋樹(Binary Search Tree)(Java實現)Java
- 二叉搜尋樹的python實現Python
- javascript實現二叉搜尋樹JavaScript
- 智慧指標和二叉樹(3):圖解查詢和刪除指標二叉樹圖解
- Java中在二叉搜尋樹中查詢節點的父節點Java
- 滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹(二叉查詢樹)和最優二叉樹二叉樹
- js實現完全排序二叉樹、二叉搜尋樹JS排序二叉樹
- 資料結構之二叉搜尋樹—Java實現資料結構Java
- JavaScript 二叉搜尋樹以及實現翻轉二叉樹JavaScript二叉樹
- Python實現二叉樹的增、刪、查Python二叉樹
- 如何在Java中實現二叉搜尋樹( binary search tree)?Java
- 資料結構-二叉搜尋樹的實現資料結構
- 二叉搜尋樹和二叉樹的最近公共祖先二叉樹
- Java-二叉排序樹的刪除Java排序
- 二叉查詢樹概念及實現
- 用Python實現二叉樹的增、刪、查Python二叉樹
- 有序表和搜尋二叉樹二叉樹
- 二叉搜尋樹
- 程式碼隨想錄演算法訓練營第22天 |二叉樹part07:235. 二叉搜尋樹的最近公共祖先、701.二叉搜尋樹中的插入操作、450.刪除二叉搜尋樹中的節點演算法二叉樹
- JavaScript實現簡單二叉查詢樹JavaScript
- 二叉樹排序樹的建立,遍歷和刪除二叉樹排序
- 資料結構和演算法-Go實現二叉搜尋樹資料結構演算法Go
- 搜尋/查詢
- 程式碼隨想錄演算法訓練營第19天|235. 二叉搜尋樹的最近公共祖先 ,701.二叉搜尋樹中的插入操作,450.刪除二叉搜尋樹中的節點演算法
- 程式碼隨想錄演算法訓練營day19| 235. 二叉搜尋樹的最近公共祖先 701.二叉搜尋樹中的插入操作 450.刪除二叉搜尋樹中的節點演算法
- 程式碼隨想錄演算法訓練營day22 | leetcode 235. 二叉搜尋樹的最近公共祖先、701. 二叉搜尋樹中的插入操作、450. 刪除二叉搜尋樹中的節點演算法LeetCode
- 938-二叉搜尋樹的範圍和
- 第 34 題:如何實現二叉查詢樹?
- 二叉搜尋樹的結構
- 二叉搜尋樹的操作集
- Day20 | 654.最大二叉樹 、 617.合併二叉樹 、 700.二叉搜尋樹中的搜尋 98.驗證二叉搜尋樹二叉樹
- 從二叉查詢樹到B*樹,一文搞懂搜尋樹的演進!|金三銀四系列