Java 實現(xiàn)二叉搜索樹的查找、插入、刪除、遍歷
由于最近想要閱讀下JDK1.8 中HashMap的具體實現(xiàn),但是由于HashMap的實現(xiàn)中用到了紅黑樹,所以我覺得有必要先復(fù)習(xí)下紅黑樹的相關(guān)知識,所以寫下這篇隨筆備忘,有不對的地方請指出~
學(xué)習(xí)紅黑樹,我覺得有必要從二叉搜索樹開始學(xué)起,本篇隨筆就主要介紹Java實現(xiàn)二叉搜索樹的查找、插入、刪除、遍歷等內(nèi)容。
二叉搜索樹需滿足以下四個條件:
若任意節(jié)點的左子樹不空,則左子樹上所有結(jié)點的值均小于它的根結(jié)點的值;
若任意節(jié)點的右子樹不空,則右子樹上所有結(jié)點的值均大于它的根結(jié)點的值;
任意節(jié)點的左、右子樹也分別為二叉查找樹;
沒有鍵值相等的節(jié)點。
二叉搜索樹舉例:

圖一
接下來將基于圖一介紹二叉搜索樹相關(guān)操作。
首先,應(yīng)先有一個節(jié)點對象相關(guān)的類,命名為 Node。
class Node {
int key;
int value;
Node leftChild;
Node rightChild;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
public void displayNode() {
}
}
Node 類中包含 key 值,用于確定節(jié)點在樹中相應(yīng)位置,value 值代表要存儲的內(nèi)容,還含有指向左右孩子節(jié)點的兩個引用。
接下來看下搜索樹相應(yīng)的類:
class Tree {
Node root;//保存樹的根
public Node find(int key) {//查找指定節(jié)點
}
public void insert(int key, int value) {//插入節(jié)點
}
public boolean delete(int key) {//刪除指定節(jié)點
}
private Node getDirectPostNode(Node delNode) {//得到待刪除節(jié)點的直接后繼節(jié)點
}
public void preOrder(Node rootNode) {//先序遍歷樹
}
public void inOrder(Node rootNode) {//中序遍歷樹
}
public void postOrder(Node rootNode) {//后序遍歷樹
}
}
類中表示樹的框架,包含查找、插入、遍歷、刪除相應(yīng)方法,其中刪除節(jié)點操作最為復(fù)雜,接下來一一介紹。
一、查找某個節(jié)點
由于二叉搜索樹定義上的特殊性,只需根據(jù)輸入的 key 值從根開始進(jìn)行比較,若小于根的 key 值,則與根的左子樹比較,大于根的key值與根的右子樹比較,以此類推,找到則返回相應(yīng)節(jié)點,否則返回 null。
public Node find(int key) {
Node currentNode = root;
while (currentNode != null && currentNode.key != key) {
if (key < currentNode.key) {
currentNode = currentNode.leftChild;
} else {
currentNode = currentNode.rightChild;
}
}
return currentNode;
}
二、插入節(jié)點
與查找操作相似,由于二叉搜索樹的特殊性,待插入的節(jié)點也需要從根節(jié)點開始進(jìn)行比較,小于根節(jié)點則與根節(jié)點左子樹比較,反之則與右子樹比較,直到左子樹為空或右子樹為空,則插入到相應(yīng)為空的位置,在比較的過程中要注意保存父節(jié)點的信息 及 待插入的位置是父節(jié)點的左子樹還是右子樹,才能插入到正確的位置。
public void insert(int key, int value) {
if (root == null) {
root = new Node(key, value);
return;
}
Node currentNode = root;
Node parentNode = root;
boolean isLeftChild = true;
while (currentNode != null) {
parentNode = currentNode;
if (key < currentNode.key) {
currentNode = currentNode.leftChild;
isLeftChild = true;
} else {
currentNode = currentNode.rightChild;
isLeftChild = false;
}
}
Node newNode = new Node(key, value);
if (isLeftChild) {
parentNode.leftChild = newNode;
} else {
parentNode.rightChild = newNode;
}
}
三、遍歷二叉搜索樹
遍歷操作與遍歷普通二叉樹操作完全相同,不贅述。
public void preOrder(Node rootNode) {
if (rootNode != null) {
System.out.println(rootNode.key + " " + rootNode.value);
preOrder(rootNode.leftChild);
preOrder(rootNode.rightChild);
}
}
public void inOrder(Node rootNode) {
if (rootNode != null) {
inOrder(rootNode.leftChild);
System.out.println(rootNode.key + " " + rootNode.value);
inOrder(rootNode.rightChild);
}
}
public void postOrder(Node rootNode) {
if (rootNode != null) {
postOrder(rootNode.leftChild);
postOrder(rootNode.rightChild);
System.out.println(rootNode.key + " " + rootNode.value);
}
}
四、刪除指定節(jié)點。
在二叉搜索樹中刪除節(jié)點操作較復(fù)雜,可分為以下三種情況。
1、待刪除的節(jié)點為葉子節(jié)點,可直接刪除。
public boolean delete(int key) {
Node currentNode = root;//用來保存待刪除節(jié)點
Node parentNode = root;//用來保存待刪除節(jié)點的父親節(jié)點
boolean isLeftChild = true;//用來確定待刪除節(jié)點是父親節(jié)點的左孩子還是右孩子
while (currentNode != null && currentNode.key != key) {
parentNode = currentNode;
if (key < currentNode.key) {
currentNode = currentNode.leftChild;
isLeftChild = true;
} else {
currentNode = currentNode.rightChild;
isLeftChild = false;
}
}
if (currentNode == null) {
return false;
}
if (currentNode.leftChild == null && currentNode.rightChild == null) {//要刪除的節(jié)點為葉子節(jié)點
if (currentNode == root)
root = null;
else if (isLeftChild)
parentNode.leftChild = null;
else
parentNode.rightChild = null;
}
......
}
2、待刪除節(jié)點只有一個孩子節(jié)點
例如刪除圖一中的 key 值為 11 的節(jié)點,只需將 key 值為 13 的節(jié)點的左孩子指向 key 值為 12的節(jié)點即可達(dá)到刪除 key 值為 11 的節(jié)點的目的。
由以上分析可得代碼如下(接上述 delete 方法省略號后):
else if (currentNode.rightChild == null) {//要刪除的節(jié)點只有左孩子
if (currentNode == root)
root = currentNode.leftChild;
else if (isLeftChild)
parentNode.leftChild = currentNode.leftChild;
else
parentNode.rightChild = currentNode.leftChild;
} else if (currentNode.leftChild == null) {//要刪除的節(jié)點只有右孩子
if (currentNode == root)
root = currentNode.rightChild;
else if (isLeftChild)
parentNode.leftChild = currentNode.rightChild;
else
parentNode.rightChild = currentNode.rightChild;
}
......
3、待刪除節(jié)點既有左孩子,又有右孩子。
例如刪除圖一中 key 值為 10 的節(jié)點,這時就需要用 key 值為 10 的節(jié)點的中序后繼節(jié)點(節(jié)點 11)來代替 key 值為 10 的節(jié)點,并刪除 key 值為 10 的節(jié)點的中序后繼節(jié)點,由中序遍歷相關(guān)規(guī)則可知, key 值為 10 的節(jié)點的直接中序后繼節(jié)點一定是其右子樹中 key 值最小的節(jié)點,所以此中序后繼節(jié)點一定不含子節(jié)點或者只含有一個右孩子,刪除此中序后繼節(jié)點就屬于上述 1,2 所述情況。圖一中 key 值為 10 的節(jié)點的直接中序后繼節(jié)點 為 11,節(jié)點 11 含有一個右孩子 12。
所以刪除 圖一中 key 值為 10 的節(jié)點分為以下幾步:
a、找到 key 值為 10 的節(jié)點的直接中序后繼節(jié)點(即其右子樹中值最小的節(jié)點 11),并刪除此直接中序后繼節(jié)點。
private Node getDirectPostNode(Node delNode) {//方法作用為得到待刪除節(jié)點的直接后繼節(jié)點
Node parentNode = delNode;//用來保存待刪除節(jié)點的直接后繼節(jié)點的父親節(jié)點
Node direcrPostNode = delNode;//用來保存待刪除節(jié)點的直接后繼節(jié)點
Node currentNode = delNode.rightChild;
while (currentNode != null) {
parentNode = direcrPostNode;
direcrPostNode = currentNode;
currentNode = currentNode.leftChild;
}
if (direcrPostNode != delNode.rightChild) {//從樹中刪除此直接后繼節(jié)點
parentNode.leftChild = direcrPostNode.rightChild;
direcrPostNode.rightChild = null;
}
return direcrPostNode;//返回此直接后繼節(jié)點
}
b、將此后繼節(jié)點的 key、value 值賦給待刪除節(jié)點的 key,value值。(接情況二中省略號代碼之后)
else { //要刪除的節(jié)點既有左孩子又有右孩子
//思路:用待刪除節(jié)點右子樹中的key值最小節(jié)點的值來替代要刪除的節(jié)點的值,然后刪除右子樹中key值最小的節(jié)點
//右子樹key最小的節(jié)點一定不含左子樹,所以刪除這個key最小的節(jié)點一定是屬于葉子節(jié)點或者只有右子樹的節(jié)點
Node directPostNode = getDirectPostNode(currentNode);
currentNode.key = directPostNode.key;
currentNode.value = directPostNode.value;
}
至此刪除指定節(jié)點的操作結(jié)束。
最后給出完整代碼及簡單測試代碼及測試結(jié)果:
class Node {
int key;
int value;
Node leftChild;
Node rightChild;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
public void displayNode() {
}
}
class Tree {
Node root;
public Node find(int key) {
Node currentNode = root;
while (currentNode != null && currentNode.key != key) {
if (key < currentNode.key) {
currentNode = currentNode.leftChild;
} else {
currentNode = currentNode.rightChild;
}
}
return currentNode;
}
public void insert(int key, int value) {
if (root == null) {
root = new Node(key, value);
return;
}
Node currentNode = root;
Node parentNode = root;
boolean isLeftChild = true;
while (currentNode != null) {
parentNode = currentNode;
if (key < currentNode.key) {
currentNode = currentNode.leftChild;
isLeftChild = true;
} else {
currentNode = currentNode.rightChild;
isLeftChild = false;
}
}
Node newNode = new Node(key, value);
if (isLeftChild) {
parentNode.leftChild = newNode;
} else {
parentNode.rightChild = newNode;
}
}
public boolean delete(int key) {
Node currentNode = root;
Node parentNode = root;
boolean isLeftChild = true;
while (currentNode != null && currentNode.key != key) {
parentNode = currentNode;
if (key < currentNode.key) {
currentNode = currentNode.leftChild;
isLeftChild = true;
} else {
currentNode = currentNode.rightChild;
isLeftChild = false;
}
}
if (currentNode == null) {
return false;
}
if (currentNode.leftChild == null && currentNode.rightChild == null) {
//要刪除的節(jié)點為葉子節(jié)點
if (currentNode == root)
root = null;
else if (isLeftChild)
parentNode.leftChild = null;
else
parentNode.rightChild = null;
} else if (currentNode.rightChild == null) {//要刪除的節(jié)點只有左孩子
if (currentNode == root)
root = currentNode.leftChild;
else if (isLeftChild)
parentNode.leftChild = currentNode.leftChild;
else
parentNode.rightChild = currentNode.leftChild;
} else if (currentNode.leftChild == null) {//要刪除的節(jié)點只有右孩子
if (currentNode == root)
root = currentNode.rightChild;
else if (isLeftChild)
parentNode.leftChild = currentNode.rightChild;
else
parentNode.rightChild = currentNode.rightChild;
} else { //要刪除的節(jié)點既有左孩子又有右孩子
//思路:用待刪除節(jié)點右子樹中的key值最小節(jié)點的值來替代要刪除的節(jié)點的值,然后刪除右子樹中key值最小的節(jié)點
//右子樹key最小的節(jié)點一定不含左子樹,所以刪除這個key最小的節(jié)點一定是屬于葉子節(jié)點或者只有右子樹的節(jié)點
Node directPostNode = getDirectPostNode(currentNode);
currentNode.key = directPostNode.key;
currentNode.value = directPostNode.value;
}
return true;
}
private Node getDirectPostNode(Node delNode) {//方法作用為得到待刪除節(jié)點的直接后繼節(jié)點
Node parentNode = delNode;//用來保存待刪除節(jié)點的直接后繼節(jié)點的父親節(jié)點
Node direcrPostNode = delNode;//用來保存待刪除節(jié)點的直接后繼節(jié)點
Node currentNode = delNode.rightChild;
while (currentNode != null) {
parentNode = direcrPostNode;
direcrPostNode = currentNode;
currentNode = currentNode.leftChild;
}
if (direcrPostNode != delNode.rightChild) {//從樹中刪除此直接后繼節(jié)點
parentNode.leftChild = direcrPostNode.rightChild;
direcrPostNode.rightChild = null;
}
return direcrPostNode;//返回此直接后繼節(jié)點
}
public void preOrder(Node rootNode) {
if (rootNode != null) {
System.out.println(rootNode.key + " " + rootNode.value);
preOrder(rootNode.leftChild);
preOrder(rootNode.rightChild);
}
}
public void inOrder(Node rootNode) {
if (rootNode != null) {
inOrder(rootNode.leftChild);
System.out.println("key: " + rootNode.key + " " + "value: " + rootNode.value);
inOrder(rootNode.rightChild);
}
}
public void postOrder(Node rootNode) {
if (rootNode != null) {
postOrder(rootNode.leftChild);
postOrder(rootNode.rightChild);
System.out.println(rootNode.key + " " + rootNode.value);
}
}
}
public class BinarySearchTreeApp {
public static void main(String[] args) {
Tree tree = new Tree();
tree.insert(6, 6);//插入操作,構(gòu)造圖一所示的二叉樹
tree.insert(3, 3);
tree.insert(14, 14);
tree.insert(16, 16);
tree.insert(10, 10);
tree.insert(9, 9);
tree.insert(13, 13);
tree.insert(11, 11);
tree.insert(12, 12);
System.out.println("刪除前遍歷結(jié)果");
tree.inOrder(tree.root);//中序遍歷操作
System.out.println("刪除節(jié)點10之后遍歷結(jié)果");
tree.delete(10);//刪除操作
tree.inOrder(tree.root);
}
}
測試結(jié)果:

以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
相關(guān)文章
淺談Spring Security 對于靜態(tài)資源的攔截與放行
這篇文章主要介紹了淺談Spring Security 對于靜態(tài)資源的攔截與放行,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
java中獲取xml文件的某個配置節(jié)點內(nèi)容方式
這篇文章主要介紹了java中獲取xml文件的某個配置節(jié)點內(nèi)容方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
Java實現(xiàn)Dbhelper支持大數(shù)據(jù)增刪改
這篇文章主要介紹了Java實現(xiàn)Dbhelper支持大數(shù)據(jù)增刪改功能的實現(xiàn)過程,感興趣的小伙伴們可以參考一下2016-01-01
Spring之底層架構(gòu)核心概念Environment及用法詳解
這篇文章主要介紹了Spring之底層架構(gòu)核心概念-Environment,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12
基于Protobuf動態(tài)解析在Java中的應(yīng)用 包含例子程序
下面小編就為大家?guī)硪黄赑rotobuf動態(tài)解析在Java中的應(yīng)用 包含例子程序。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07

