這篇文章將為大家詳細(xì)講解有關(guān)Java 如何利用二叉搜索樹(shù)實(shí)現(xiàn)查找、插入、刪除、遍歷,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
創(chuàng)新互聯(lián)是一家專業(yè)提供莘縣企業(yè)網(wǎng)站建設(shè),專注與做網(wǎng)站、成都網(wǎng)站制作、HTML5建站、小程序制作等業(yè)務(wù)。10年已為莘縣眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設(shè)計(jì)公司優(yōu)惠進(jìn)行中。
由于最近想要閱讀下JDK1.8 中HashMap的具體實(shí)現(xiàn),但是由于HashMap的實(shí)現(xiàn)中用到了紅黑樹(shù),所以我覺(jué)得有必要先復(fù)習(xí)下紅黑樹(shù)的相關(guān)知識(shí),所以寫下這篇隨筆備忘,有不對(duì)的地方請(qǐng)指出~
學(xué)習(xí)紅黑樹(shù),我覺(jué)得有必要從二叉搜索樹(shù)開(kāi)始學(xué)起,本篇隨筆就主要介紹Java實(shí)現(xiàn)二叉搜索樹(shù)的查找、插入、刪除、遍歷等內(nèi)容。
二叉搜索樹(shù)需滿足以下四個(gè)條件:
若任意節(jié)點(diǎn)的左子樹(shù)不空,則左子樹(shù)上所有結(jié)點(diǎn)的值均小于它的根結(jié)點(diǎn)的值;
若任意節(jié)點(diǎn)的右子樹(shù)不空,則右子樹(shù)上所有結(jié)點(diǎn)的值均大于它的根結(jié)點(diǎn)的值;
任意節(jié)點(diǎn)的左、右子樹(shù)也分別為二叉查找樹(shù);
沒(méi)有鍵值相等的節(jié)點(diǎn)。
二叉搜索樹(shù)舉例:
圖一
接下來(lái)將基于圖一介紹二叉搜索樹(shù)相關(guān)操作。
首先,應(yīng)先有一個(gè)節(jié)點(diǎn)對(duì)象相關(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é)點(diǎn)在樹(shù)中相應(yīng)位置,value 值代表要存儲(chǔ)的內(nèi)容,還含有指向左右孩子節(jié)點(diǎn)的兩個(gè)引用。
接下來(lái)看下搜索樹(shù)相應(yīng)的類:
class Tree { Node root;//保存樹(shù)的根 public Node find(int key) {//查找指定節(jié)點(diǎn) } public void insert(int key, int value) {//插入節(jié)點(diǎn) } public boolean delete(int key) {//刪除指定節(jié)點(diǎn) } private Node getDirectPostNode(Node delNode) {//得到待刪除節(jié)點(diǎn)的直接后繼節(jié)點(diǎn) } public void preOrder(Node rootNode) {//先序遍歷樹(shù) } public void inOrder(Node rootNode) {//中序遍歷樹(shù) } public void postOrder(Node rootNode) {//后序遍歷樹(shù) } }
類中表示樹(shù)的框架,包含查找、插入、遍歷、刪除相應(yīng)方法,其中刪除節(jié)點(diǎn)操作最為復(fù)雜,接下來(lái)一一介紹。
一、查找某個(gè)節(jié)點(diǎn)
由于二叉搜索樹(shù)定義上的特殊性,只需根據(jù)輸入的 key 值從根開(kāi)始進(jìn)行比較,若小于根的 key 值,則與根的左子樹(shù)比較,大于根的key值與根的右子樹(shù)比較,以此類推,找到則返回相應(yīng)節(jié)點(diǎn),否則返回 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é)點(diǎn)
與查找操作相似,由于二叉搜索樹(shù)的特殊性,待插入的節(jié)點(diǎn)也需要從根節(jié)點(diǎn)開(kāi)始進(jìn)行比較,小于根節(jié)點(diǎn)則與根節(jié)點(diǎn)左子樹(shù)比較,反之則與右子樹(shù)比較,直到左子樹(shù)為空或右子樹(shù)為空,則插入到相應(yīng)為空的位置,在比較的過(guò)程中要注意保存父節(jié)點(diǎn)的信息 及 待插入的位置是父節(jié)點(diǎn)的左子樹(shù)還是右子樹(shù),才能插入到正確的位置。
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; } }
三、遍歷二叉搜索樹(shù)
遍歷操作與遍歷普通二叉樹(shù)操作完全相同,不贅述。
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é)點(diǎn)。
在二叉搜索樹(shù)中刪除節(jié)點(diǎn)操作較復(fù)雜,可分為以下三種情況。
1、待刪除的節(jié)點(diǎn)為葉子節(jié)點(diǎn),可直接刪除。
public boolean delete(int key) { Node currentNode = root;//用來(lái)保存待刪除節(jié)點(diǎn) Node parentNode = root;//用來(lái)保存待刪除節(jié)點(diǎn)的父親節(jié)點(diǎn) boolean isLeftChild = true;//用來(lái)確定待刪除節(jié)點(diǎn)是父親節(jié)點(diǎn)的左孩子還是右孩子 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ǎng)h除的節(jié)點(diǎn)為葉子節(jié)點(diǎn) if (currentNode == root) root = null; else if (isLeftChild) parentNode.leftChild = null; else parentNode.rightChild = null; } ...... }
2、待刪除節(jié)點(diǎn)只有一個(gè)孩子節(jié)點(diǎn)
例如刪除圖一中的 key 值為 11 的節(jié)點(diǎn),只需將 key 值為 13 的節(jié)點(diǎn)的左孩子指向 key 值為 12的節(jié)點(diǎn)即可達(dá)到刪除 key 值為 11 的節(jié)點(diǎn)的目的。
由以上分析可得代碼如下(接上述 delete 方法省略號(hào)后):
else if (currentNode.rightChild == null) {//要?jiǎng)h除的節(jié)點(diǎn)只有左孩子 if (currentNode == root) root = currentNode.leftChild; else if (isLeftChild) parentNode.leftChild = currentNode.leftChild; else parentNode.rightChild = currentNode.leftChild; } else if (currentNode.leftChild == null) {//要?jiǎng)h除的節(jié)點(diǎn)只有右孩子 if (currentNode == root) root = currentNode.rightChild; else if (isLeftChild) parentNode.leftChild = currentNode.rightChild; else parentNode.rightChild = currentNode.rightChild; } ......
3、待刪除節(jié)點(diǎn)既有左孩子,又有右孩子。
例如刪除圖一中 key 值為 10 的節(jié)點(diǎn),這時(shí)就需要用 key 值為 10 的節(jié)點(diǎn)的中序后繼節(jié)點(diǎn)(節(jié)點(diǎn) 11)來(lái)代替 key 值為 10 的節(jié)點(diǎn),并刪除 key 值為 10 的節(jié)點(diǎn)的中序后繼節(jié)點(diǎn),由中序遍歷相關(guān)規(guī)則可知, key 值為 10 的節(jié)點(diǎn)的直接中序后繼節(jié)點(diǎn)一定是其右子樹(shù)中 key 值最小的節(jié)點(diǎn),所以此中序后繼節(jié)點(diǎn)一定不含子節(jié)點(diǎn)或者只含有一個(gè)右孩子,刪除此中序后繼節(jié)點(diǎn)就屬于上述 1,2 所述情況。圖一中 key 值為 10 的節(jié)點(diǎn)的直接中序后繼節(jié)點(diǎn) 為 11,節(jié)點(diǎn) 11 含有一個(gè)右孩子 12。
所以刪除 圖一中 key 值為 10 的節(jié)點(diǎn)分為以下幾步:
a、找到 key 值為 10 的節(jié)點(diǎn)的直接中序后繼節(jié)點(diǎn)(即其右子樹(shù)中值最小的節(jié)點(diǎn) 11),并刪除此直接中序后繼節(jié)點(diǎn)。
private Node getDirectPostNode(Node delNode) {//方法作用為得到待刪除節(jié)點(diǎn)的直接后繼節(jié)點(diǎn) Node parentNode = delNode;//用來(lái)保存待刪除節(jié)點(diǎn)的直接后繼節(jié)點(diǎn)的父親節(jié)點(diǎn) Node direcrPostNode = delNode;//用來(lái)保存待刪除節(jié)點(diǎn)的直接后繼節(jié)點(diǎn) Node currentNode = delNode.rightChild; while (currentNode != null) { parentNode = direcrPostNode; direcrPostNode = currentNode; currentNode = currentNode.leftChild; } if (direcrPostNode != delNode.rightChild) {//從樹(shù)中刪除此直接后繼節(jié)點(diǎn) parentNode.leftChild = direcrPostNode.rightChild; direcrPostNode.rightChild = null; } return direcrPostNode;//返回此直接后繼節(jié)點(diǎn) }
b、將此后繼節(jié)點(diǎn)的 key、value 值賦給待刪除節(jié)點(diǎn)的 key,value值。(接情況二中省略號(hào)代碼之后)
else { //要?jiǎng)h除的節(jié)點(diǎn)既有左孩子又有右孩子 //思路:用待刪除節(jié)點(diǎn)右子樹(shù)中的key值最小節(jié)點(diǎn)的值來(lái)替代要?jiǎng)h除的節(jié)點(diǎn)的值,然后刪除右子樹(shù)中key值最小的節(jié)點(diǎn) //右子樹(shù)key最小的節(jié)點(diǎn)一定不含左子樹(shù),所以刪除這個(gè)key最小的節(jié)點(diǎn)一定是屬于葉子節(jié)點(diǎn)或者只有右子樹(shù)的節(jié)點(diǎn) Node directPostNode = getDirectPostNode(currentNode); currentNode.key = directPostNode.key; currentNode.value = directPostNode.value; }
至此刪除指定節(jié)點(diǎn)的操作結(jié)束。
最后給出完整代碼及簡(jiǎn)單測(cè)試代碼及測(cè)試結(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ǎng)h除的節(jié)點(diǎn)為葉子節(jié)點(diǎn) if (currentNode == root) root = null; else if (isLeftChild) parentNode.leftChild = null; else parentNode.rightChild = null; } else if (currentNode.rightChild == null) {//要?jiǎng)h除的節(jié)點(diǎn)只有左孩子 if (currentNode == root) root = currentNode.leftChild; else if (isLeftChild) parentNode.leftChild = currentNode.leftChild; else parentNode.rightChild = currentNode.leftChild; } else if (currentNode.leftChild == null) {//要?jiǎng)h除的節(jié)點(diǎn)只有右孩子 if (currentNode == root) root = currentNode.rightChild; else if (isLeftChild) parentNode.leftChild = currentNode.rightChild; else parentNode.rightChild = currentNode.rightChild; } else { //要?jiǎng)h除的節(jié)點(diǎn)既有左孩子又有右孩子 //思路:用待刪除節(jié)點(diǎn)右子樹(shù)中的key值最小節(jié)點(diǎn)的值來(lái)替代要?jiǎng)h除的節(jié)點(diǎn)的值,然后刪除右子樹(shù)中key值最小的節(jié)點(diǎn) //右子樹(shù)key最小的節(jié)點(diǎn)一定不含左子樹(shù),所以刪除這個(gè)key最小的節(jié)點(diǎn)一定是屬于葉子節(jié)點(diǎn)或者只有右子樹(shù)的節(jié)點(diǎn) Node directPostNode = getDirectPostNode(currentNode); currentNode.key = directPostNode.key; currentNode.value = directPostNode.value; } return true; } private Node getDirectPostNode(Node delNode) {//方法作用為得到待刪除節(jié)點(diǎn)的直接后繼節(jié)點(diǎn) Node parentNode = delNode;//用來(lái)保存待刪除節(jié)點(diǎn)的直接后繼節(jié)點(diǎn)的父親節(jié)點(diǎn) Node direcrPostNode = delNode;//用來(lái)保存待刪除節(jié)點(diǎn)的直接后繼節(jié)點(diǎn) Node currentNode = delNode.rightChild; while (currentNode != null) { parentNode = direcrPostNode; direcrPostNode = currentNode; currentNode = currentNode.leftChild; } if (direcrPostNode != delNode.rightChild) {//從樹(shù)中刪除此直接后繼節(jié)點(diǎn) parentNode.leftChild = direcrPostNode.rightChild; direcrPostNode.rightChild = null; } return direcrPostNode;//返回此直接后繼節(jié)點(diǎn) } 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)造圖一所示的二叉樹(shù) 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é)點(diǎn)10之后遍歷結(jié)果"); tree.delete(10);//刪除操作 tree.inOrder(tree.root); } }
測(cè)試結(jié)果:
關(guān)于Java 如何利用二叉搜索樹(shù)實(shí)現(xiàn)查找、插入、刪除、遍歷就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。