1. 程式人生 > >【LeetCode & 劍指offer刷題】樹題2:二叉查詢樹的查詢、插入、刪除

【LeetCode & 劍指offer刷題】樹題2:二叉查詢樹的查詢、插入、刪除

【LeetCode & 劍指offer 刷題筆記】目錄(持續更新中...)

二叉查詢樹的查詢、插入、刪除

  1 查詢結點 最佳情況是 O(log­ 2 n),而最壞情況是 O(n)
  • BST 的查詢是從根結點開始,若二叉樹非空,將給定值與根結點的關鍵字比較,
  • 若相等,則查詢成功;
  • 若不等,則比較查詢結點值與當前結點值大小,當給定值小於當前結點值時,在當前結點左子樹中查詢,否則在右子樹中查詢
typedef struct Node {      int key ;     Node * left ;     Node * right ;     Node *
parent ; } * BSTree ; /**  * 遞迴查詢:返回指向包含關鍵字 k 的結點的指標  */ Node * BST_Search ( BSTree T , int k ) {      if (
T == NULL || k == T -> key )          return T ;      if ( k < T -> key )          return BST_Search ( T -> left , k );      else          return BST_Search ( T -> right , k );   2 插入結點 最佳情況是 O(log­ 2 n),而最壞情況是 O(n)         當向樹中插入一個新的結點時,該結點將總是作為葉子結點 。所以,最困難的地方就是 如何找到該結點的父結點 類似於查詢演算法中的描述,我們 將這個新的結點稱為結點 n,而遍歷的當前結點稱為結點 c 。開始時,結點 c 為 BST 的根結點。則定位結點 n 父結點的步驟如下:
  1. 在二叉樹中搜尋( 若插入值小於當前結點值,則在左子樹中遞迴插入,否則在右子樹中遞迴插入,直到遍歷到空結點,插入到此位置 (聯絡在有序陣列中二分插入一值)
  2. 如果結點 c 為空 ,將結點 插入 到c位置。
  3. 如果結點c不為空比較結點 c 與結點 n 的值。
  4. 如果結點 c 的值與結點 n 的值相等,則說明使用者在試圖插入一個重複的結點。解決辦法可以是直接丟棄結點 n,或者可以丟擲異常。
  5. 如果結點 n 的值小於結點 c 的值,則說明結點 n 一定是在結點 c 的左子樹中。則將父結點設定為結點 c,並將結點 c 設定為結點 c 的左孩子,然後返回至第 1 步。
  6. 如果結點 n 的值大於結點 c 的值,則說明結點 n 一定是在結點 c 的右子樹中。則將父結點設定為結點 c,並將結點 c 設定為結點 c 的右孩子,然後返回至第 1 步
  當合適的結點找到時,該演算法結束。從而使新結點被放入 BST 中成為某一父結點合適的孩子結點。   /**  * 插入:將關鍵字 k 插入到二叉查詢樹  */ int BST_Insert ( BSTree & T , int k , Node * parent ) {      if ( T == NULL )      {         T = ( BSTree ) malloc ( sizeof ( Node ));         T -> key = k ;         T -> left = NULL ;         T -> right = NULL ;         T -> parent = parent ;          return 1 ;   // 返回 1 表示成功      }      else if ( k == T -> key )          return 0 ;   // 樹中存在相同關鍵字      else if ( k < T -> key )          return BST_Insert ( T -> left , k , T );      else          return BST_Insert ( T -> right , k , T ); }     3 刪除結點 最佳情況是 O(log­2n),而最壞情況是 O(n)                 從 BST 中刪除節點比插入節點難度更大。因為刪除一個非葉子節點,就必須選擇其他節點來填補因刪除節點所造成的樹的斷裂。如果不選擇節點來填補這個斷裂,那麼就違背了 BST 的性質要求。          刪除節點演算法的第一步是定位要被刪除的節點 ,這可以使用前面介紹的查詢演算法,因此執行時間為 O(log­ 2 n)。 接著應該選擇合適的節點來代替刪除節點的位置 ,它共有三種情況需要考慮
  • 情況 1: 如果刪除的節點沒有右孩子,那麼就選擇它的左孩子來代替原來的節點 。二叉查詢樹的性質保證了被刪除節點的左子樹必然符合二叉查詢樹的性質。因此左子樹的值要麼都大於,要麼都小於被刪除節點的父節點的值,這取決於被刪除節點是左孩子還是右孩子。因此用被刪除節點的左子樹來替代被刪除節點,是完全符合二叉搜尋樹的性質的。
  • 情況 2: 如果被刪除節點的右孩子沒有左孩子 ,那麼這個右孩子被用來替換被刪除節點。因為被刪除節點的右孩子都大於被刪除節點左子樹的所有節點,同時也大於或小於被刪除節點的父節點,這同樣取決於被刪除節點是左孩子還是右孩子。因此,用右孩子來替換被刪除節點,符合二叉查詢樹的性質。
  • 情況 3: 如果被刪除節點的右孩子有左孩子 ,就需要用被刪除節點右孩子的左子樹中的最下面的節點來替換它,就是說,我們 用被刪除節點的右子樹中最小值的節點來替換
或者用程式碼中的思路 /* 二叉查詢樹的結點刪除 (1) 若被刪除結點z 是葉子結點,則直接刪除,不會破壞二叉排序樹的性質; (2) 若結點z 只有左子樹或只有右子樹,則讓 z 的子樹成為 z 父結點的子樹,替代 z 的位置; (3) 若結點z 既有左子樹,又有右子樹,則 用二叉樹中序遍歷z的後繼(Successor)代替z(由於是二叉查詢樹,這個後繼是子樹中最小的值) ,然後從二叉查詢樹中刪除這個後繼,這樣就轉換成了第一或第二種情況。 */ void BST_Delete ( BSTree & T , Node * z ) {      if ( z -> left == NULL && z -> right == NULL ) //被刪除的是葉子結點,直接刪除      {          if ( z -> parent != NULL )          {              if ( z -> parent -> left == z )                 z -> parent -> left = NULL ;              else                 z -> parent -> right = NULL ;          }          else          {             T = NULL ; // 只剩一個結點的情況          }         free ( z );      }      else if ( z -> left != NULL && z -> right == NULL ) //只有右子樹,則讓子樹替代z的位置      {         z -> left -> parent = z -> parent ;          if ( z -> parent != NULL )          {              if ( z -> parent -> left == z )                 z -> parent -> left = z -> left ;              else                 z -> parent -> right = z -> left ;          }          else          {             T = z -> left ;   // 刪除左斜單支樹的根結點          }         free ( z );      }      else if ( z -> left == NULL && z -> right != NULL ) //只有左子樹      {         z -> right -> parent = z -> parent ;          if ( z -> parent != NULL )          {              if ( z -> parent -> left == z )                 z -> parent -> left = z -> right ;              else                 z -> parent -> right = z -> right ;          }          else          {             T = z -> right ;   // 刪除右斜單支樹的根結點          }         free ( z );      }      else //既有左子樹又有右子樹,讓z的後繼代替z,然後刪除這個後繼      {         Node * s = BST_Successor ( z );         z -> key = s -> key ;    // s 的關鍵字替換 z 的關鍵字         BST_Delete ( T , s );   // 轉換為第一或第二種情況      } } /**  * 後繼:求二叉樹中序遍歷的下一個結點   方法: (1) 有右子樹的,那麼下個結點就是右子樹最左邊的點; (2) 沒有右子樹的,是父結點的左孩子,返回父結點; (3) 沒有右子樹,是父結點的右孩子,從結點 x 開始向上查詢,直到遍歷到的結點是其父結點的左孩子位置,返回該父結點  */ Node * BST_Successor ( Node * node ) {      if ( node -> right ) // 有右子樹時,下個結點為右子樹最左結點      {         Node * t = node -> right ; // 右子樹          while ( t -> left )             t = t -> left ;          return t ;      }      else // 無右子樹時,返回父結點 ( 向上查詢,結點為左孩子的父結點 )      {         Node * par = node -> parent ; // 父結點          while ( par &&   node != par->left ) // 如果當前結點是父結點的右孩子,向上遍歷,直到當前結點為左結點          {             node = par ;             par = par -> parent ;          } // 退出時,當前結點為父結點的左孩子          return par ; // 返回父結點      } }   參考: http://www.cnblogs.com/gaochundong/p/binary_search_tree.html https://songlee24.github.io/2015/01/13/binary-search-tree/