二叉查詢樹 Java實現
定義:
一棵二叉查詢樹是一棵二叉樹,每個節點都含有一個Comparable的鍵(以及對應的值)。
每個節點的鍵都大於左子樹中任意節點的鍵而小於右子樹中任意節點的鍵。
樹的術語:
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; }