二叉樹構建、新增、刪除和遍歷總結
阿新 • • 發佈:2018-11-11
敬請關注部落格,後期不斷更新優質博文,謝謝
原始碼: ------------------------------------------------------------------------------------ Node.java: package cn.com.tree; /** * 二叉樹節點 * @author daniel zhou * */ public class Node {
// 資料項
public
long
data
;
// 資料項
public
String
sData
;
// 左子節點
public
Node
leftChild
;
// 右子節點
public
Node
rightChild
;
/**
* 構造方法
*
@param
data
*
@param
sData
*/
public
Node(
long
data, String sData){
this
.
data
= data;
this
.
sData
= sData;
}
}
--------------------------------------------------------------------------------
Tree.java:
package cn.com.tree; /** * 二叉樹類 * @author daniel zhou * */ public class Tree { // 根節點 public Node root ; /** * 原理:在一顆二叉樹上插入節點,插入是建立在小於父節點, * 則插入到父節點左邊,如果大於父節點,則插入到父節點右邊。 * 在插入樹節點,需要判斷樹根是否為空,如果不為空,則需要 * 當前節點指向樹根和父節點指向當前節點。直到當前節點等於null, * 那麼可以在父節點左邊或者右邊插入新節點,並且返回樹根跳出迴圈。 * 如果樹根為空,直接把樹根指向新建立的節點,實現過程如下所示: * * 插入節點 * @param value */ public void insert( long value, String sValue){ // 封裝節點 Node newNode = new Node(value, sValue); // 引用當前節點 Node current = root ; // 引用父節點 Node parent; // 如果root為空,也就是第一次插入的時候 if ( root == null ){ root = newNode; return ; } else { while ( true ){ // 父節點指向當前節點 parent = current; // 如果當前指向的節點資料比插入的要大,則向左走 if (current. data > value){ current = current. leftChild ; if (current == null ){ parent. leftChild = newNode; return ; } } else { current = current. rightChild ; if (current == null ){ parent. rightChild = newNode; return ; } } } } } /** * 查詢節點 * @param value * @return */ public Node find( long value){ // 引用當前節點 Node current = root ; // 迴圈,只要查詢值不等於當前節點的資料項 while (current. data != value){ // 進行比較,比較查詢值和當前節點的大小 if (current. data > value){ current = current. leftChild ; } else { current = current. rightChild ; } // 如果查詢不到 if (current == null ){ return null ; } } return current; }
二叉樹刪除節點原理圖: 由於過程比較複雜,這裡用圖來表示
/** * 刪除節點: * 工作原理: 從二叉查詢樹上刪除節點的操作複雜程度取決於刪除哪個節點。如果刪除沒有子節點的節點就非常簡單, 如果節點只有一個子節點,不管是左子節點還是右子節點,就變得稍微有點複雜,如果節點包含兩個子節點就最複雜。 如果待刪除節點是葉子節點,那麼只需要將從父節點指向它的連結指向null。 如果待刪除節點只包含一個子節點,那麼原本指向它的節點就得使其指向它的子節點。 如果待刪除節點包含兩個子節點,那麼我們可以採用兩種方式: 一種是查詢待刪除節點左子樹上的最大值, 一種是查詢待刪除節點右節點上的最小值。 我們採取後者,找到最小值後,將臨時節點上的值複製到待刪除節點,然後再刪除臨時節點。 * @param value */ public boolean delete( long value){ // 引用當前節點,從父節點開始 Node current = root ; // 引用當前節點父節點 Node parent = root ; // 是否為左節點 boolean isLeftChild = true ; while (current. data != value){ parent = current; // 進行比較 if (current. data > value){ // 向左走 current = current. leftChild ; isLeftChild = true ; } else { // 向左走 current = current. rightChild ; isLeftChild = false ; } // 如果查詢不到 if (current == null ){ return false ; } } // 找到對應的節點 if (current. leftChild == null && current. rightChild == null ){ // 1,該節點沒有子節點,直接刪除葉子節點 // 如果是根節點 if (current == root ){ root = null ; } else if (isLeftChild){ // 如果該節點是其父節點的左子節點,將其父節點的左子節點置為null parent. leftChild = null ; } else { // 如果該節點是其父節點的右子節點,將其父節點的右子節點置為null parent. rightChild = null ; } } else if (current. rightChild == null ){ // 2,該節點沒有右子節點,直接將該節點的左子節點掛在其父節點的左/右節點上 if (current == root ){ root = current. leftChild ; } else if (isLeftChild){ // 如果該節點是其父節點的左子節點,將該節點的左子節點掛在其父節點的左子節點上 parent. leftChild = current. leftChild ; } else { // 如果該節點是其父節點的右子節點,將該節點的左子節點掛在其父節點的右子節點上 parent. rightChild = current. leftChild ; } } else if (current. leftChild == null ){ // 3,該節點沒有左子節點,直接將該節點的右子節點掛在其父節點的左/右節點上 if (current == root ){ root = current. rightChild ; } else if (isLeftChild){ // 如果該節點是其父節點的左子節點,將該節點的左子節點掛在其父節點的左子節點上 parent. leftChild = current. rightChild ; } else { // 如果該節點是其父節點的右子節點,將該節點的右子節點掛在其父節點的右子節點上 parent. rightChild = current. rightChild ; } } else { // 4,該節點有左右子節點 // 獲取替代被刪除節點的節點(即:找到該節點的中序後繼節點,並將該節點替代被刪除節點) Node successor = getSuccessor(current); if (current == root ){ root = successor; } else if (isLeftChild){ // 如果該節點是其父節點的左子節點 parent. leftChild = successor; } else { // 如果該節點是其父節點的右子節點 parent. rightChild = successor; } // 固定替代節點的位置(固定其在被刪除節點的位置,右邊位置已經固定,現在固定左邊) successor. leftChild = current. leftChild ; } return true ; } /** 返回替代被刪除節點的節點物件 * 工作原理: * 被刪除的有兩個孩子節點,這種情況最複雜,因為要考慮到刪除之後順序不能亂。 * 所以這種型別的節點要刪除,如果直接刪,真個樹的大小順序就亂了,所以需要考慮, * 在樹中找到一個合適的節點來把這個節點給替換掉,用這種方法來保持整個數的穩定。 * 所以又一個問題又來了了,該找哪個節點來替換它?結論是,需要在樹中找出所有比 * 被刪除節點的值大的所有數,並在這些數中找出一個最小的數來。聽起來很拗,如果 * 把它用圖形來描述的話,就是,從被刪除的節點出發經過它的右節點,然後右節點最 * 左邊的葉子節點就是我們要找的,它有一個專業名詞叫中序後繼節點。下面專門來寫一個方法來找它: */ public Node getSuccessor(Node delNode) { // 替代節點 Node successor = delNode; // 替代節點的父節點 Node successorParent = delNode; // 從該節點的出發經過它的右節點 Node current = delNode. rightChild ; //找到該節點右節點最左邊的葉子節點(就是我們要找的替代該節點的節點) while (current != null ){ successorParent = successor; successor = current; current = current. leftChild ; } // 如果替代節點不為該節點的右節點 if (successor != delNode. rightChild ){ // 替代節點的父節點的左節點=替代節點的右節點 successorParent. leftChild = successor. rightChild ; // 替代節點(已經換到被刪除節點的位置上)的右節點=原來被刪除節點的右節點 successor. rightChild = delNode. rightChild ; } // 如果替代節點為該節點的右節點,直接返回 return successor ; } //*************N--root, L--left, R--right****************// /** 遍歷順序:
1 前序遍歷 遍歷的順序為:ABDGHCEIF /** * 基本思想: 1.訪問根節點 2.前序遍歷左子樹 3.前序遍歷右子樹 * 順序:NLR * 前序遍歷(遞迴遍歷) * @param localNode */ public void frontOrder(Node localNode){ if (localNode != null ){ // 訪問根節點 System. out .println(localNode. data + ", " + localNode. sData ); // 前序遍歷左子樹 frontOrder(localNode. leftChild ); // 前序遍歷右子樹 frontOrder(localNode. rightChild ); } } /** * 前序非遞迴遍歷: 對於任一結點p: a. 訪問結點p,並將結點p入棧; b. 判斷結點p的左孩子是否為空,若為空,則取棧頂結點並進行出棧操作, 並將棧頂結點的右孩子置為當前的結點p,迴圈置a;若不為空,則將p的左孩子置為當前結點p; c. 直到p為空,並且棧為空,則遍歷結束。 * @param localNode */ public void frontOrder2(Node localNode){ }
2 中序遍歷 遍歷的順序為:GDHBAEICF /** * 基本思想: 1.中序遍歷左子樹 2.訪問根節點 3.中序遍歷右子樹 * 中序遍歷 * 順序:LNR * @param localNode */ public void inOrder(Node localNode){ if (localNode != null ){ // 中序遍歷左子樹 inOrder(localNode. leftChild ); // 訪問根節點 &n
原始碼: ------------------------------------------------------------------------------------ Node.java: package cn.com.tree; /** * 二叉樹節點 * @author daniel zhou * */ public class Node {
package cn.com.tree; /** * 二叉樹類 * @author daniel zhou * */ public class Tree { // 根節點 public Node root ; /** * 原理:在一顆二叉樹上插入節點,插入是建立在小於父節點, * 則插入到父節點左邊,如果大於父節點,則插入到父節點右邊。 * 在插入樹節點,需要判斷樹根是否為空,如果不為空,則需要 * 當前節點指向樹根和父節點指向當前節點。直到當前節點等於null, * 那麼可以在父節點左邊或者右邊插入新節點,並且返回樹根跳出迴圈。 * 如果樹根為空,直接把樹根指向新建立的節點,實現過程如下所示: * * 插入節點 * @param value */ public void insert( long value, String sValue){ // 封裝節點 Node newNode = new Node(value, sValue); // 引用當前節點 Node current = root ; // 引用父節點 Node parent; // 如果root為空,也就是第一次插入的時候 if ( root == null ){ root = newNode; return ; } else { while ( true ){ // 父節點指向當前節點 parent = current; // 如果當前指向的節點資料比插入的要大,則向左走 if (current. data > value){ current = current. leftChild ; if (current == null ){ parent. leftChild = newNode; return ; } } else { current = current. rightChild ; if (current == null ){ parent. rightChild = newNode; return ; } } } } } /** * 查詢節點 * @param value * @return */ public Node find( long value){ // 引用當前節點 Node current = root ; // 迴圈,只要查詢值不等於當前節點的資料項 while (current. data != value){ // 進行比較,比較查詢值和當前節點的大小 if (current. data > value){ current = current. leftChild ; } else { current = current. rightChild ; } // 如果查詢不到 if (current == null ){ return null ; } } return current; }
二叉樹刪除節點原理圖: 由於過程比較複雜,這裡用圖來表示
/** * 刪除節點: * 工作原理: 從二叉查詢樹上刪除節點的操作複雜程度取決於刪除哪個節點。如果刪除沒有子節點的節點就非常簡單, 如果節點只有一個子節點,不管是左子節點還是右子節點,就變得稍微有點複雜,如果節點包含兩個子節點就最複雜。 如果待刪除節點是葉子節點,那麼只需要將從父節點指向它的連結指向null。 如果待刪除節點只包含一個子節點,那麼原本指向它的節點就得使其指向它的子節點。 如果待刪除節點包含兩個子節點,那麼我們可以採用兩種方式: 一種是查詢待刪除節點左子樹上的最大值, 一種是查詢待刪除節點右節點上的最小值。 我們採取後者,找到最小值後,將臨時節點上的值複製到待刪除節點,然後再刪除臨時節點。 * @param value */ public boolean delete( long value){ // 引用當前節點,從父節點開始 Node current = root ; // 引用當前節點父節點 Node parent = root ; // 是否為左節點 boolean isLeftChild = true ; while (current. data != value){ parent = current; // 進行比較 if (current. data > value){ // 向左走 current = current. leftChild ; isLeftChild = true ; } else { // 向左走 current = current. rightChild ; isLeftChild = false ; } // 如果查詢不到 if (current == null ){ return false ; } } // 找到對應的節點 if (current. leftChild == null && current. rightChild == null ){ // 1,該節點沒有子節點,直接刪除葉子節點 // 如果是根節點 if (current == root ){ root = null ; } else if (isLeftChild){ // 如果該節點是其父節點的左子節點,將其父節點的左子節點置為null parent. leftChild = null ; } else { // 如果該節點是其父節點的右子節點,將其父節點的右子節點置為null parent. rightChild = null ; } } else if (current. rightChild == null ){ // 2,該節點沒有右子節點,直接將該節點的左子節點掛在其父節點的左/右節點上 if (current == root ){ root = current. leftChild ; } else if (isLeftChild){ // 如果該節點是其父節點的左子節點,將該節點的左子節點掛在其父節點的左子節點上 parent. leftChild = current. leftChild ; } else { // 如果該節點是其父節點的右子節點,將該節點的左子節點掛在其父節點的右子節點上 parent. rightChild = current. leftChild ; } } else if (current. leftChild == null ){ // 3,該節點沒有左子節點,直接將該節點的右子節點掛在其父節點的左/右節點上 if (current == root ){ root = current. rightChild ; } else if (isLeftChild){ // 如果該節點是其父節點的左子節點,將該節點的左子節點掛在其父節點的左子節點上 parent. leftChild = current. rightChild ; } else { // 如果該節點是其父節點的右子節點,將該節點的右子節點掛在其父節點的右子節點上 parent. rightChild = current. rightChild ; } } else { // 4,該節點有左右子節點 // 獲取替代被刪除節點的節點(即:找到該節點的中序後繼節點,並將該節點替代被刪除節點) Node successor = getSuccessor(current); if (current == root ){ root = successor; } else if (isLeftChild){ // 如果該節點是其父節點的左子節點 parent. leftChild = successor; } else { // 如果該節點是其父節點的右子節點 parent. rightChild = successor; } // 固定替代節點的位置(固定其在被刪除節點的位置,右邊位置已經固定,現在固定左邊) successor. leftChild = current. leftChild ; } return true ; } /** 返回替代被刪除節點的節點物件 * 工作原理: * 被刪除的有兩個孩子節點,這種情況最複雜,因為要考慮到刪除之後順序不能亂。 * 所以這種型別的節點要刪除,如果直接刪,真個樹的大小順序就亂了,所以需要考慮, * 在樹中找到一個合適的節點來把這個節點給替換掉,用這種方法來保持整個數的穩定。 * 所以又一個問題又來了了,該找哪個節點來替換它?結論是,需要在樹中找出所有比 * 被刪除節點的值大的所有數,並在這些數中找出一個最小的數來。聽起來很拗,如果 * 把它用圖形來描述的話,就是,從被刪除的節點出發經過它的右節點,然後右節點最 * 左邊的葉子節點就是我們要找的,它有一個專業名詞叫中序後繼節點。下面專門來寫一個方法來找它: */ public Node getSuccessor(Node delNode) { // 替代節點 Node successor = delNode; // 替代節點的父節點 Node successorParent = delNode; // 從該節點的出發經過它的右節點 Node current = delNode. rightChild ; //找到該節點右節點最左邊的葉子節點(就是我們要找的替代該節點的節點) while (current != null ){ successorParent = successor; successor = current; current = current. leftChild ; } // 如果替代節點不為該節點的右節點 if (successor != delNode. rightChild ){ // 替代節點的父節點的左節點=替代節點的右節點 successorParent. leftChild = successor. rightChild ; // 替代節點(已經換到被刪除節點的位置上)的右節點=原來被刪除節點的右節點 successor. rightChild = delNode. rightChild ; } // 如果替代節點為該節點的右節點,直接返回 return successor ; } //*************N--root, L--left, R--right****************// /** 遍歷順序:
- 前序遍歷:NLR
1 前序遍歷 遍歷的順序為:ABDGHCEIF /** * 基本思想: 1.訪問根節點 2.前序遍歷左子樹 3.前序遍歷右子樹 * 順序:NLR * 前序遍歷(遞迴遍歷) * @param localNode */ public void frontOrder(Node localNode){ if (localNode != null ){ // 訪問根節點 System. out .println(localNode. data + ", " + localNode. sData ); // 前序遍歷左子樹 frontOrder(localNode. leftChild ); // 前序遍歷右子樹 frontOrder(localNode. rightChild ); } } /** * 前序非遞迴遍歷: 對於任一結點p: a. 訪問結點p,並將結點p入棧; b. 判斷結點p的左孩子是否為空,若為空,則取棧頂結點並進行出棧操作, 並將棧頂結點的右孩子置為當前的結點p,迴圈置a;若不為空,則將p的左孩子置為當前結點p; c. 直到p為空,並且棧為空,則遍歷結束。 * @param localNode */ public void frontOrder2(Node localNode){ }
2 中序遍歷 遍歷的順序為:GDHBAEICF /** * 基本思想: 1.中序遍歷左子樹 2.訪問根節點 3.中序遍歷右子樹 * 中序遍歷 * 順序:LNR * @param localNode */ public void inOrder(Node localNode){ if (localNode != null ){ // 中序遍歷左子樹 inOrder(localNode. leftChild ); // 訪問根節點 &n