二叉查找樹 Java實作
定義:
一棵二叉查找樹是一棵二叉樹,每個節點都含有一個Comparable的鍵(以及對應的值)。
每個節點的鍵都大于左子樹中任意節點的鍵而小于右子樹中任意節點的鍵。
image
樹的術語:
Name Function
路徑 順着連接配接點的邊從一個節點走向另一個節點,所經過的節點的順序排列就稱為路徑。
根 樹頂端的節點就稱為根,一棵樹隻有一個根,如果要把一個節點和邊的集合定義為樹,那麼從根到其他任何一個節點都必須有一條路徑。
父節點 每個節點(除了根)都恰好有一條邊向上連接配接到另一個節點,上面的節點就稱為下面節點的“父節點”。
子節點 每個節點都可能有一條或多條邊向下連接配接其他節點,下面的這些節點就稱為它的“子節點”。
葉節點 沒有子節點的節點稱為“葉子節點”或簡稱“葉節點”。樹隻能有一個根,但是可以有很多葉節點。
子樹 每個節點都可以作為子樹的根,它和它所有的子節點,子節點的子節點等都含在子樹中。
通路 當程式控制流程到達某個節點的時候,就稱為“通路”這個節點,通常是為了在這個節點處執行某種操作,例如檢視節點某個資料字段的值或者顯示節點。
周遊 周遊樹意味着要遵循某種特定的順序通路樹中的所有節點。
層 一個節點的層數是指從根開始到這個節點有多少“代”。
關鍵字 可以看到,對象中通常會有一個資料域被指定為關鍵字值。這個值通常用于查詢或者其他操作。
二叉樹 如果樹中的每個節點最多隻能有兩個子節點,這樣的樹就稱為“二叉樹”。
性質:
若它的左子樹不為空,則左子樹上的所有節點的值都小于它的根節點的值;
若它的右子樹不為空,則右子樹上所有節點的值都大于它的根節點的值;
其他的左右子樹也分别為二叉查找樹;
二叉查找樹是動态查找表,在查找的過程中可見添加和删除相應的元素,在這些操作中需要保持二叉查找樹的以上性質。
根據其二叉樹的特性,節點類如下:
public class Node {
public int index;//關鍵字段
public String data;//值
public Node leftNode;//左節點
public Node rightNode;//右節點
@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
其中引用了commons-lang3包中的内容,作為對象進行比較
查找某個節點,相當于二分查找,如果小于目前節點,則走左邊,如果大于目前節點,則走右邊。當最後葉子節點還沒有找到,則沒有找到
public Node findNode(int key){
Node current = root;
while(current.index != key){
if(key < current.index){//左節點
current = current.leftNode;
}else{//右節點
current = current.rightNode;
}
if(current == null){
return null;
}
}
return current;
插入節點,按照插入的節點都不會出現重複關鍵字。每一次插入都将變為根節點的子節點,例如根節點關鍵字為1,接下來插入的節點則變為根節點的右子節點。
public void insertNode(int key,String value){
Node node = new Node();
node.index = key;
node.data = value;
if(root == null){
root = node;
return;
}
//找到插入節點的位置
Node parent = root;
Node current = root;
while(true){
parent = current;
if(key == current.index){
return;
}
if(key < current.index){//左節點
current = current.leftNode;
if(current == null){//目前節點已經是葉子結點了
parent.leftNode = node;
return;
}
}else{
current = current.rightNode;
if(current == null){
parent.rightNode = node;
return;
}
}
}
周遊節點,中序周遊.
調用自身來周遊節點的左子樹
通路這個節點
掉用自身來周遊節點的右子樹
public void inOrder(Node localRoot) {
if (localRoot != null) {
inOrder(localRoot.leftNode);
System.out.println("索引:" + localRoot.index + ",值:" + localRoot.data);
inOrder(localRoot.rightNode);
}
}
删除節點,分三種情況:
删除節點沒有子節點,那麼将父節點的左節點或者是右節點設定為空
删除節點隻有一個子節點,删除該節點後,該節點的子節點變為父節點的子節點,如果删除節點時父節點的左節點,那麼父節點的左節點指向該節點的子節點,反之則右節點指向删除節點的子節點。
删除節點有兩個位元組點,删除了該節點後,則需要選擇一個後繼節點,并且還不破壞該二叉樹的特性(左節點要小于右節點),一般是從删除節點的右節點中找到一個後繼節點,而這個節點是右子樹的最小值。
public boolean delete(int key) {
Node current = root;
Node parent = root;
boolean isLeftChild = true;
//找到被删除的節點,并辨別該節點是否為左節點
while (current.index != key) {
parent = current;
if (key < current.index) {
isLeftChild = true;
current = current.leftNode;
} else {
isLeftChild = false;
current = current.rightNode;
}
if (current == null) {
return false;
}
}
//第一種情況,删除節點為子節點
if (current.leftNode == null && current.rightNode == null) {
if (current == root) {
root = null;
} else {
if (isLeftChild) {
parent.leftNode = null;
} else {
parent.rightNode = null;
}
}
} else if ((current.leftNode != null && current.rightNode == null) || (current.leftNode == null && current.rightNode != null)) {
//第二中情況,删除節點隻包含一個子節點,則将子節點移動動目前節點中
if (current.rightNode == null) {//删除的節點的左節點有值,右節點為空
if (root == current) {
root = current.leftNode;
} else {
if (isLeftChild) {
parent.leftNode = current.leftNode;
} else {
parent.rightNode = current.leftNode;
}
}
} else {//删除的節點的右節點有值,左節點為空
if (root == current) {
root = current.rightNode;
} else {
if (isLeftChild) {
parent.leftNode = current.rightNode;
} else {
parent.rightNode = current.rightNode;
}
}
}
} else if (current.leftNode != null && current.rightNode != null) {
//第三種情況,删除節點中有左右兩個節點
//找到後繼節點
Node processer = processer(current);
if (current == root) {//删除是根節點,則
root = processer;
} else {
if (isLeftChild) {
parent.leftNode = processer;
} else {
parent.rightNode = processer;
}
}
//選中的節點的左節點與删除節點的左節點相連
processer.leftNode = current.leftNode;
}
return true;
}
//找到後繼節點
private Node processer(Node delNode) {
Node parent = delNode;
Node success = delNode;
Node current = delNode.rightNode;
while (current != null) {
// 後繼節點父節點首先儲存後繼節點的狀态
parent = current;
success = current;
// 後繼節點 不斷的向左更新
current = current.leftNode;
}
// 假如我們找到的後繼節點不直接是 要删除節點的右節點 而是在其右節點那條子樹上面最小的一個節點
if (success != delNode.rightNode) {
//後繼節點的父節點斷開其與後繼節點左邊的引用,重新連接配接上後繼節點的右子節點(因為後繼節點是沒有左子節點的,鎖以要儲存之前樹的狀态,還要把後繼節點的右子節點處理一下,不管 其存在不存在)
parent.leftNode = success.rightNode;
// 這時候後繼節點的右邊已經空了 上一條語句已經将其給了自己父節點的左子節點 然後讓後繼節點的右邊 連接配接要删除節點的右子樹
success.rightNode = delNode.rightNode;
}
return success;
}
原文位址
https://www.cnblogs.com/skyice/p/10618303.html